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
This commit is contained in:
Tim Allen 2016-03-25 17:19:08 +11:00
parent d3413db04a
commit 379ab6991f
41 changed files with 904 additions and 256 deletions

View File

@ -6,7 +6,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; 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 Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@ -47,7 +47,7 @@ auto System::serializeAll(serializer& s) -> void {
auto System::serializeInit() -> void { auto System::serializeInit() -> void {
serializer s; serializer s;
uint signature = 0, version = 0, crc32 = 0; uint signature = 0, version = 0;
char hash[64], description[512]; char hash[64], description[512];
s.integer(signature); s.integer(signature);

View File

@ -1,32 +1,35 @@
auto V30MZ::read(Size size, uint16 segment, uint16 address) -> uint32 { auto V30MZ::read(Size size, uint16 segment, uint16 address) -> uint32 {
uint32 data = read(segment * 16 + address); uint32 data;
if(size == Word) data |= read(segment * 16 + ++address) << 8; if(size >= Byte) data.byte(0) = read(segment * 16 + address++);
if(size == Long) data |= read(segment * 16 + ++address) << 16; if(size >= Word) data.byte(1) = read(segment * 16 + address++);
if(size == Long) data |= read(segment * 16 + ++address) << 24; if(size >= Long) data.byte(2) = read(segment * 16 + address++);
if(size >= Long) data.byte(3) = read(segment * 16 + address++);
return data; return data;
} }
auto V30MZ::write(Size size, uint16 segment, uint16 address, uint16 data) -> void { auto V30MZ::write(Size size, uint16 segment, uint16 address, uint16 data) -> void {
write(segment * 16 + address, data); if(size >= Byte) write(segment * 16 + address++, data.byte(0));
if(size == Word) write(segment * 16 + ++address, data >> 8); if(size >= Word) write(segment * 16 + address++, data.byte(1));
} }
// //
auto V30MZ::in(Size size, uint16 address) -> uint16 { auto V30MZ::in(Size size, uint16 address) -> uint16 {
uint16 data = in(address); uint16 data;
if(size == Word) data |= in(++address) << 8; if(size >= Byte) data.byte(0) = in(address++);
if(size >= Word) data.byte(1) = in(address++);
return data; return data;
} }
auto V30MZ::out(Size size, uint16 address, uint16 data) -> void { auto V30MZ::out(Size size, uint16 address, uint16 data) -> void {
out(address, data); if(size >= Byte) out(address++, data.byte(0));
if(size == Word) out(++address, data >> 8); if(size >= Word) out(address++, data.byte(1));
} }
// //
auto V30MZ::fetch(Size size) -> uint16 { auto V30MZ::fetch(Size size) -> uint16 {
wait(size);
uint16 data = read(size, r.cs, r.ip); uint16 data = read(size, r.cs, r.ip);
return r.ip += size, data; return r.ip += size, data;
} }

View File

@ -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);
}

View File

@ -15,6 +15,7 @@ namespace Processor {
#include "instructions-misc.cpp" #include "instructions-misc.cpp"
#include "instructions-move.cpp" #include "instructions-move.cpp"
#include "instructions-string.cpp" #include "instructions-string.cpp"
#include "serialization.cpp"
#include "disassembler.cpp" #include "disassembler.cpp"
auto V30MZ::debug(string text) -> void { auto V30MZ::debug(string text) -> void {
@ -53,6 +54,8 @@ auto V30MZ::exec() -> void {
} }
auto V30MZ::interrupt(uint8 vector) -> void { auto V30MZ::interrupt(uint8 vector) -> void {
wait(32);
state.halt = false; state.halt = false;
state.poll = true; state.poll = true;
state.prefix = false; state.prefix = false;
@ -81,10 +84,7 @@ auto V30MZ::interrupt(uint8 vector) -> void {
} }
auto V30MZ::instruction() -> void { auto V30MZ::instruction() -> void {
opcode = fetch(); switch(opcode = fetch()) {
wait(1);
switch(opcode) {
case 0x00: return opAddMemReg(Byte); case 0x00: return opAddMemReg(Byte);
case 0x01: return opAddMemReg(Word); case 0x01: return opAddMemReg(Word);
case 0x02: return opAddRegMem(Byte); case 0x02: return opAddRegMem(Byte);

View File

@ -203,6 +203,9 @@ struct V30MZ {
auto opLoadString(Size); auto opLoadString(Size);
auto opScanString(Size); auto opScanString(Size);
//serialization.cpp
auto serialize(serializer&) -> void;
//disassembler.cpp //disassembler.cpp
auto disassemble(uint16 cs, uint16 ip, bool registers = true, bool bytes = true) -> string; auto disassemble(uint16 cs, uint16 ip, bool registers = true, bool bytes = true) -> string;

View File

@ -21,10 +21,12 @@ auto SharpRTC::tick_hour() -> void {
auto SharpRTC::tick_day() -> void { auto SharpRTC::tick_day() -> void {
uint days = daysinmonth[month % 12]; uint days = daysinmonth[month % 12];
//add one day for leap years //add one day in February for leap years
if(year % 400 == 0) days++; if(month == 1) {
else if(year % 100 == 0); if(year % 400 == 0) days++;
else if(year % 4 == 0) days++; else if(year % 100 == 0);
else if(year % 4 == 0) days++;
}
if(day++ < days) return; if(day++ < days) return;
day = 1; day = 1;

View File

@ -3,6 +3,7 @@ processors += v30mz
objects += ws-interface ws-system ws-scheduler objects += ws-interface ws-system ws-scheduler
objects += ws-memory ws-eeprom ws-cartridge objects += ws-memory ws-eeprom ws-cartridge
objects += ws-cpu ws-ppu ws-apu objects += ws-cpu ws-ppu ws-apu
objects += ws-cheat
obj/ws-interface.o: ws/interface/interface.cpp $(call rwildcard,ws/interface/) obj/ws-interface.o: ws/interface/interface.cpp $(call rwildcard,ws/interface/)
obj/ws-system.o: ws/system/system.cpp $(call rwildcard,ws/system/) 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-cpu.o: ws/cpu/cpu.cpp $(call rwildcard,ws/cpu/)
obj/ws-ppu.o: ws/ppu/ppu.cpp $(call rwildcard,ws/ppu/) 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-apu.o: ws/apu/apu.cpp $(call rwildcard,ws/apu/)
obj/ws-cheat.o: ws/cheat/cheat.cpp $(call rwildcard,ws/cheat/)

View File

@ -10,6 +10,7 @@ APU apu;
#include "channel3.cpp" #include "channel3.cpp"
#include "channel4.cpp" #include "channel4.cpp"
#include "channel5.cpp" #include "channel5.cpp"
#include "serialization.cpp"
auto APU::Enter() -> void { auto APU::Enter() -> void {
while(true) scheduler.synchronize(), apu.main(); while(true) scheduler.synchronize(), apu.main();

View File

@ -10,10 +10,23 @@ struct APU : Thread, IO {
auto portRead(uint16 addr) -> uint8; auto portRead(uint16 addr) -> uint8;
auto portWrite(uint16 addr, uint8 data) -> void; auto portWrite(uint16 addr, uint8 data) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct State { struct State {
uint13 sweepClock; uint13 sweepClock;
} s; } s;
struct Registers {
//$008f SND_WAVE_BASE
uint8 waveBase;
//$0091 SND_OUTPUT
uint1 speakerEnable;
uint2 speakerShift;
uint1 headphoneEnable;
} r;
struct DMA { struct DMA {
auto run() -> void; auto run() -> void;
@ -40,30 +53,6 @@ struct APU : Thread, IO {
} r; } r;
} dma; } 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 { struct Channel1 {
auto run() -> void; auto run() -> void;

View File

@ -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);
}

View File

@ -4,7 +4,39 @@ namespace WonderSwan {
Cartridge cartridge; Cartridge cartridge;
#include "memory.cpp" #include "memory.cpp"
#include "rtc.cpp"
#include "io.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 { auto Cartridge::load() -> void {
information.manifest = ""; 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.title = document["information/title"].text();
information.orientation = document["information/orientation"].text() == "vertical"; information.orientation = document["information/orientation"].text() == "vertical";
information.sha256 = Hash::SHA256(rom.data, rom.size).digest(); information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
@ -56,17 +96,12 @@ auto Cartridge::unload() -> void {
ram.size = 0; ram.size = 0;
ram.mask = 0; ram.mask = 0;
ram.name = ""; ram.name = "";
}
auto Cartridge::power() -> void { delete[] rtc.data;
eeprom.power(); rtc.data = nullptr;
rtc.size = 0;
bus.map(this, 0x00c0, 0x00c8); rtc.mask = 0;
rtc.name = "";
r.bank_rom0 = 0xff;
r.bank_rom1 = 0xff;
r.bank_rom2 = 0xff;
r.bank_sram = 0xff;
} }
} }

View File

@ -1,22 +1,41 @@
struct Cartridge : IO { struct Cartridge : Thread, IO {
auto load() -> void; static auto Enter() -> void;
auto unload() -> void; auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void; auto power() -> void;
auto load() -> void;
auto unload() -> void;
//memory.cpp
auto romRead(uint20 addr) -> uint8; auto romRead(uint20 addr) -> uint8;
auto romWrite(uint20 addr, uint8 data) -> void; auto romWrite(uint20 addr, uint8 data) -> void;
auto ramRead(uint20 addr) -> uint8; auto ramRead(uint20 addr) -> uint8;
auto ramWrite(uint20 addr, uint8 data) -> void; 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 portRead(uint16 addr) -> uint8 override;
auto portWrite(uint16 addr, uint8 data) -> void override; auto portWrite(uint16 addr, uint8 data) -> void override;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Registers { struct Registers {
uint8 bank_rom0; uint8 romBank0;
uint8 bank_rom1; uint8 romBank1;
uint8 bank_rom2; uint8 romBank2;
uint8 bank_sram; uint8 sramBank;
} r; } r;
struct Memory { struct Memory {
@ -24,9 +43,29 @@ struct Cartridge : IO {
uint size = 0; uint size = 0;
uint mask = 0; uint mask = 0;
string name; 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; EEPROM eeprom;
RTC rtc;
struct Information { struct Information {
string manifest; string manifest;

View File

@ -1,15 +1,15 @@
auto Cartridge::portRead(uint16 addr) -> uint8 { auto Cartridge::portRead(uint16 addr) -> uint8 {
//BANK_ROM2 //BANK_ROM2
if(addr == 0x00c0) return r.bank_rom2; if(addr == 0x00c0) return r.romBank2;
//BANK_SRAM //BANK_SRAM
if(addr == 0x00c1) return r.bank_sram; if(addr == 0x00c1) return r.sramBank;
//BANK_ROM0 //BANK_ROM0
if(addr == 0x00c2) return r.bank_rom0; if(addr == 0x00c2) return r.romBank0;
//BANK_ROM1 //BANK_ROM1
if(addr == 0x00c3) return r.bank_rom1; if(addr == 0x00c3) return r.romBank1;
//EEP_DATA //EEP_DATA
if(addr == 0x00c4) return eeprom.read(EEPROM::DataLo); if(addr == 0x00c4) return eeprom.read(EEPROM::DataLo);
@ -22,42 +22,42 @@ auto Cartridge::portRead(uint16 addr) -> uint8 {
//EEP_STATUS //EEP_STATUS
if(addr == 0x00c8) return eeprom.read(EEPROM::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; return 0x00;
} }
auto Cartridge::portWrite(uint16 addr, uint8 data) -> void { auto Cartridge::portWrite(uint16 addr, uint8 data) -> void {
//BANK_ROM2 //BANK_ROM2
if(addr == 0x00c0) { if(addr == 0x00c0) r.romBank2 = data;
r.bank_rom2 = data;
return;
}
//BANK_SRAM //BANK_SRAM
if(addr == 0x00c1) { if(addr == 0x00c1) r.sramBank = data;
r.bank_sram = data;
return;
}
//BANK_ROM0 //BANK_ROM0
if(addr == 0x00c2) { if(addr == 0x00c2) r.romBank0 = data;
r.bank_rom0 = data;
return;
}
//BANK_ROM1 //BANK_ROM1
if(addr == 0x00c3) { if(addr == 0x00c3) r.romBank1 = data;
r.bank_rom1 = data;
return;
}
//EEP_DATA //EEP_DATA
if(addr == 0x00c4) return eeprom.write(EEPROM::DataLo, data); if(addr == 0x00c4) eeprom.write(EEPROM::DataLo, data);
if(addr == 0x00c5) return eeprom.write(EEPROM::DataHi, data); if(addr == 0x00c5) eeprom.write(EEPROM::DataHi, data);
//EEP_ADDR //EEP_ADDR
if(addr == 0x00c6) return eeprom.write(EEPROM::AddressLo, data); if(addr == 0x00c6) eeprom.write(EEPROM::AddressLo, data);
if(addr == 0x00c7) return eeprom.write(EEPROM::AddressHi, data); if(addr == 0x00c7) eeprom.write(EEPROM::AddressHi, data);
//EEP_CMD //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);
} }

View File

@ -3,9 +3,9 @@ auto Cartridge::romRead(uint20 addr) -> uint8 {
if(!rom.data) return 0x00; if(!rom.data) return 0x00;
uint28 offset; uint28 offset;
switch(addr.byte(2)) { switch(addr.byte(2)) {
case 2: offset = r.bank_rom0 << 16 | addr.bits(0,15); break; //20000-2ffff case 2: offset = r.romBank0 << 16 | addr.bits(0,15); break; //20000-2ffff
case 3: offset = r.bank_rom1 << 16 | addr.bits(0,15); break; //30000-3ffff case 3: offset = r.romBank1 << 16 | addr.bits(0,15); break; //30000-3ffff
default: offset = r.bank_rom2 << 20 | addr.bits(0,19); break; //40000-fffff default: offset = r.romBank2 << 20 | addr.bits(0,19); break; //40000-fffff
} }
return rom.data[offset & rom.mask]; return rom.data[offset & rom.mask];
} }
@ -16,12 +16,12 @@ auto Cartridge::romWrite(uint20 addr, uint8 data) -> void {
//10000-1ffff //10000-1ffff
auto Cartridge::ramRead(uint20 addr) -> uint8 { auto Cartridge::ramRead(uint20 addr) -> uint8 {
if(!ram.data) return 0x00; 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]; return ram.data[offset & ram.mask];
} }
auto Cartridge::ramWrite(uint20 addr, uint8 data) -> void { auto Cartridge::ramWrite(uint20 addr, uint8 data) -> void {
if(!ram.data) return; 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; ram.data[offset & ram.mask] = data;
} }

149
higan/ws/cartridge/rtc.cpp Normal file
View File

@ -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;
}
}

View File

@ -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);
}

28
higan/ws/cheat/cheat.cpp Normal file
View File

@ -0,0 +1,28 @@
#include <ws/ws.hpp>
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<uint> {
for(auto& code : codes) {
if(code.addr == addr && (code.comp == Unused || code.comp == comp)) {
return code.data;
}
}
return nothing;
}
}

18
higan/ws/cheat/cheat.hpp Normal file
View File

@ -0,0 +1,18 @@
struct Cheat {
struct Code {
uint addr;
uint comp;
uint data;
};
vector<Code> 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<uint>;
};
extern Cheat cheat;

View File

@ -6,6 +6,7 @@ CPU cpu;
#include "io.cpp" #include "io.cpp"
#include "interrupt.cpp" #include "interrupt.cpp"
#include "dma.cpp" #include "dma.cpp"
#include "serialization.cpp"
auto CPU::Enter() -> void { auto CPU::Enter() -> void {
while(true) scheduler.synchronize(), cpu.main(); while(true) scheduler.synchronize(), cpu.main();
@ -22,6 +23,9 @@ auto CPU::step(uint clocks) -> void {
apu.clock -= clocks; apu.clock -= clocks;
if(apu.clock < 0) co_switch(apu.thread); 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 { auto CPU::wait(uint clocks) -> void {
@ -51,9 +55,7 @@ auto CPU::power() -> void {
bus.map(this, 0x00a0); bus.map(this, 0x00a0);
bus.map(this, 0x00b0); bus.map(this, 0x00b0);
bus.map(this, 0x00b2); bus.map(this, 0x00b2);
bus.map(this, 0x00b4); bus.map(this, 0x00b4, 0x00b6);
bus.map(this, 0x00b5);
bus.map(this, 0x00b6);
if(system.model() != Model::WonderSwan) { if(system.model() != Model::WonderSwan) {
bus.map(this, 0x0040, 0x0049); bus.map(this, 0x0040, 0x0049);

View File

@ -35,6 +35,9 @@ struct CPU : Processor::V30MZ, Thread, IO {
//dma.cpp //dma.cpp
auto dmaTransfer() -> void; auto dmaTransfer() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
struct Registers { struct Registers {
//$0040-0042 DMA_SRC //$0040-0042 DMA_SRC
uint20 dmaSource; uint20 dmaSource;

View File

@ -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);
}

View File

@ -2,6 +2,8 @@
namespace WonderSwan { namespace WonderSwan {
#include "serialization.cpp"
auto EEPROM::name() const -> string { return _name; } auto EEPROM::name() const -> string { return _name; }
auto EEPROM::data() -> uint16* { return _data; } auto EEPROM::data() -> uint16* { return _data; }
auto EEPROM::size() const -> uint { return _size; } auto EEPROM::size() const -> uint { return _size; }

View File

@ -26,6 +26,9 @@ struct EEPROM {
auto read(uint) -> uint8; auto read(uint) -> uint8;
auto write(uint, uint8) -> void; auto write(uint, uint8) -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
private: private:
auto execute() -> void; auto execute() -> void;

View File

@ -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);
}

View File

@ -16,8 +16,8 @@ Interface::Interface() {
information.aspectRatio = 1.0; information.aspectRatio = 1.0;
information.resettable = false; information.resettable = false;
information.capability.states = false; information.capability.states = true;
information.capability.cheats = false; information.capability.cheats = true;
media.append({ID::WonderSwan, "WonderSwan", "ws", true}); media.append({ID::WonderSwan, "WonderSwan", "ws", true});
media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc", true}); media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc", true});
@ -94,6 +94,7 @@ auto Interface::group(uint id) -> uint {
case ID::ROM: case ID::ROM:
case ID::RAM: case ID::RAM:
case ID::EEPROM: case ID::EEPROM:
case ID::RTC:
switch(system.model()) { switch(system.model()) {
case Model::WonderSwan: case Model::WonderSwan:
return ID::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 = system.eeprom.name()) interface->saveRequest(ID::SystemEEPROM, name);
if(auto name = cartridge.ram.name) interface->saveRequest(ID::RAM, 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.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 { 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) { if(id == ID::EEPROM) {
stream.read((uint8_t*)cartridge.eeprom.data(), min(cartridge.eeprom.size() * sizeof(uint16), stream.size())); 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 { 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) { if(id == ID::EEPROM) {
stream.write((uint8_t*)cartridge.eeprom.data(), cartridge.eeprom.size() * sizeof(uint16)); 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 { auto Interface::unload() -> void {
@ -169,11 +181,24 @@ auto Interface::run() -> void {
} }
auto Interface::serialize() -> serializer { auto Interface::serialize() -> serializer {
return {}; system.runToSave();
return system.serialize();
} }
auto Interface::unserialize(serializer& s) -> bool { 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 { auto Interface::cap(const string& name) -> bool {

View File

@ -16,6 +16,7 @@ struct ID {
ROM, ROM,
RAM, RAM,
EEPROM, EEPROM,
RTC,
}; };
enum : uint { enum : uint {
@ -47,6 +48,8 @@ struct Interface : Emulator::Interface {
auto serialize() -> serializer override; auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override; auto unserialize(serializer&) -> bool override;
auto cheatSet(const lstring&) -> void;
auto cap(const string& name) -> bool override; auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override; auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override; auto set(const string& name, const any& value) -> bool override;

View File

@ -9,6 +9,10 @@ auto InternalRAM::power() -> void {
for(auto& byte : memory) byte = 0x00; 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 { auto InternalRAM::read(uint16 addr, uint size) -> uint32 {
if(size == Long) return read(addr + 0, Word) << 0 | read(addr + 2, Word) << 16; 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; 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 { auto Bus::read(uint20 addr) -> uint8 {
if(addr.bits(16,19) == 0) return iram.read(addr); uint8 data = 0;
if(addr.bits(16,19) == 1) return cartridge.ramRead(addr); if(addr.bits(16,19) == 0) data = iram.read(addr);
if(addr.bits(16,19) >= 2) return cartridge.romRead(addr); if(addr.bits(16,19) == 1) data = cartridge.ramRead(addr);
unreachable; 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 { auto Bus::write(uint20 addr, uint8 data) -> void {
if(addr.bits(16,19) == 0) return iram.write(addr, data); if(addr.bits(16,19) == 0) iram.write(addr, data);
if(addr.bits(16,19) == 1) return cartridge.ramWrite(addr, data); if(addr.bits(16,19) == 1) cartridge.ramWrite(addr, data);
if(addr.bits(16,19) >= 2) return cartridge.romWrite(addr, data); if(addr.bits(16,19) >= 2) cartridge.romWrite(addr, data);
} }
auto Bus::map(IO* io, uint16_t lo, maybe<uint16_t> hi) -> void { auto Bus::map(IO* io, uint16_t lo, maybe<uint16_t> hi) -> void {

View File

@ -5,6 +5,7 @@ struct IO {
struct InternalRAM { struct InternalRAM {
auto power() -> void; auto power() -> void;
auto serialize(serializer&) -> void;
auto read(uint16 addr, uint size = Byte) -> uint32; auto read(uint16 addr, uint size = Byte) -> uint32;
auto write(uint16 addr, uint8 data) -> void; auto write(uint16 addr, uint8 data) -> void;

63
higan/ws/ppu/latch.cpp Normal file
View File

@ -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);
}
}

View File

@ -4,23 +4,27 @@ namespace WonderSwan {
PPU ppu; PPU ppu;
#include "io.cpp" #include "io.cpp"
#include "render-sprite.cpp" #include "latch.cpp"
#include "render-mono.cpp" #include "render-mono.cpp"
#include "render-color.cpp" #include "render-color.cpp"
#include "video.cpp" #include "video.cpp"
#include "serialization.cpp"
auto PPU::Enter() -> void { auto PPU::Enter() -> void {
while(true) scheduler.synchronize(), ppu.main(); while(true) scheduler.synchronize(), ppu.main();
} }
auto PPU::main() -> void { auto PPU::main() -> void {
if(s.vclk == 142) {
latchOAM();
}
if(s.vclk < 144) { if(s.vclk < 144) {
latchRegisters(); latchRegisters();
renderSpriteFetch(); latchSprites();
renderSpriteDecode();
for(auto x : range(224)) { for(auto x : range(224)) {
if(!r.lcdEnable) { if(!r.lcdEnable) {
pixel = {Pixel::Source::Back, 0x000}; s.pixel = {Pixel::Source::Back, 0x000};
} else if(!system.color()) { } else if(!system.color()) {
renderMonoBack(); renderMonoBack();
renderMonoScreenOne(); renderMonoScreenOne();
@ -32,7 +36,7 @@ auto PPU::main() -> void {
renderColorScreenTwo(); renderColorScreenTwo();
renderColorSprite(); renderColorSprite();
} }
output[s.vclk * 224 + s.hclk] = pixel.color; output[s.vclk * 224 + s.hclk] = s.pixel.color;
step(1); step(1);
} }
step(32); step(32);
@ -54,7 +58,7 @@ auto PPU::main() -> void {
auto PPU::scanline() -> void { auto PPU::scanline() -> void {
s.hclk = 0; s.hclk = 0;
s.vclk++; if(++s.vclk == 159) frame();
if(s.vclk == r.lineCompare) { if(s.vclk == r.lineCompare) {
cpu.raise(CPU::Interrupt::LineCompare); cpu.raise(CPU::Interrupt::LineCompare);
} }
@ -71,7 +75,6 @@ auto PPU::scanline() -> void {
} }
} }
} }
if(s.vclk == 159) frame();
} }
auto PPU::frame() -> void { auto PPU::frame() -> void {
@ -88,36 +91,6 @@ auto PPU::step(uint clocks) -> void {
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread); 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 { auto PPU::power() -> void {
create(PPU::Enter, 3'072'000); create(PPU::Enter, 3'072'000);
@ -127,58 +100,13 @@ auto PPU::power() -> void {
bus.map(this, 0x00a4, 0x00ab); bus.map(this, 0x00a4, 0x00ab);
for(auto& n : output) n = 0; for(auto& n : output) n = 0;
for(auto& n : oam[0]) n = 0; memory::fill(&s, sizeof(State));
for(auto& n : oam[1]) n = 0; 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.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.vtotal = 158;
r.vblank = 155; 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(); video.power();
} }

View File

@ -6,16 +6,16 @@ struct PPU : Thread, IO {
auto scanline() -> void; auto scanline() -> void;
auto frame() -> void; auto frame() -> void;
auto step(uint clocks) -> void; auto step(uint clocks) -> void;
auto latchRegisters() -> void;
auto power() -> void; auto power() -> void;
//io.cpp //io.cpp
auto portRead(uint16 addr) -> uint8 override; auto portRead(uint16 addr) -> uint8 override;
auto portWrite(uint16 addr, uint8 data) -> void override; auto portWrite(uint16 addr, uint8 data) -> void override;
//render-sprite.cpp //latch.cpp
auto renderSpriteFetch() -> void; auto latchRegisters() -> void;
auto renderSpriteDecode() -> void; auto latchSprites() -> void;
auto latchOAM() -> void;
//render-mono.cpp //render-mono.cpp
auto renderMonoFetch(uint14 offset, uint3 y, uint3 x) -> uint2; auto renderMonoFetch(uint14 offset, uint3 y, uint3 x) -> uint2;
@ -33,9 +33,15 @@ struct PPU : Thread, IO {
auto renderColorScreenTwo() -> void; auto renderColorScreenTwo() -> void;
auto renderColorSprite() -> void; auto renderColorSprite() -> void;
//serialization.cpp
auto serialize(serializer&) -> void;
//state //state
uint12 output[224 * 144]; struct Pixel {
uint32 oam[2][128]; enum class Source : uint { Back, ScreenOne, ScreenTwo, Sprite };
Source source;
uint12 color;
};
struct Sprite { struct Sprite {
uint8 x; uint8 x;
@ -44,24 +50,21 @@ struct PPU : Thread, IO {
uint1 hflip; uint1 hflip;
uint1 priority; uint1 priority;
uint1 window; uint1 window;
uint4 palette; //renderSpriteDecode() always sets bit3 uint4 palette; //latchSprites() always sets bit3
uint9 tile; uint9 tile;
}; };
vector<Sprite> sprites;
struct Pixel { uint12 output[224 * 144];
enum class Source : uint { Back, ScreenOne, ScreenTwo, Sprite };
Source source;
uint12 color;
} pixel;
struct State { struct State {
bool field; bool field;
uint vclk; uint vclk;
uint hclk; uint hclk;
Pixel pixel;
} s; } s;
struct Latches { struct Latches {
//latchRegisters()
uint8 backColor; uint8 backColor;
uint1 screenOneEnable; uint1 screenOneEnable;
@ -81,14 +84,19 @@ struct PPU : Thread, IO {
uint8 screenTwoWindowY1; uint8 screenTwoWindowY1;
uint1 spriteEnable; uint1 spriteEnable;
uint6 spriteBase;
uint7 spriteFirst;
uint8 spriteCount;
uint1 spriteWindowEnable; uint1 spriteWindowEnable;
uint8 spriteWindowX0; uint8 spriteWindowX0;
uint8 spriteWindowY0; uint8 spriteWindowY0;
uint8 spriteWindowX1; uint8 spriteWindowX1;
uint8 spriteWindowY1; uint8 spriteWindowY1;
//latchSprites()
Sprite sprite[32];
uint spriteCount;
//latchOAM()
uint32 oam[2][128];
uint oamCount;
} l; } l;
struct Registers { struct Registers {

View File

@ -23,7 +23,7 @@ auto PPU::renderColorPalette(uint4 palette, uint4 index) -> uint12 {
auto PPU::renderColorBack() -> void { auto PPU::renderColorBack() -> void {
uint12 color = iram.read(0xfe00 + (l.backColor << 1), Word); uint12 color = iram.read(0xfe00 + (l.backColor << 1), Word);
pixel = {Pixel::Source::Back, color}; s.pixel = {Pixel::Source::Back, color};
} }
auto PPU::renderColorScreenOne() -> void { auto PPU::renderColorScreenOne() -> void {
@ -43,7 +43,7 @@ auto PPU::renderColorScreenOne() -> void {
uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX);
if(tileColor == 0) return; 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 { auto PPU::renderColorScreenTwo() -> void {
@ -68,14 +68,15 @@ auto PPU::renderColorScreenTwo() -> void {
uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX);
if(tileColor == 0) return; 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 { auto PPU::renderColorSprite() -> void {
if(!l.spriteEnable) return; if(!l.spriteEnable) return;
bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1; 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(l.spriteWindowEnable && sprite.window == windowInside) continue;
if((uint8)(s.hclk - sprite.x) > 7) 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); uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX); uint4 tileColor = renderColorFetch(tileOffset, tileY, tileX);
if(tileColor == 0) continue; 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; break;
} }
} }

View File

@ -23,7 +23,7 @@ auto PPU::renderMonoPalette(uint4 palette, uint2 index) -> uint12 {
auto PPU::renderMonoBack() -> void { auto PPU::renderMonoBack() -> void {
uint4 poolColor = 15 - r.pool[l.backColor.bits(0,2)]; 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 { auto PPU::renderMonoScreenOne() -> void {
@ -43,7 +43,7 @@ auto PPU::renderMonoScreenOne() -> void {
uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX);
if(tile.bit(11) && tileColor == 0) return; 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 { auto PPU::renderMonoScreenTwo() -> void {
@ -68,14 +68,15 @@ auto PPU::renderMonoScreenTwo() -> void {
uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX);
if(tile.bit(11) && tileColor == 0) return; 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 { auto PPU::renderMonoSprite() -> void {
if(!l.spriteEnable) return; if(!l.spriteEnable) return;
bool windowInside = s.hclk >= l.spriteWindowX0 && s.hclk <= l.spriteWindowX1; 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(l.spriteWindowEnable && sprite.window == windowInside) continue;
if((uint8)(s.hclk - sprite.x) > 7) 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); uint3 tileX = (uint8)(s.hclk - sprite.x) ^ (sprite.hflip ? 7 : 0);
uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX); uint2 tileColor = renderMonoFetch(tileOffset, tileY, tileX);
if(sprite.palette.bit(2) && tileColor == 0) continue; 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; break;
} }
} }

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -4,6 +4,7 @@ namespace WonderSwan {
System system; System system;
#include "io.cpp" #include "io.cpp"
#include "serialization.cpp"
auto System::loaded() const -> bool { return _loaded; } auto System::loaded() const -> bool { return _loaded; }
auto System::model() const -> Model { return _model; } 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::depth() const -> bool { return r.depth == 1; }
auto System::init() -> void { auto System::init() -> void {
assert(interface != nullptr);
} }
auto System::term() -> void { auto System::term() -> void {
@ -37,6 +39,7 @@ auto System::load(Model model) -> void {
cartridge.load(); cartridge.load();
_loaded = true; _loaded = true;
_orientation = cartridge.information.orientation; _orientation = cartridge.information.orientation;
serializeInit();
} }
auto System::unload() -> void { auto System::unload() -> void {
@ -69,8 +72,18 @@ auto System::power() -> void {
} }
auto System::run() -> 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; bool rotate = keypad.rotate;
keypad.y1 = interface->inputPoll(_orientation, 0, 0); keypad.y1 = interface->inputPoll(_orientation, 0, 0);
keypad.y2 = interface->inputPoll(_orientation, 0, 1); keypad.y2 = interface->inputPoll(_orientation, 0, 1);

View File

@ -13,10 +13,20 @@ struct System : IO {
auto unload() -> void; auto unload() -> void;
auto power() -> void; auto power() -> void;
auto run() -> void; auto run() -> void;
auto runToSave() -> void;
auto pollKeypad() -> void;
//io.cpp
auto portRead(uint16 addr) -> uint8 override; auto portRead(uint16 addr) -> uint8 override;
auto portWrite(uint16 addr, uint8 data) -> void 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 { struct Information {
string manifest; string manifest;
} information; } information;
@ -42,6 +52,7 @@ privileged:
bool _loaded = false; bool _loaded = false;
Model _model = Model::WonderSwan; Model _model = Model::WonderSwan;
bool _orientation = 0; //0 = horizontal, 1 = vertical bool _orientation = 0; //0 = horizontal, 1 = vertical
uint _serializeSize = 0;
}; };
extern System system; extern System system;

View File

@ -58,6 +58,7 @@ namespace WonderSwan {
#include <ws/cpu/cpu.hpp> #include <ws/cpu/cpu.hpp>
#include <ws/ppu/ppu.hpp> #include <ws/ppu/ppu.hpp>
#include <ws/apu/apu.hpp> #include <ws/apu/apu.hpp>
#include <ws/cheat/cheat.hpp>
} }
#include <ws/interface/interface.hpp> #include <ws/interface/interface.hpp>

View File

@ -10,6 +10,7 @@ struct WonderSwanCartridge {
string ramType; string ramType;
uint ramSize; uint ramSize;
bool orientation; //0 = horizontal; 1 = vertical bool orientation; //0 = horizontal; 1 = vertical
bool hasRTC;
} information; } information;
}; };
@ -34,10 +35,14 @@ WonderSwanCartridge::WonderSwanCartridge(string location, uint8_t* data, uint si
information.orientation = metadata[12] & 1; information.orientation = metadata[12] & 1;
information.hasRTC = metadata[13] & 1;
manifest.append("board\n"); manifest.append("board\n");
manifest.append(" rom name=program.rom size=0x", hex(size), "\n"); manifest.append(" rom name=program.rom size=0x", hex(size), "\n");
if(information.ramType && information.ramSize) if(information.ramType && information.ramSize)
manifest.append(" ram name=save.ram type=", information.ramType, " size=0x", hex(information.ramSize), "\n"); 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("\n");
manifest.append("information\n"); manifest.append("information\n");
manifest.append(" title: ", prefixname(location), "\n"); manifest.append(" title: ", prefixname(location), "\n");