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 {
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/";

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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/)

View File

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

View File

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

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;
#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 = "";
}
}

View File

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

View File

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

View File

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

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 "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);

View File

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

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 {
#include "serialization.cpp"
auto EEPROM::name() const -> string { return _name; }
auto EEPROM::data() -> uint16* { return _data; }
auto EEPROM::size() const -> uint { return _size; }

View File

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

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.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 {

View File

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

View File

@ -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 {

View File

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

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;
#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();
}

View File

@ -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 {

View File

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

View File

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

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;
#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);

View File

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

View File

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

View File

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