diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 96fe9c21..4961124a 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -6,7 +6,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "097.27"; + static const string Version = "097.28"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "http://byuu.org/"; diff --git a/higan/gb/system/serialization.cpp b/higan/gb/system/serialization.cpp index 5b56c830..979d0de3 100644 --- a/higan/gb/system/serialization.cpp +++ b/higan/gb/system/serialization.cpp @@ -47,7 +47,7 @@ auto System::serializeAll(serializer& s) -> void { auto System::serializeInit() -> void { serializer s; - uint signature = 0, version = 0, crc32 = 0; + uint signature = 0, version = 0; char hash[64], description[512]; s.integer(signature); diff --git a/higan/processor/v30mz/memory.cpp b/higan/processor/v30mz/memory.cpp index 68e8f20a..bcc9cd24 100644 --- a/higan/processor/v30mz/memory.cpp +++ b/higan/processor/v30mz/memory.cpp @@ -1,32 +1,35 @@ auto V30MZ::read(Size size, uint16 segment, uint16 address) -> uint32 { - uint32 data = read(segment * 16 + address); - if(size == Word) data |= read(segment * 16 + ++address) << 8; - if(size == Long) data |= read(segment * 16 + ++address) << 16; - if(size == Long) data |= read(segment * 16 + ++address) << 24; + uint32 data; + if(size >= Byte) data.byte(0) = read(segment * 16 + address++); + if(size >= Word) data.byte(1) = read(segment * 16 + address++); + if(size >= Long) data.byte(2) = read(segment * 16 + address++); + if(size >= Long) data.byte(3) = read(segment * 16 + address++); return data; } auto V30MZ::write(Size size, uint16 segment, uint16 address, uint16 data) -> void { - write(segment * 16 + address, data); - if(size == Word) write(segment * 16 + ++address, data >> 8); + if(size >= Byte) write(segment * 16 + address++, data.byte(0)); + if(size >= Word) write(segment * 16 + address++, data.byte(1)); } // auto V30MZ::in(Size size, uint16 address) -> uint16 { - uint16 data = in(address); - if(size == Word) data |= in(++address) << 8; + uint16 data; + if(size >= Byte) data.byte(0) = in(address++); + if(size >= Word) data.byte(1) = in(address++); return data; } auto V30MZ::out(Size size, uint16 address, uint16 data) -> void { - out(address, data); - if(size == Word) out(++address, data >> 8); + if(size >= Byte) out(address++, data.byte(0)); + if(size >= Word) out(address++, data.byte(1)); } // auto V30MZ::fetch(Size size) -> uint16 { + wait(size); uint16 data = read(size, r.cs, r.ip); return r.ip += size, data; } diff --git a/higan/processor/v30mz/serialization.cpp b/higan/processor/v30mz/serialization.cpp new file mode 100644 index 00000000..54939a09 --- /dev/null +++ b/higan/processor/v30mz/serialization.cpp @@ -0,0 +1,51 @@ +auto V30MZ::serialize(serializer& s) -> void { + s.integer(state.halt); + s.integer(state.poll); + s.integer(state.prefix); + + s.integer(opcode); + if(s.mode() == serializer::Save) { + uint8 _prefixes[7] = {0}; + uint8 _prefixCount = prefixes.size(); + for(auto n : range(prefixes)) _prefixes[n] = prefixes[n]; + s.integer(_prefixCount); + s.array(_prefixes); + } else { + uint8 _prefixes[7] = {0}; + uint8 _prefixCount = 0; + s.integer(_prefixCount); + s.array(_prefixes); + prefixes.resize(_prefixCount); + for(auto n : range(prefixes)) prefixes[n] = _prefixes[n]; + } + + s.integer(modrm.mod); + s.integer(modrm.reg); + s.integer(modrm.mem); + s.integer(modrm.segment); + s.integer(modrm.address); + + s.integer(r.ax); + s.integer(r.cx); + s.integer(r.dx); + s.integer(r.bx); + s.integer(r.sp); + s.integer(r.bp); + s.integer(r.si); + s.integer(r.di); + s.integer(r.es); + s.integer(r.cs); + s.integer(r.ss); + s.integer(r.ds); + s.integer(r.ip); + s.integer(r.f.m); + s.integer(r.f.v); + s.integer(r.f.d); + s.integer(r.f.i); + s.integer(r.f.b); + s.integer(r.f.s); + s.integer(r.f.z); + s.integer(r.f.h); + s.integer(r.f.p); + s.integer(r.f.c); +} diff --git a/higan/processor/v30mz/v30mz.cpp b/higan/processor/v30mz/v30mz.cpp index e1efd74b..508297bb 100644 --- a/higan/processor/v30mz/v30mz.cpp +++ b/higan/processor/v30mz/v30mz.cpp @@ -15,6 +15,7 @@ namespace Processor { #include "instructions-misc.cpp" #include "instructions-move.cpp" #include "instructions-string.cpp" +#include "serialization.cpp" #include "disassembler.cpp" auto V30MZ::debug(string text) -> void { @@ -53,6 +54,8 @@ auto V30MZ::exec() -> void { } auto V30MZ::interrupt(uint8 vector) -> void { + wait(32); + state.halt = false; state.poll = true; state.prefix = false; @@ -81,10 +84,7 @@ auto V30MZ::interrupt(uint8 vector) -> void { } auto V30MZ::instruction() -> void { - opcode = fetch(); - wait(1); - - switch(opcode) { + switch(opcode = fetch()) { case 0x00: return opAddMemReg(Byte); case 0x01: return opAddMemReg(Word); case 0x02: return opAddRegMem(Byte); diff --git a/higan/processor/v30mz/v30mz.hpp b/higan/processor/v30mz/v30mz.hpp index 16555377..00622b68 100644 --- a/higan/processor/v30mz/v30mz.hpp +++ b/higan/processor/v30mz/v30mz.hpp @@ -203,6 +203,9 @@ struct V30MZ { auto opLoadString(Size); auto opScanString(Size); + //serialization.cpp + auto serialize(serializer&) -> void; + //disassembler.cpp auto disassemble(uint16 cs, uint16 ip, bool registers = true, bool bytes = true) -> string; diff --git a/higan/sfc/coprocessor/sharprtc/time.cpp b/higan/sfc/coprocessor/sharprtc/time.cpp index 3149b346..3fb05327 100644 --- a/higan/sfc/coprocessor/sharprtc/time.cpp +++ b/higan/sfc/coprocessor/sharprtc/time.cpp @@ -21,10 +21,12 @@ auto SharpRTC::tick_hour() -> void { auto SharpRTC::tick_day() -> void { uint days = daysinmonth[month % 12]; - //add one day for leap years - if(year % 400 == 0) days++; - else if(year % 100 == 0); - else if(year % 4 == 0) days++; + //add one day in February for leap years + if(month == 1) { + if(year % 400 == 0) days++; + else if(year % 100 == 0); + else if(year % 4 == 0) days++; + } if(day++ < days) return; day = 1; diff --git a/higan/ws/GNUmakefile b/higan/ws/GNUmakefile index cd3e50d4..ff48dfa7 100644 --- a/higan/ws/GNUmakefile +++ b/higan/ws/GNUmakefile @@ -3,6 +3,7 @@ processors += v30mz objects += ws-interface ws-system ws-scheduler objects += ws-memory ws-eeprom ws-cartridge objects += ws-cpu ws-ppu ws-apu +objects += ws-cheat obj/ws-interface.o: ws/interface/interface.cpp $(call rwildcard,ws/interface/) obj/ws-system.o: ws/system/system.cpp $(call rwildcard,ws/system/) @@ -13,3 +14,4 @@ obj/ws-cartridge.o: ws/cartridge/cartridge.cpp $(call rwildcard,ws/cartridge/) obj/ws-cpu.o: ws/cpu/cpu.cpp $(call rwildcard,ws/cpu/) obj/ws-ppu.o: ws/ppu/ppu.cpp $(call rwildcard,ws/ppu/) obj/ws-apu.o: ws/apu/apu.cpp $(call rwildcard,ws/apu/) +obj/ws-cheat.o: ws/cheat/cheat.cpp $(call rwildcard,ws/cheat/) diff --git a/higan/ws/apu/apu.cpp b/higan/ws/apu/apu.cpp index 98169266..30bba573 100644 --- a/higan/ws/apu/apu.cpp +++ b/higan/ws/apu/apu.cpp @@ -10,6 +10,7 @@ APU apu; #include "channel3.cpp" #include "channel4.cpp" #include "channel5.cpp" +#include "serialization.cpp" auto APU::Enter() -> void { while(true) scheduler.synchronize(), apu.main(); diff --git a/higan/ws/apu/apu.hpp b/higan/ws/apu/apu.hpp index d9bdc752..02c286ba 100644 --- a/higan/ws/apu/apu.hpp +++ b/higan/ws/apu/apu.hpp @@ -10,10 +10,23 @@ struct APU : Thread, IO { auto portRead(uint16 addr) -> uint8; auto portWrite(uint16 addr, uint8 data) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct State { uint13 sweepClock; } s; + struct Registers { + //$008f SND_WAVE_BASE + uint8 waveBase; + + //$0091 SND_OUTPUT + uint1 speakerEnable; + uint2 speakerShift; + uint1 headphoneEnable; + } r; + struct DMA { auto run() -> void; @@ -40,30 +53,6 @@ struct APU : Thread, IO { } r; } dma; - struct Registers { - //$004a-$004c SDMA_SRC - uint20 dmaSource; - - //$004e-$0050 SDMA_LEN - uint20 dmaLength; - - //$0052 SDMA_CTRL - uint2 dmaRate; - uint1 dmaUnknown; - uint1 dmaLoop; - uint1 dmaTarget; - uint1 dmaDirection; - uint1 dmaEnable; - - //$008f SND_WAVE_BASE - uint8 waveBase; - - //$0091 SND_OUTPUT - uint1 speakerEnable; - uint2 speakerShift; - uint1 headphoneEnable; - } r; - struct Channel1 { auto run() -> void; diff --git a/higan/ws/apu/serialization.cpp b/higan/ws/apu/serialization.cpp new file mode 100644 index 00000000..62d70e08 --- /dev/null +++ b/higan/ws/apu/serialization.cpp @@ -0,0 +1,80 @@ +auto APU::serialize(serializer& s) -> void { + s.integer(this->s.sweepClock); + s.integer(r.waveBase); + s.integer(r.speakerEnable); + s.integer(r.speakerShift); + s.integer(r.headphoneEnable); + + s.integer(dma.s.clock); + s.integer(dma.s.source); + s.integer(dma.s.length); + s.integer(dma.r.source); + s.integer(dma.r.length); + s.integer(dma.r.rate); + s.integer(dma.r.unknown); + s.integer(dma.r.loop); + s.integer(dma.r.target); + s.integer(dma.r.direction); + s.integer(dma.r.enable); + + s.integer(channel1.o.left); + s.integer(channel1.o.right); + s.integer(channel1.s.period); + s.integer(channel1.s.sampleOffset); + s.integer(channel1.r.pitch); + s.integer(channel1.r.volumeLeft); + s.integer(channel1.r.volumeRight); + s.integer(channel1.r.enable); + + s.integer(channel2.o.left); + s.integer(channel2.o.right); + s.integer(channel2.s.period); + s.integer(channel2.s.sampleOffset); + s.integer(channel2.r.pitch); + s.integer(channel2.r.volumeLeft); + s.integer(channel2.r.volumeRight); + s.integer(channel2.r.enable); + s.integer(channel2.r.voice); + s.integer(channel2.r.voiceEnableLeft); + s.integer(channel2.r.voiceEnableRight); + + s.integer(channel3.o.left); + s.integer(channel3.o.right); + s.integer(channel3.s.period); + s.integer(channel3.s.sampleOffset); + s.integer(channel3.s.sweepCounter); + s.integer(channel3.r.pitch); + s.integer(channel3.r.volumeLeft); + s.integer(channel3.r.volumeRight); + s.integer(channel3.r.sweepValue); + s.integer(channel3.r.sweepTime); + s.integer(channel3.r.enable); + s.integer(channel3.r.sweep); + + s.integer(channel4.o.left); + s.integer(channel4.o.right); + s.integer(channel4.s.period); + s.integer(channel4.s.sampleOffset); + s.integer(channel4.s.noiseOutput); + s.integer(channel4.s.noiseLFSR); + s.integer(channel4.r.pitch); + s.integer(channel4.r.volumeLeft); + s.integer(channel4.r.volumeRight); + s.integer(channel4.r.noiseMode); + s.integer(channel4.r.noiseReset); + s.integer(channel4.r.noiseUpdate); + s.integer(channel4.r.enable); + s.integer(channel4.r.noise); + + s.integer(channel5.o.left); + s.integer(channel5.o.right); + s.integer(channel5.s.clock); + s.integer(channel5.s.data); + s.integer(channel5.r.volume); + s.integer(channel5.r.scale); + s.integer(channel5.r.speed); + s.integer(channel5.r.enable); + s.integer(channel5.r.unknown); + s.integer(channel5.r.leftEnable); + s.integer(channel5.r.rightEnable); +} diff --git a/higan/ws/cartridge/cartridge.cpp b/higan/ws/cartridge/cartridge.cpp index 3cfb56c8..6c3c594e 100644 --- a/higan/ws/cartridge/cartridge.cpp +++ b/higan/ws/cartridge/cartridge.cpp @@ -4,7 +4,39 @@ namespace WonderSwan { Cartridge cartridge; #include "memory.cpp" +#include "rtc.cpp" #include "io.cpp" +#include "serialization.cpp" + +auto Cartridge::Enter() -> void { + while(true) scheduler.synchronize(), cartridge.main(); +} + +auto Cartridge::main() -> void { + if(rtc.data) { + rtcTickSecond(); + rtcCheckAlarm(); + } + step(3'072'000); +} + +auto Cartridge::step(uint clocks) -> void { + clock += clocks; + if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread); +} + +auto Cartridge::power() -> void { + create(Cartridge::Enter, 3'072'000); + eeprom.power(); + + bus.map(this, 0x00c0, 0x00c8); + if(rtc.data) bus.map(this, 0x00ca, 0x00cb); + + r.romBank0 = 0xff; + r.romBank1 = 0xff; + r.romBank2 = 0xff; + r.sramBank = 0xff; +} auto Cartridge::load() -> void { information.manifest = ""; @@ -39,6 +71,14 @@ auto Cartridge::load() -> void { } } + if(auto node = document["board/rtc"]) { + rtc.name = node["name"].text(); + rtc.size = node["size"].natural(); + rtc.mask = bit::round(rtc.size) - 1; + if(rtc.size) rtc.data = new uint8[rtc.mask + 1](); + if(rtc.name) interface->loadRequest(ID::RTC, rtc.name, false); + } + information.title = document["information/title"].text(); information.orientation = document["information/orientation"].text() == "vertical"; information.sha256 = Hash::SHA256(rom.data, rom.size).digest(); @@ -56,17 +96,12 @@ auto Cartridge::unload() -> void { ram.size = 0; ram.mask = 0; ram.name = ""; -} -auto Cartridge::power() -> void { - eeprom.power(); - - bus.map(this, 0x00c0, 0x00c8); - - r.bank_rom0 = 0xff; - r.bank_rom1 = 0xff; - r.bank_rom2 = 0xff; - r.bank_sram = 0xff; + delete[] rtc.data; + rtc.data = nullptr; + rtc.size = 0; + rtc.mask = 0; + rtc.name = ""; } } diff --git a/higan/ws/cartridge/cartridge.hpp b/higan/ws/cartridge/cartridge.hpp index c25b9b0c..70b04d34 100644 --- a/higan/ws/cartridge/cartridge.hpp +++ b/higan/ws/cartridge/cartridge.hpp @@ -1,22 +1,41 @@ -struct Cartridge : IO { - auto load() -> void; - auto unload() -> void; +struct Cartridge : Thread, IO { + static auto Enter() -> void; + auto main() -> void; + auto step(uint clocks) -> void; auto power() -> void; + auto load() -> void; + auto unload() -> void; + + //memory.cpp auto romRead(uint20 addr) -> uint8; auto romWrite(uint20 addr, uint8 data) -> void; auto ramRead(uint20 addr) -> uint8; auto ramWrite(uint20 addr, uint8 data) -> void; + //rtc.cpp + auto rtcLoad() -> void; + auto rtcSave() -> void; + auto rtcTickSecond() -> void; + auto rtcCheckAlarm() -> void; + auto rtcStatus() -> uint8; + auto rtcCommand(uint8 data) -> void; + auto rtcRead() -> uint8; + auto rtcWrite(uint8 data) -> void; + + //io.cpp auto portRead(uint16 addr) -> uint8 override; auto portWrite(uint16 addr, uint8 data) -> void override; + //serialization.cpp + auto serialize(serializer&) -> void; + struct Registers { - uint8 bank_rom0; - uint8 bank_rom1; - uint8 bank_rom2; - uint8 bank_sram; + uint8 romBank0; + uint8 romBank1; + uint8 romBank2; + uint8 sramBank; } r; struct Memory { @@ -24,9 +43,29 @@ struct Cartridge : IO { uint size = 0; uint mask = 0; string name; - } rom, ram; + }; + struct RTC : Memory { + uint8 command; + uint4 index; + + uint8 alarm; + uint8 alarmHour; + uint8 alarmMinute; + + auto year() -> uint8& { return data[0]; } + auto month() -> uint8& { return data[1]; } + auto day() -> uint8& { return data[2]; } + auto weekday() -> uint8& { return data[3]; } + auto hour() -> uint8& { return data[4]; } + auto minute() -> uint8& { return data[5]; } + auto second() -> uint8& { return data[6]; } + }; + + Memory rom; + Memory ram; EEPROM eeprom; + RTC rtc; struct Information { string manifest; diff --git a/higan/ws/cartridge/io.cpp b/higan/ws/cartridge/io.cpp index 4d21b83f..be4f6fce 100644 --- a/higan/ws/cartridge/io.cpp +++ b/higan/ws/cartridge/io.cpp @@ -1,15 +1,15 @@ auto Cartridge::portRead(uint16 addr) -> uint8 { //BANK_ROM2 - if(addr == 0x00c0) return r.bank_rom2; + if(addr == 0x00c0) return r.romBank2; //BANK_SRAM - if(addr == 0x00c1) return r.bank_sram; + if(addr == 0x00c1) return r.sramBank; //BANK_ROM0 - if(addr == 0x00c2) return r.bank_rom0; + if(addr == 0x00c2) return r.romBank0; //BANK_ROM1 - if(addr == 0x00c3) return r.bank_rom1; + if(addr == 0x00c3) return r.romBank1; //EEP_DATA if(addr == 0x00c4) return eeprom.read(EEPROM::DataLo); @@ -22,42 +22,42 @@ auto Cartridge::portRead(uint16 addr) -> uint8 { //EEP_STATUS if(addr == 0x00c8) return eeprom.read(EEPROM::Status); + //RTC_STATUS + if(addr == 0x00ca) return rtcStatus(); + + //RTC_DATA + if(addr == 0x00cb) return rtcRead(); + return 0x00; } auto Cartridge::portWrite(uint16 addr, uint8 data) -> void { //BANK_ROM2 - if(addr == 0x00c0) { - r.bank_rom2 = data; - return; - } + if(addr == 0x00c0) r.romBank2 = data; //BANK_SRAM - if(addr == 0x00c1) { - r.bank_sram = data; - return; - } + if(addr == 0x00c1) r.sramBank = data; //BANK_ROM0 - if(addr == 0x00c2) { - r.bank_rom0 = data; - return; - } + if(addr == 0x00c2) r.romBank0 = data; //BANK_ROM1 - if(addr == 0x00c3) { - r.bank_rom1 = data; - return; - } + if(addr == 0x00c3) r.romBank1 = data; //EEP_DATA - if(addr == 0x00c4) return eeprom.write(EEPROM::DataLo, data); - if(addr == 0x00c5) return eeprom.write(EEPROM::DataHi, data); + if(addr == 0x00c4) eeprom.write(EEPROM::DataLo, data); + if(addr == 0x00c5) eeprom.write(EEPROM::DataHi, data); //EEP_ADDR - if(addr == 0x00c6) return eeprom.write(EEPROM::AddressLo, data); - if(addr == 0x00c7) return eeprom.write(EEPROM::AddressHi, data); + if(addr == 0x00c6) eeprom.write(EEPROM::AddressLo, data); + if(addr == 0x00c7) eeprom.write(EEPROM::AddressHi, data); //EEP_CMD - if(addr == 0x00c8) return eeprom.write(EEPROM::Command, data); + if(addr == 0x00c8) eeprom.write(EEPROM::Command, data); + + //RTC_CMD + if(addr == 0x00ca) rtcCommand(data); + + //RTC_DATA + if(addr == 0x00cb) rtcWrite(data); } diff --git a/higan/ws/cartridge/memory.cpp b/higan/ws/cartridge/memory.cpp index 98b1a857..76f700ab 100644 --- a/higan/ws/cartridge/memory.cpp +++ b/higan/ws/cartridge/memory.cpp @@ -3,9 +3,9 @@ auto Cartridge::romRead(uint20 addr) -> uint8 { if(!rom.data) return 0x00; uint28 offset; switch(addr.byte(2)) { - case 2: offset = r.bank_rom0 << 16 | addr.bits(0,15); break; //20000-2ffff - case 3: offset = r.bank_rom1 << 16 | addr.bits(0,15); break; //30000-3ffff - default: offset = r.bank_rom2 << 20 | addr.bits(0,19); break; //40000-fffff + case 2: offset = r.romBank0 << 16 | addr.bits(0,15); break; //20000-2ffff + case 3: offset = r.romBank1 << 16 | addr.bits(0,15); break; //30000-3ffff + default: offset = r.romBank2 << 20 | addr.bits(0,19); break; //40000-fffff } return rom.data[offset & rom.mask]; } @@ -16,12 +16,12 @@ auto Cartridge::romWrite(uint20 addr, uint8 data) -> void { //10000-1ffff auto Cartridge::ramRead(uint20 addr) -> uint8 { if(!ram.data) return 0x00; - uint24 offset = r.bank_sram << 16 | addr.bits(0,15); + uint24 offset = r.sramBank << 16 | addr.bits(0,15); return ram.data[offset & ram.mask]; } auto Cartridge::ramWrite(uint20 addr, uint8 data) -> void { if(!ram.data) return; - uint24 offset = r.bank_sram << 16 | addr.bits(0,15); + uint24 offset = r.sramBank << 16 | addr.bits(0,15); ram.data[offset & ram.mask] = data; } diff --git a/higan/ws/cartridge/rtc.cpp b/higan/ws/cartridge/rtc.cpp new file mode 100644 index 00000000..a47e6719 --- /dev/null +++ b/higan/ws/cartridge/rtc.cpp @@ -0,0 +1,149 @@ +//calculate time between last play of game and current time; +//increment RTC by said amount of seconds +auto Cartridge::rtcLoad() -> void { + uint64 timestamp = 0; + for(auto n : range(8)) timestamp.byte(n) = rtc.data[8 + n]; + if(!timestamp) return; //new save file + + timestamp = time(0) - timestamp; + while(timestamp--) rtcTickSecond(); +} + +//save time when game is unloaded +auto Cartridge::rtcSave() -> void { + uint64 timestamp = time(0); + for(auto n : range(8)) rtc.data[8 + n] = timestamp.byte(n); +} + +auto Cartridge::rtcTickSecond() -> void { + if(++rtc.second() < 60) return; + rtc.second() = 0; + + if(++rtc.minute() < 60) return; + rtc.minute() = 0; + + if(++rtc.hour() < 60) return; + rtc.hour() = 0; + + rtc.weekday() += 1; + rtc.weekday() %= 7; + + uint daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + if(rtc.year() && (rtc.year() % 100) && !(rtc.year() % 4)) daysInMonth[1]++; + + if(++rtc.day() < daysInMonth[rtc.month()]) return; + rtc.day() = 0; + + if(++rtc.month() < 12) return; + rtc.month() = 0; + + ++rtc.year(); +} + +auto Cartridge::rtcCheckAlarm() -> void { + if(!rtc.alarm.bit(5)) return; + + if(rtc.hour() == rtc.alarmHour && rtc.minute() == rtc.alarmMinute) { + cpu.raise(CPU::Interrupt::Cartridge); + } else { + cpu.lower(CPU::Interrupt::Cartridge); + } +} + +auto Cartridge::rtcStatus() -> uint8 { + return 0x80; +} + +auto Cartridge::rtcCommand(uint8 data) -> void { + rtc.command = data; + + //RESET + if(rtc.command == 0x10) { + rtc.year() = 0; + rtc.month() = 0; + rtc.day() = 0; + rtc.weekday() = 0; + rtc.hour() = 0; + rtc.minute() = 0; + rtc.second() = 0; + } + + //ALARM_FLAG + if(rtc.command == 0x12) { + rtc.index = 0; + } + + //SET_DATETIME + if(rtc.command == 0x14) { + rtc.index = 0; + } + + //GET_DATETIME + if(rtc.command == 0x15) { + rtc.index = 0; + } + + //SET_ALARM + if(rtc.command == 0x18) { + rtc.index = 0; + } +} + +auto Cartridge::rtcRead() -> uint8 { + uint8 data = 0; + + static auto encode = [](uint8 data) -> uint8 { + return ((data / 10) << 4) + (data % 10); + }; + + //GET_DATETIME + if(rtc.command == 0x15) { + switch(rtc.index) { + case 0: data = encode(rtc.year()); break; + case 1: data = encode(rtc.month() + 1); break; + case 2: data = encode(rtc.day() + 1); break; + case 3: data = encode(rtc.weekday()); break; + case 4: data = encode(rtc.hour()); break; + case 5: data = encode(rtc.minute()); break; + case 6: data = encode(rtc.second()); break; + } + if(++rtc.index >= 7) rtc.command = 0; + } + + return data; +} + +auto Cartridge::rtcWrite(uint8 data) -> void { + static auto decode = [](uint8 data) -> uint8 { + return (data >> 4) * 10 + (data & 0x0f); + }; + + //ALARM_FLAG + if(rtc.command == 0x12) { + if(data.bit(6)) rtc.alarm = data; //todo: is bit6 really required to be set? + rtc.command = 0; + } + + //SET_DATETIME + if(rtc.command == 0x14) { + switch(rtc.index) { + case 0: rtc.year() = decode(data); break; + case 1: rtc.month() = decode(data) - 1; break; + case 2: rtc.day() = decode(data) - 1; break; + case 3: rtc.weekday() = decode(data); break; + case 4: rtc.hour() = decode(data); break; + case 5: rtc.minute() = decode(data); break; + case 6: rtc.second() = decode(data); break; + } + if(++rtc.index >= 7) rtc.command = 0; + } + + //SET_ALRM + if(rtc.command == 0x18) { + switch(rtc.index) { + case 0: rtc.alarmHour = decode(data.bits(0,6)); break; + case 1: rtc.alarmMinute = decode(data); break; + } + if(++rtc.index >= 2) rtc.command = 0; + } +} diff --git a/higan/ws/cartridge/serialization.cpp b/higan/ws/cartridge/serialization.cpp new file mode 100644 index 00000000..6e9713d2 --- /dev/null +++ b/higan/ws/cartridge/serialization.cpp @@ -0,0 +1,18 @@ +auto Cartridge::serialize(serializer& s) -> void { + if(ram.size) s.array(ram.data, ram.size); + if(eeprom.size()) eeprom.serialize(s); + if(rtc.size) s.array(rtc.data, rtc.size); + + if(rtc.size) { + s.integer(rtc.command); + s.integer(rtc.index); + s.integer(rtc.alarm); + s.integer(rtc.alarmHour); + s.integer(rtc.alarmMinute); + } + + s.integer(r.romBank0); + s.integer(r.romBank1); + s.integer(r.romBank2); + s.integer(r.sramBank); +} diff --git a/higan/ws/cheat/cheat.cpp b/higan/ws/cheat/cheat.cpp new file mode 100644 index 00000000..bb80f0e5 --- /dev/null +++ b/higan/ws/cheat/cheat.cpp @@ -0,0 +1,28 @@ +#include + +namespace WonderSwan { + +Cheat cheat; + +auto Cheat::reset() -> void { + codes.reset(); +} + +auto Cheat::append(uint addr, uint data) -> void { + codes.append({addr, Unused, data}); +} + +auto Cheat::append(uint addr, uint comp, uint data) -> void { + codes.append({addr, comp, data}); +} + +auto Cheat::find(uint addr, uint comp) -> maybe { + for(auto& code : codes) { + if(code.addr == addr && (code.comp == Unused || code.comp == comp)) { + return code.data; + } + } + return nothing; +} + +} diff --git a/higan/ws/cheat/cheat.hpp b/higan/ws/cheat/cheat.hpp new file mode 100644 index 00000000..ee8c1004 --- /dev/null +++ b/higan/ws/cheat/cheat.hpp @@ -0,0 +1,18 @@ +struct Cheat { + struct Code { + uint addr; + uint comp; + uint data; + }; + vector codes; + enum : uint { Unused = ~0u }; + + alwaysinline auto enable() const -> bool { return codes.size() > 0; } + + auto reset() -> void; + auto append(uint addr, uint data) -> void; + auto append(uint addr, uint comp, uint data) -> void; + auto find(uint addr, uint comp) -> maybe; +}; + +extern Cheat cheat; diff --git a/higan/ws/cpu/cpu.cpp b/higan/ws/cpu/cpu.cpp index 7b90308a..29c7ff3f 100644 --- a/higan/ws/cpu/cpu.cpp +++ b/higan/ws/cpu/cpu.cpp @@ -6,6 +6,7 @@ CPU cpu; #include "io.cpp" #include "interrupt.cpp" #include "dma.cpp" +#include "serialization.cpp" auto CPU::Enter() -> void { while(true) scheduler.synchronize(), cpu.main(); @@ -22,6 +23,9 @@ auto CPU::step(uint clocks) -> void { apu.clock -= clocks; if(apu.clock < 0) co_switch(apu.thread); + + cartridge.clock -= clocks; + if(cartridge.clock < 0) co_switch(cartridge.thread); } auto CPU::wait(uint clocks) -> void { @@ -51,9 +55,7 @@ auto CPU::power() -> void { bus.map(this, 0x00a0); bus.map(this, 0x00b0); bus.map(this, 0x00b2); - bus.map(this, 0x00b4); - bus.map(this, 0x00b5); - bus.map(this, 0x00b6); + bus.map(this, 0x00b4, 0x00b6); if(system.model() != Model::WonderSwan) { bus.map(this, 0x0040, 0x0049); diff --git a/higan/ws/cpu/cpu.hpp b/higan/ws/cpu/cpu.hpp index 92f13f35..c02c2c0c 100644 --- a/higan/ws/cpu/cpu.hpp +++ b/higan/ws/cpu/cpu.hpp @@ -35,6 +35,9 @@ struct CPU : Processor::V30MZ, Thread, IO { //dma.cpp auto dmaTransfer() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + struct Registers { //$0040-0042 DMA_SRC uint20 dmaSource; diff --git a/higan/ws/cpu/serialization.cpp b/higan/ws/cpu/serialization.cpp new file mode 100644 index 00000000..51ce05bb --- /dev/null +++ b/higan/ws/cpu/serialization.cpp @@ -0,0 +1,15 @@ +auto CPU::serialize(serializer& s) -> void { + V30MZ::serialize(s); + + s.integer(r.dmaSource); + s.integer(r.dmaTarget); + s.integer(r.dmaLength); + s.integer(r.dmaEnable); + s.integer(r.dmaMode); + s.integer(r.interruptBase); + s.integer(r.interruptEnable); + s.integer(r.interruptStatus); + s.integer(r.ypadEnable); + s.integer(r.xpadEnable); + s.integer(r.buttonEnable); +} diff --git a/higan/ws/eeprom/eeprom.cpp b/higan/ws/eeprom/eeprom.cpp index 35932ab1..28c15ed3 100644 --- a/higan/ws/eeprom/eeprom.cpp +++ b/higan/ws/eeprom/eeprom.cpp @@ -2,6 +2,8 @@ namespace WonderSwan { +#include "serialization.cpp" + auto EEPROM::name() const -> string { return _name; } auto EEPROM::data() -> uint16* { return _data; } auto EEPROM::size() const -> uint { return _size; } diff --git a/higan/ws/eeprom/eeprom.hpp b/higan/ws/eeprom/eeprom.hpp index d9c165ce..e65c2ccc 100644 --- a/higan/ws/eeprom/eeprom.hpp +++ b/higan/ws/eeprom/eeprom.hpp @@ -26,6 +26,9 @@ struct EEPROM { auto read(uint) -> uint8; auto write(uint, uint8) -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + private: auto execute() -> void; diff --git a/higan/ws/eeprom/serialization.cpp b/higan/ws/eeprom/serialization.cpp new file mode 100644 index 00000000..a5160ed6 --- /dev/null +++ b/higan/ws/eeprom/serialization.cpp @@ -0,0 +1,12 @@ +auto EEPROM::serialize(serializer& s) -> void { + s.array(_data); + + s.integer(r.latch); + s.integer(r.address); + s.integer(r.unknown); + s.integer(r.writeRequested); + s.integer(r.readRequested); + s.integer(r.writeCompleted); + s.integer(r.readCompleted); + s.integer(r.writeProtect); +} diff --git a/higan/ws/interface/interface.cpp b/higan/ws/interface/interface.cpp index 1e7cfc30..c0078bdd 100644 --- a/higan/ws/interface/interface.cpp +++ b/higan/ws/interface/interface.cpp @@ -16,8 +16,8 @@ Interface::Interface() { information.aspectRatio = 1.0; information.resettable = false; - information.capability.states = false; - information.capability.cheats = false; + information.capability.states = true; + information.capability.cheats = true; media.append({ID::WonderSwan, "WonderSwan", "ws", true}); media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc", true}); @@ -94,6 +94,7 @@ auto Interface::group(uint id) -> uint { case ID::ROM: case ID::RAM: case ID::EEPROM: + case ID::RTC: switch(system.model()) { case Model::WonderSwan: return ID::WonderSwan; @@ -114,6 +115,7 @@ auto Interface::save() -> void { if(auto name = system.eeprom.name()) interface->saveRequest(ID::SystemEEPROM, name); if(auto name = cartridge.ram.name) interface->saveRequest(ID::RAM, name); if(auto name = cartridge.eeprom.name()) interface->saveRequest(ID::EEPROM, name); + if(auto name = cartridge.rtc.name) interface->saveRequest(ID::RTC, name); } auto Interface::load(uint id, const stream& stream) -> void { @@ -140,6 +142,11 @@ auto Interface::load(uint id, const stream& stream) -> void { if(id == ID::EEPROM) { stream.read((uint8_t*)cartridge.eeprom.data(), min(cartridge.eeprom.size() * sizeof(uint16), stream.size())); } + + if(id == ID::RTC) { + stream.read((uint8_t*)cartridge.rtc.data, min(cartridge.rtc.size, stream.size())); + cartridge.rtcLoad(); + } } auto Interface::save(uint id, const stream& stream) -> void { @@ -154,6 +161,11 @@ auto Interface::save(uint id, const stream& stream) -> void { if(id == ID::EEPROM) { stream.write((uint8_t*)cartridge.eeprom.data(), cartridge.eeprom.size() * sizeof(uint16)); } + + if(id == ID::RTC) { + cartridge.rtcSave(); + stream.write((uint8_t*)cartridge.rtc.data, cartridge.rtc.size); + } } auto Interface::unload() -> void { @@ -169,11 +181,24 @@ auto Interface::run() -> void { } auto Interface::serialize() -> serializer { - return {}; + system.runToSave(); + return system.serialize(); } auto Interface::unserialize(serializer& s) -> bool { - return false; + return system.unserialize(s); +} + +auto Interface::cheatSet(const lstring& list) -> void { + cheat.reset(); + for(auto& codeset : list) { + lstring codes = codeset.split("+"); + for(auto& code : codes) { + lstring part = code.split("/"); + if(part.size() == 2) cheat.append(hex(part[0]), hex(part[1])); + if(part.size() == 3) cheat.append(hex(part[0]), hex(part[1]), hex(part[2])); + } + } } auto Interface::cap(const string& name) -> bool { diff --git a/higan/ws/interface/interface.hpp b/higan/ws/interface/interface.hpp index 61edea33..2901c2c3 100644 --- a/higan/ws/interface/interface.hpp +++ b/higan/ws/interface/interface.hpp @@ -16,6 +16,7 @@ struct ID { ROM, RAM, EEPROM, + RTC, }; enum : uint { @@ -47,6 +48,8 @@ struct Interface : Emulator::Interface { auto serialize() -> serializer override; auto unserialize(serializer&) -> bool override; + auto cheatSet(const lstring&) -> void; + auto cap(const string& name) -> bool override; auto get(const string& name) -> any override; auto set(const string& name, const any& value) -> bool override; diff --git a/higan/ws/memory/memory.cpp b/higan/ws/memory/memory.cpp index 1fed6c9b..c6379c2b 100644 --- a/higan/ws/memory/memory.cpp +++ b/higan/ws/memory/memory.cpp @@ -9,6 +9,10 @@ auto InternalRAM::power() -> void { for(auto& byte : memory) byte = 0x00; } +auto InternalRAM::serialize(serializer& s) -> void { + s.array(memory, system.model() == Model::WonderSwan ? 0x4000 : 0x10000); +} + auto InternalRAM::read(uint16 addr, uint size) -> uint32 { if(size == Long) return read(addr + 0, Word) << 0 | read(addr + 2, Word) << 16; if(size == Word) return read(addr + 0, Byte) << 0 | read(addr + 1, Byte) << 8; @@ -27,16 +31,20 @@ auto Bus::power() -> void { } auto Bus::read(uint20 addr) -> uint8 { - if(addr.bits(16,19) == 0) return iram.read(addr); - if(addr.bits(16,19) == 1) return cartridge.ramRead(addr); - if(addr.bits(16,19) >= 2) return cartridge.romRead(addr); - unreachable; + uint8 data = 0; + if(addr.bits(16,19) == 0) data = iram.read(addr); + if(addr.bits(16,19) == 1) data = cartridge.ramRead(addr); + if(addr.bits(16,19) >= 2) data = cartridge.romRead(addr); + if(cheat.enable()) { + if(auto result = cheat.find(addr, data)) data = result(); + } + return data; } auto Bus::write(uint20 addr, uint8 data) -> void { - if(addr.bits(16,19) == 0) return iram.write(addr, data); - if(addr.bits(16,19) == 1) return cartridge.ramWrite(addr, data); - if(addr.bits(16,19) >= 2) return cartridge.romWrite(addr, data); + if(addr.bits(16,19) == 0) iram.write(addr, data); + if(addr.bits(16,19) == 1) cartridge.ramWrite(addr, data); + if(addr.bits(16,19) >= 2) cartridge.romWrite(addr, data); } auto Bus::map(IO* io, uint16_t lo, maybe hi) -> void { diff --git a/higan/ws/memory/memory.hpp b/higan/ws/memory/memory.hpp index c6997679..d9cc292c 100644 --- a/higan/ws/memory/memory.hpp +++ b/higan/ws/memory/memory.hpp @@ -5,6 +5,7 @@ struct IO { struct InternalRAM { auto power() -> void; + auto serialize(serializer&) -> void; auto read(uint16 addr, uint size = Byte) -> uint32; auto write(uint16 addr, uint8 data) -> void; diff --git a/higan/ws/ppu/latch.cpp b/higan/ws/ppu/latch.cpp new file mode 100644 index 00000000..8e07e43d --- /dev/null +++ b/higan/ws/ppu/latch.cpp @@ -0,0 +1,63 @@ +auto PPU::latchRegisters() -> void { + l.backColor = r.backColor; + + l.screenOneEnable = r.screenOneEnable; + l.screenOneMapBase = r.screenOneMapBase; + l.scrollOneX = r.scrollOneX; + l.scrollOneY = r.scrollOneY; + + l.screenTwoEnable = r.screenTwoEnable; + l.screenTwoMapBase = r.screenTwoMapBase; + l.scrollTwoX = r.scrollTwoX; + l.scrollTwoY = r.scrollTwoY; + l.screenTwoWindowEnable = r.screenTwoWindowEnable; + l.screenTwoWindowInvert = r.screenTwoWindowInvert; + l.screenTwoWindowX0 = r.screenTwoWindowX0; + l.screenTwoWindowY0 = r.screenTwoWindowY0; + l.screenTwoWindowX1 = r.screenTwoWindowX1; + l.screenTwoWindowY1 = r.screenTwoWindowY1; + + l.spriteEnable = r.spriteEnable; + l.spriteWindowEnable = r.spriteWindowEnable; + l.spriteWindowX0 = r.spriteWindowX0; + l.spriteWindowY0 = r.spriteWindowY0; + l.spriteWindowX1 = r.spriteWindowX1; + l.spriteWindowY1 = r.spriteWindowY1; +} + +auto PPU::latchSprites() -> void { + l.spriteCount = 0; + if(!l.spriteEnable) return; + + uint offset = 0; + bool windowInside = s.vclk >= l.spriteWindowY0 && s.vclk <= l.spriteWindowY1; + for(auto index : range(l.oamCount)) { + uint32 attributes = l.oam[!s.field][index]; + + auto& sprite = l.sprite[l.spriteCount]; + sprite.x = attributes.bits(24,31); + if(sprite.x > 224 && sprite.x < 249) continue; + sprite.y = attributes.bits(16,23); + if((uint8)(s.vclk - sprite.y) > 7) continue; + sprite.vflip = attributes.bit(15); + sprite.hflip = attributes.bit(14); + sprite.priority = attributes.bit(13); + sprite.window = attributes.bit(12); + if(l.spriteWindowEnable && sprite.window == windowInside) continue; + sprite.palette = 8 + attributes.bits(9,11); + sprite.tile = attributes.bits(0,8); + + if(++l.spriteCount >= 32) break; + } +} + +//note: this implicitly latches spriteBase, spriteFirst, spriteCount +auto PPU::latchOAM() -> void { + uint7 spriteIndex = r.spriteFirst; + uint8 spriteCount = min(128, (uint)r.spriteCount); + uint16 spriteBase = r.spriteBase.bits(0, 4 + system.depth()) << 9; + l.oamCount = spriteCount; + for(auto index : range(spriteCount)) { + l.oam[s.field][index] = iram.read(spriteBase + (spriteIndex++ << 2), Long); + } +} diff --git a/higan/ws/ppu/ppu.cpp b/higan/ws/ppu/ppu.cpp index 3a01bb5d..336e7425 100644 --- a/higan/ws/ppu/ppu.cpp +++ b/higan/ws/ppu/ppu.cpp @@ -4,23 +4,27 @@ namespace WonderSwan { PPU ppu; #include "io.cpp" -#include "render-sprite.cpp" +#include "latch.cpp" #include "render-mono.cpp" #include "render-color.cpp" #include "video.cpp" +#include "serialization.cpp" auto PPU::Enter() -> void { while(true) scheduler.synchronize(), ppu.main(); } auto PPU::main() -> void { + if(s.vclk == 142) { + latchOAM(); + } + if(s.vclk < 144) { latchRegisters(); - renderSpriteFetch(); - renderSpriteDecode(); + latchSprites(); for(auto x : range(224)) { if(!r.lcdEnable) { - pixel = {Pixel::Source::Back, 0x000}; + s.pixel = {Pixel::Source::Back, 0x000}; } else if(!system.color()) { renderMonoBack(); renderMonoScreenOne(); @@ -32,7 +36,7 @@ auto PPU::main() -> void { renderColorScreenTwo(); renderColorSprite(); } - output[s.vclk * 224 + s.hclk] = pixel.color; + output[s.vclk * 224 + s.hclk] = s.pixel.color; step(1); } step(32); @@ -54,7 +58,7 @@ auto PPU::main() -> void { auto PPU::scanline() -> void { s.hclk = 0; - s.vclk++; + if(++s.vclk == 159) frame(); if(s.vclk == r.lineCompare) { cpu.raise(CPU::Interrupt::LineCompare); } @@ -71,7 +75,6 @@ auto PPU::scanline() -> void { } } } - if(s.vclk == 159) frame(); } auto PPU::frame() -> void { @@ -88,36 +91,6 @@ auto PPU::step(uint clocks) -> void { if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread); } -auto PPU::latchRegisters() -> void { - l.backColor = r.backColor; - - l.screenOneEnable = r.screenOneEnable; - l.screenOneMapBase = r.screenOneMapBase; - l.scrollOneX = r.scrollOneX; - l.scrollOneY = r.scrollOneY; - - l.screenTwoEnable = r.screenTwoEnable; - l.screenTwoMapBase = r.screenTwoMapBase; - l.scrollTwoX = r.scrollTwoX; - l.scrollTwoY = r.scrollTwoY; - l.screenTwoWindowEnable = r.screenTwoWindowEnable; - l.screenTwoWindowInvert = r.screenTwoWindowInvert; - l.screenTwoWindowX0 = r.screenTwoWindowX0; - l.screenTwoWindowY0 = r.screenTwoWindowY0; - l.screenTwoWindowX1 = r.screenTwoWindowX1; - l.screenTwoWindowY1 = r.screenTwoWindowY1; - - l.spriteEnable = r.spriteEnable; - l.spriteBase = r.spriteBase; - l.spriteFirst = r.spriteFirst; - l.spriteCount = r.spriteCount; - l.spriteWindowEnable = r.spriteWindowEnable; - l.spriteWindowX0 = r.spriteWindowX0; - l.spriteWindowY0 = r.spriteWindowY0; - l.spriteWindowX1 = r.spriteWindowX1; - l.spriteWindowY1 = r.spriteWindowY1; -} - auto PPU::power() -> void { create(PPU::Enter, 3'072'000); @@ -127,58 +100,13 @@ auto PPU::power() -> void { bus.map(this, 0x00a4, 0x00ab); for(auto& n : output) n = 0; - for(auto& n : oam[0]) n = 0; - for(auto& n : oam[1]) n = 0; + memory::fill(&s, sizeof(State)); + memory::fill(&l, sizeof(Latches)); + memory::fill(&r, sizeof(Registers)); - s.vclk = 0; - s.hclk = 0; - - r.screenOneEnable = 0; - r.screenTwoEnable = 0; - r.spriteEnable = 0; - r.spriteWindowEnable = 0; - r.screenTwoWindowInvert = 0; - r.screenTwoWindowEnable = 0; - r.backColor = 0; - r.lineCompare = 0xff; - r.spriteBase = 0; - r.spriteFirst = 0; - r.spriteCount = 0; - r.screenOneMapBase = 0; - r.screenTwoMapBase = 0; - r.screenTwoWindowX0 = 0; - r.screenTwoWindowY0 = 0; - r.screenTwoWindowX1 = 0; - r.screenTwoWindowY1 = 0; - r.spriteWindowX0 = 0; - r.spriteWindowY0 = 0; - r.spriteWindowX1 = 0; - r.spriteWindowY1 = 0; - r.scrollOneX = 0; - r.scrollOneY = 0; - r.scrollTwoX = 0; - r.scrollTwoY = 0; r.lcdEnable = 1; - r.lcdContrast = 0; - r.lcdUnknown = 0; - r.iconSleep = 0; - r.iconVertical = 0; - r.iconHorizontal = 0; - r.iconAux1 = 0; - r.iconAux2 = 0; - r.iconAux3 = 0; r.vtotal = 158; r.vblank = 155; - for(auto& color : r.pool) color = 0; - for(auto& p : r.palette) for(auto& color : p.color) color = 0; - r.htimerEnable = 0; - r.htimerRepeat = 0; - r.vtimerEnable = 0; - r.vtimerRepeat = 0; - r.htimerFrequency = 0; - r.vtimerFrequency = 0; - r.htimerCounter = 0; - r.vtimerCounter = 0; video.power(); } diff --git a/higan/ws/ppu/ppu.hpp b/higan/ws/ppu/ppu.hpp index 89a8816c..e6c33128 100644 --- a/higan/ws/ppu/ppu.hpp +++ b/higan/ws/ppu/ppu.hpp @@ -6,16 +6,16 @@ struct PPU : Thread, IO { auto scanline() -> void; auto frame() -> void; auto step(uint clocks) -> void; - auto latchRegisters() -> void; auto power() -> void; //io.cpp auto portRead(uint16 addr) -> uint8 override; auto portWrite(uint16 addr, uint8 data) -> void override; - //render-sprite.cpp - auto renderSpriteFetch() -> void; - auto renderSpriteDecode() -> void; + //latch.cpp + auto latchRegisters() -> void; + auto latchSprites() -> void; + auto latchOAM() -> void; //render-mono.cpp auto renderMonoFetch(uint14 offset, uint3 y, uint3 x) -> uint2; @@ -33,9 +33,15 @@ struct PPU : Thread, IO { auto renderColorScreenTwo() -> void; auto renderColorSprite() -> void; + //serialization.cpp + auto serialize(serializer&) -> void; + //state - uint12 output[224 * 144]; - uint32 oam[2][128]; + struct Pixel { + enum class Source : uint { Back, ScreenOne, ScreenTwo, Sprite }; + Source source; + uint12 color; + }; struct Sprite { uint8 x; @@ -44,24 +50,21 @@ struct PPU : Thread, IO { uint1 hflip; uint1 priority; uint1 window; - uint4 palette; //renderSpriteDecode() always sets bit3 + uint4 palette; //latchSprites() always sets bit3 uint9 tile; }; - vector sprites; - struct Pixel { - enum class Source : uint { Back, ScreenOne, ScreenTwo, Sprite }; - Source source; - uint12 color; - } pixel; + uint12 output[224 * 144]; struct State { bool field; uint vclk; uint hclk; + Pixel pixel; } s; struct Latches { + //latchRegisters() uint8 backColor; uint1 screenOneEnable; @@ -81,14 +84,19 @@ struct PPU : Thread, IO { uint8 screenTwoWindowY1; uint1 spriteEnable; - uint6 spriteBase; - uint7 spriteFirst; - uint8 spriteCount; uint1 spriteWindowEnable; uint8 spriteWindowX0; uint8 spriteWindowY0; uint8 spriteWindowX1; uint8 spriteWindowY1; + + //latchSprites() + Sprite sprite[32]; + uint spriteCount; + + //latchOAM() + uint32 oam[2][128]; + uint oamCount; } l; struct Registers { diff --git a/higan/ws/ppu/render-color.cpp b/higan/ws/ppu/render-color.cpp index 01a244a7..38585117 100644 --- a/higan/ws/ppu/render-color.cpp +++ b/higan/ws/ppu/render-color.cpp @@ -23,7 +23,7 @@ auto PPU::renderColorPalette(uint4 palette, uint4 index) -> uint12 { auto PPU::renderColorBack() -> void { uint12 color = iram.read(0xfe00 + (l.backColor << 1), Word); - pixel = {Pixel::Source::Back, color}; + s.pixel = {Pixel::Source::Back, color}; } auto PPU::renderColorScreenOne() -> void { @@ -43,7 +43,7 @@ auto PPU::renderColorScreenOne() -> void { uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); if(tileColor == 0) return; - pixel = {Pixel::Source::ScreenOne, renderColorPalette(tile.bits(9, 12), tileColor)}; + s.pixel = {Pixel::Source::ScreenOne, renderColorPalette(tile.bits(9, 12), tileColor)}; } auto PPU::renderColorScreenTwo() -> void { @@ -68,14 +68,15 @@ auto PPU::renderColorScreenTwo() -> void { uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); if(tileColor == 0) return; - pixel = {Pixel::Source::ScreenTwo, renderColorPalette(tile.bits(9, 12), tileColor)}; + s.pixel = {Pixel::Source::ScreenTwo, renderColorPalette(tile.bits(9, 12), tileColor)}; } auto PPU::renderColorSprite() -> void { if(!l.spriteEnable) return; bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1; - for(auto& sprite : sprites) { + for(auto index : range(l.spriteCount)) { + auto& sprite = l.sprite[index]; if(l.spriteWindowEnable && sprite.window == windowInside) continue; if((uint8)(s.hclk - sprite.x) > 7) continue; @@ -84,9 +85,9 @@ auto PPU::renderColorSprite() -> void { uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0); uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); if(tileColor == 0) continue; - if(!sprite.priority && pixel.source == Pixel::Source::ScreenTwo) continue; + if(!sprite.priority && s.pixel.source == Pixel::Source::ScreenTwo) continue; - pixel = {Pixel::Source::Sprite, renderColorPalette(sprite.palette, tileColor)}; + s.pixel = {Pixel::Source::Sprite, renderColorPalette(sprite.palette, tileColor)}; break; } } diff --git a/higan/ws/ppu/render-mono.cpp b/higan/ws/ppu/render-mono.cpp index 303a5da8..de63b0f6 100644 --- a/higan/ws/ppu/render-mono.cpp +++ b/higan/ws/ppu/render-mono.cpp @@ -23,7 +23,7 @@ auto PPU::renderMonoPalette(uint4 palette, uint2 index) -> uint12 { auto PPU::renderMonoBack() -> void { uint4 poolColor = 15 - r.pool[l.backColor.bits(0,2)]; - pixel = {Pixel::Source::Back, poolColor << 0 | poolColor << 4 | poolColor << 8}; + s.pixel = {Pixel::Source::Back, poolColor << 0 | poolColor << 4 | poolColor << 8}; } auto PPU::renderMonoScreenOne() -> void { @@ -43,7 +43,7 @@ auto PPU::renderMonoScreenOne() -> void { uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); if(tile.bit(11) && tileColor == 0) return; - pixel = {Pixel::Source::ScreenOne, renderMonoPalette(tile.bits(9,12), tileColor)}; + s.pixel = {Pixel::Source::ScreenOne, renderMonoPalette(tile.bits(9,12), tileColor)}; } auto PPU::renderMonoScreenTwo() -> void { @@ -68,14 +68,15 @@ auto PPU::renderMonoScreenTwo() -> void { uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); if(tile.bit(11) && tileColor == 0) return; - pixel = {Pixel::Source::ScreenTwo, renderMonoPalette(tile.bits(9,12), tileColor)}; + s.pixel = {Pixel::Source::ScreenTwo, renderMonoPalette(tile.bits(9,12), tileColor)}; } auto PPU::renderMonoSprite() -> void { if(!l.spriteEnable) return; bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1; - for(auto& sprite : sprites) { + for(auto index : range(l.spriteCount)) { + auto& sprite = l.sprite[index]; if(l.spriteWindowEnable && sprite.window == windowInside) continue; if((uint8)(s.hclk - sprite.x) > 7) continue; @@ -84,9 +85,9 @@ auto PPU::renderMonoSprite() -> void { uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0); uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); if(sprite.palette.bit(2) && tileColor == 0) continue; - if(!sprite.priority && pixel.source == Pixel::Source::ScreenTwo) continue; + if(!sprite.priority && s.pixel.source == Pixel::Source::ScreenTwo) continue; - pixel = {Pixel::Source::Sprite, renderMonoPalette(sprite.palette, tileColor)}; + s.pixel = {Pixel::Source::Sprite, renderMonoPalette(sprite.palette, tileColor)}; break; } } diff --git a/higan/ws/ppu/render-sprite.cpp b/higan/ws/ppu/render-sprite.cpp deleted file mode 100644 index 43b7eac5..00000000 --- a/higan/ws/ppu/render-sprite.cpp +++ /dev/null @@ -1,36 +0,0 @@ -auto PPU::renderSpriteFetch() -> void { - uint16 spriteBase = l.spriteBase.bits(0, 4 + system.depth()) << 9; - for(auto spriteIndex : range(128)) { - oam[s.field][spriteIndex] = iram.read(spriteBase + (spriteIndex << 2), Long); - } -} - -auto PPU::renderSpriteDecode() -> void { - sprites.reset(); - sprites.reserve(32); - if(!l.spriteEnable) return; - - uint offset = 0; - bool windowInside = s.vclk >= l.spriteWindowY0 && s.vclk <= l.spriteWindowY1; - uint7 spriteIndex = l.spriteFirst; - uint8 spriteCount = min(128, (uint)l.spriteCount); - while(spriteCount--) { - uint32 attributes = oam[s.field][spriteIndex++]; - - Sprite sprite; - sprite.x = attributes.bits(24,31); - if(sprite.x > 224 && sprite.x < 249) continue; - sprite.y = attributes.bits(16,23); - if((uint8)(s.vclk - sprite.y) > 7) continue; - sprite.vflip = attributes.bit(15); - sprite.hflip = attributes.bit(14); - sprite.priority = attributes.bit(13); - sprite.window = attributes.bit(12); - if(l.spriteWindowEnable && sprite.window == windowInside) continue; - sprite.palette = 8 + attributes.bits(9,11); - sprite.tile = attributes.bits(0,8); - - sprites.append(sprite); - if(sprites.size() >= 32) break; - } -} diff --git a/higan/ws/ppu/serialization.cpp b/higan/ws/ppu/serialization.cpp new file mode 100644 index 00000000..b3f96f55 --- /dev/null +++ b/higan/ws/ppu/serialization.cpp @@ -0,0 +1,95 @@ +auto PPU::serialize(serializer& s) -> void { + s.integer(this->s.field); + s.integer(this->s.vclk); + s.integer(this->s.hclk); + s.integer((uint&)this->s.pixel.source); + s.integer(this->s.pixel.color); + + s.integer(l.backColor); + s.integer(l.screenOneEnable); + s.integer(l.screenOneMapBase); + s.integer(l.scrollOneX); + s.integer(l.scrollOneY); + s.integer(l.screenTwoEnable); + s.integer(l.screenTwoMapBase); + s.integer(l.scrollTwoX); + s.integer(l.scrollTwoY); + s.integer(l.screenTwoWindowEnable); + s.integer(l.screenTwoWindowInvert); + s.integer(l.screenTwoWindowX0); + s.integer(l.screenTwoWindowY0); + s.integer(l.screenTwoWindowX1); + s.integer(l.screenTwoWindowY1); + s.integer(l.spriteEnable); + s.integer(l.spriteWindowEnable); + s.integer(l.spriteWindowX0); + s.integer(l.spriteWindowY0); + s.integer(l.spriteWindowX1); + s.integer(l.spriteWindowY1); + + for(uint n : range(32)) { + s.integer(l.sprite[n].x); + s.integer(l.sprite[n].y); + s.integer(l.sprite[n].vflip); + s.integer(l.sprite[n].hflip); + s.integer(l.sprite[n].priority); + s.integer(l.sprite[n].window); + s.integer(l.sprite[n].palette); + s.integer(l.sprite[n].tile); + } + s.integer(l.spriteCount); + + for(uint n : range(2)) { + s.array(l.oam[n]); + } + s.integer(l.oamCount); + + s.integer(r.screenOneEnable); + s.integer(r.screenTwoEnable); + s.integer(r.spriteEnable); + s.integer(r.spriteWindowEnable); + s.integer(r.screenTwoWindowInvert); + s.integer(r.screenTwoWindowEnable); + s.integer(r.backColor); + s.integer(r.lineCompare); + s.integer(r.spriteBase); + s.integer(r.spriteFirst); + s.integer(r.spriteCount); + s.integer(r.screenOneMapBase); + s.integer(r.screenTwoMapBase); + s.integer(r.screenTwoWindowX0); + s.integer(r.screenTwoWindowY0); + s.integer(r.screenTwoWindowX1); + s.integer(r.screenTwoWindowY1); + s.integer(r.spriteWindowX0); + s.integer(r.spriteWindowY0); + s.integer(r.spriteWindowX1); + s.integer(r.spriteWindowY1); + s.integer(r.scrollOneX); + s.integer(r.scrollOneY); + s.integer(r.scrollTwoX); + s.integer(r.scrollTwoY); + s.integer(r.lcdEnable); + s.integer(r.lcdContrast); + s.integer(r.lcdUnknown); + s.integer(r.iconSleep); + s.integer(r.iconVertical); + s.integer(r.iconHorizontal); + s.integer(r.iconAux1); + s.integer(r.iconAux2); + s.integer(r.iconAux3); + s.integer(r.vtotal); + s.integer(r.vblank); + s.array(r.pool); + for(uint n : range(16)) { + s.array(r.palette[n].color); + } + s.integer(r.htimerEnable); + s.integer(r.htimerRepeat); + s.integer(r.vtimerEnable); + s.integer(r.vtimerRepeat); + s.integer(r.htimerFrequency); + s.integer(r.vtimerFrequency); + s.integer(r.htimerCounter); + s.integer(r.vtimerCounter); +} diff --git a/higan/ws/system/serialization.cpp b/higan/ws/system/serialization.cpp new file mode 100644 index 00000000..16809479 --- /dev/null +++ b/higan/ws/system/serialization.cpp @@ -0,0 +1,66 @@ +auto System::serializeInit() -> void { + serializer s; + + uint signature = 0, version = 0; + char hash[64], description[512]; + + s.integer(signature); + s.integer(version); + s.array(hash); + s.array(description); + + serializeAll(s); + _serializeSize = s.size(); +} + +auto System::serialize() -> serializer { + serializer s(_serializeSize); + + uint signature = 0x31545342, version = Info::SerializerVersion; + char hash[64], description[512]; + memory::copy(&hash, (const char*)cartridge.information.sha256, 64); + memory::fill(&description, 512); + + s.integer(signature); + s.integer(version); + s.array(hash); + s.array(description); + + serializeAll(s); + return s; +} + +auto System::unserialize(serializer& s) -> bool { + uint signature, version; + char hash[64], description[512]; + + s.integer(signature); + s.integer(version); + s.array(hash); + s.array(description); + + if(signature != 0x31545342) return false; + if(version != Info::SerializerVersion) return false; + + power(); + serializeAll(s); + return true; +} + +auto System::serializeAll(serializer& s) -> void { + system.serialize(s); + cpu.serialize(s); + ppu.serialize(s); + apu.serialize(s); + cartridge.serialize(s); + iram.serialize(s); +} + +auto System::serialize(serializer& s) -> void { + eeprom.serialize(s); + + s.integer(r.depth); + s.integer(r.color); + s.integer(r.format); + s.integer(r.unknown); +} diff --git a/higan/ws/system/system.cpp b/higan/ws/system/system.cpp index e8248ace..861f2100 100644 --- a/higan/ws/system/system.cpp +++ b/higan/ws/system/system.cpp @@ -4,6 +4,7 @@ namespace WonderSwan { System system; #include "io.cpp" +#include "serialization.cpp" auto System::loaded() const -> bool { return _loaded; } auto System::model() const -> Model { return _model; } @@ -14,6 +15,7 @@ auto System::packed() const -> bool { return r.format == 1; } auto System::depth() const -> bool { return r.depth == 1; } auto System::init() -> void { + assert(interface != nullptr); } auto System::term() -> void { @@ -37,6 +39,7 @@ auto System::load(Model model) -> void { cartridge.load(); _loaded = true; _orientation = cartridge.information.orientation; + serializeInit(); } auto System::unload() -> void { @@ -69,8 +72,18 @@ auto System::power() -> void { } auto System::run() -> void { - while(scheduler.enter() != Scheduler::Event::Frame); + scheduler.enter(); + pollKeypad(); +} +auto System::runToSave() -> void { + scheduler.synchronize(cpu.thread); + scheduler.synchronize(ppu.thread); + scheduler.synchronize(apu.thread); + scheduler.synchronize(cartridge.thread); +} + +auto System::pollKeypad() -> void { bool rotate = keypad.rotate; keypad.y1 = interface->inputPoll(_orientation, 0, 0); keypad.y2 = interface->inputPoll(_orientation, 0, 1); diff --git a/higan/ws/system/system.hpp b/higan/ws/system/system.hpp index 8c94e3a6..cf130ee2 100644 --- a/higan/ws/system/system.hpp +++ b/higan/ws/system/system.hpp @@ -13,10 +13,20 @@ struct System : IO { auto unload() -> void; auto power() -> void; auto run() -> void; + auto runToSave() -> void; + auto pollKeypad() -> void; + //io.cpp auto portRead(uint16 addr) -> uint8 override; auto portWrite(uint16 addr, uint8 data) -> void override; + //serialization.cpp + auto serializeInit() -> void; + auto serialize() -> serializer; + auto unserialize(serializer&) -> bool; + auto serializeAll(serializer&) -> void; + auto serialize(serializer&) -> void; + struct Information { string manifest; } information; @@ -42,6 +52,7 @@ privileged: bool _loaded = false; Model _model = Model::WonderSwan; bool _orientation = 0; //0 = horizontal, 1 = vertical + uint _serializeSize = 0; }; extern System system; diff --git a/higan/ws/ws.hpp b/higan/ws/ws.hpp index 1e3f7260..e513b1cb 100644 --- a/higan/ws/ws.hpp +++ b/higan/ws/ws.hpp @@ -58,6 +58,7 @@ namespace WonderSwan { #include #include #include + #include } #include diff --git a/icarus/heuristics/wonderswan.cpp b/icarus/heuristics/wonderswan.cpp index 017e3dad..40fe3800 100644 --- a/icarus/heuristics/wonderswan.cpp +++ b/icarus/heuristics/wonderswan.cpp @@ -10,6 +10,7 @@ struct WonderSwanCartridge { string ramType; uint ramSize; bool orientation; //0 = horizontal; 1 = vertical + bool hasRTC; } information; }; @@ -34,10 +35,14 @@ WonderSwanCartridge::WonderSwanCartridge(string location, uint8_t* data, uint si information.orientation = metadata[12] & 1; + information.hasRTC = metadata[13] & 1; + manifest.append("board\n"); manifest.append(" rom name=program.rom size=0x", hex(size), "\n"); if(information.ramType && information.ramSize) manifest.append(" ram name=save.ram type=", information.ramType, " size=0x", hex(information.ramSize), "\n"); + if(information.hasRTC) + manifest.append(" rtc name=rtc.ram size=16\n"); manifest.append("\n"); manifest.append("information\n"); manifest.append(" title: ", prefixname(location), "\n");