From 379ab6991ffb0875cdeb5ea32300af381325ffdb Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Fri, 25 Mar 2016 17:19:08 +1100 Subject: [PATCH] Update to v097r28 release. byuu says: Changelog: (all WSC unless otherwise noted) - fixed LINECMP=0 interrupt case (fixes FF4 world map during airship sequence) - improved CPU timing (fixes Magical Drop flickering and FF1 battle music) - added per-frame OAM caching (fixes sprite glitchiness in Magical Drop, Riviera, etc.) - added RTC emulation (fixes Dicing Knight and Judgement Silversword) - added save state support - added cheat code support (untested because I don't know of any cheat codes that exist for this system) - icarus: can now detect games with RTC chips - SFC: bugfix to SharpRTC emulation (Dai Kaijuu Monogatari II) - ( I was adding the extra leap year day to all 12 months instead of just February ... >_< ) Note that the RTC emulation is very incomplete. It's not really documented at all, and the two games I've tried that use it never even ask you to set the date/time (so they're probably just using it to count seconds.) I'm not even sure if I've implement the level-sensitive behavior correctly (actually, now that I think about it, I need to mask the clear bit in INT_ACK for the level-sensitive interrupts ...) A bit worried about the RTC alarm, because it seems like it'll fire continuously for a full minute. Or even if you turn it off after it fires, then that doesn't seem to be lowering the line until the next second ticks on the RTC, so that likely needs to happen when changing the alarm flag. Also not sure on this RTC's weekday byte. On the SharpRTC, it actually computes this for you. Because it's not at all an easy thing to calculate yourself in 65816 or V30MZ assembler. About 40 lines of code to do it in C. For now, I'm requiring the program to calculate the value itself. Also note that there's some gibberish tiles in Judgement Silversword, sadly. Not sure what's up there, but the game's still fully playable at least. Finally, no surprise: Beat-Mania doesn't run :P --- higan/emulator/emulator.hpp | 2 +- higan/gb/system/serialization.cpp | 2 +- higan/processor/v30mz/memory.cpp | 23 ++-- higan/processor/v30mz/serialization.cpp | 51 ++++++++ higan/processor/v30mz/v30mz.cpp | 8 +- higan/processor/v30mz/v30mz.hpp | 3 + higan/sfc/coprocessor/sharprtc/time.cpp | 10 +- higan/ws/GNUmakefile | 2 + higan/ws/apu/apu.cpp | 1 + higan/ws/apu/apu.hpp | 37 +++--- higan/ws/apu/serialization.cpp | 80 +++++++++++++ higan/ws/cartridge/cartridge.cpp | 55 +++++++-- higan/ws/cartridge/cartridge.hpp | 55 +++++++-- higan/ws/cartridge/io.cpp | 50 ++++---- higan/ws/cartridge/memory.cpp | 10 +- higan/ws/cartridge/rtc.cpp | 149 ++++++++++++++++++++++++ higan/ws/cartridge/serialization.cpp | 18 +++ higan/ws/cheat/cheat.cpp | 28 +++++ higan/ws/cheat/cheat.hpp | 18 +++ higan/ws/cpu/cpu.cpp | 8 +- higan/ws/cpu/cpu.hpp | 3 + higan/ws/cpu/serialization.cpp | 15 +++ higan/ws/eeprom/eeprom.cpp | 2 + higan/ws/eeprom/eeprom.hpp | 3 + higan/ws/eeprom/serialization.cpp | 12 ++ higan/ws/interface/interface.cpp | 33 +++++- higan/ws/interface/interface.hpp | 3 + higan/ws/memory/memory.cpp | 22 ++-- higan/ws/memory/memory.hpp | 1 + higan/ws/ppu/latch.cpp | 63 ++++++++++ higan/ws/ppu/ppu.cpp | 98 +++------------- higan/ws/ppu/ppu.hpp | 40 ++++--- higan/ws/ppu/render-color.cpp | 13 ++- higan/ws/ppu/render-mono.cpp | 13 ++- higan/ws/ppu/render-sprite.cpp | 36 ------ higan/ws/ppu/serialization.cpp | 95 +++++++++++++++ higan/ws/system/serialization.cpp | 66 +++++++++++ higan/ws/system/system.cpp | 15 ++- higan/ws/system/system.hpp | 11 ++ higan/ws/ws.hpp | 1 + icarus/heuristics/wonderswan.cpp | 5 + 41 files changed, 904 insertions(+), 256 deletions(-) create mode 100644 higan/processor/v30mz/serialization.cpp create mode 100644 higan/ws/apu/serialization.cpp create mode 100644 higan/ws/cartridge/rtc.cpp create mode 100644 higan/ws/cartridge/serialization.cpp create mode 100644 higan/ws/cheat/cheat.cpp create mode 100644 higan/ws/cheat/cheat.hpp create mode 100644 higan/ws/cpu/serialization.cpp create mode 100644 higan/ws/eeprom/serialization.cpp create mode 100644 higan/ws/ppu/latch.cpp delete mode 100644 higan/ws/ppu/render-sprite.cpp create mode 100644 higan/ws/ppu/serialization.cpp create mode 100644 higan/ws/system/serialization.cpp 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");