mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
d3413db04a
commit
379ab6991f
|
@ -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/";
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 = "";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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; }
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<uint16_t> hi) -> void {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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<Sprite> 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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -58,6 +58,7 @@ namespace WonderSwan {
|
|||
#include <ws/cpu/cpu.hpp>
|
||||
#include <ws/ppu/ppu.hpp>
|
||||
#include <ws/apu/apu.hpp>
|
||||
#include <ws/cheat/cheat.hpp>
|
||||
}
|
||||
|
||||
#include <ws/interface/interface.hpp>
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue