Update to v102r04 release.

byuu says:

Changelog:
  - Super Game Boy support is functional once again
  - new GameBoy::SuperGameBoyInterface class
  - system.(dmg,cgb,sgb) is now Model::(Super)GameBoy(Color) ala the PC
    Engine
  - merged WonderSwanInterface, WonderSwanColorInterface shared
    functions to WonderSwan::Interface
  - merged GameBoyInterface, GameBoyColorInterface shared functions to
    GameBoy::Interface
  - Interface::unload() now calls Interface::save() for Master System,
    Game Gear, Mega Drive, PC Engine, SuperGrafx
  - PCE: emulated PCE-CD backup RAM; stored per-game as save.ram (2KiB
    file)
      - this means you can now save your progress in games like Neutopia
      - the PCE-CD I/O registers like BRAM write protect are not
        emulated yet
  - PCE: IRQ sources now hold the IRQ line state, instead of the CPU
    holding it
      - this fixes most SuperGrafx games, which were fighting over the
        VDC IRQ line previously
  - PCE: CPU I/O $14xx should return the pending IRQ bits even if IRQs
    are disabled
  - PCE: VCE and the VDCs now synchronize to each other; fixes pixel
    widths in all games
  - PCE: greatly increased the accuracy of the VPC priority selection
    code (windows may be buggy still)
  - HuC6280: PLA, PLX, PLY should set Z, N flags; fixes many game bugs
    [Jonas Quinn]

The big thing I wanted to do was enslave the VDC(s) to the VCE. But
unfortunately, I forgot about the asynchronous DMA channels that each
VDC supports, so this isn't going to be possible I'm afraid.

In the most demanding case, Daimakaimura in-game, we're looking at 85fps
on my Xeon E3 1276v3. So ... not great, and we don't even have sound
connected yet.

We are going to have to profile and optimize this code once sound
emulation and save states are in.

Basically, think of it like this: the VCE, VDC0, and VDC1 all have the
same overhead, scheduling wise (which is the bulk of the performance
loss) as the dot-renderer for the SNES core. So it's like there's three
bsnes-accuracy PPU threads running just for video.

-----

Oh, just a fair warning ... the hooks for the SGB are a work in
progress.

If anyone is working on higan or a fork and want to do something similar
to it, don't use it as a template, at least not yet.

Right now, higan looks like this:

  - Emulator::Video handles the platform→videoRefresh calls
  - Emulator::Audio handles the platform→audioSample calls
  - each core hard-codes the platform→inputPoll, inputRumble calls
  - each core hard-codes calls to path, open, load to process files
  - dipSettings and notify are specialty hacks, neither are even hooked
    up right now to anything

With the SGB, it's an emulation core inside an emulation core, so
ideally you want to hook all of those functions. Emulator::Video and
Emulator::Audio aren't really abstractions over that, as the GB core
calls them and we have to special case not calling them in SGB mode.

The path, open, load can be implemented without hooks, thanks to the UI
only using one instance of Emulator::Platform for all cores. All we have
to do is override the folder path ID for the "Game Boy.sys" folder, so
that it picks "Super Game Boy.sfc/" and loads its boot ROM instead.
That's just a simple argument to GameBoy::System::load() and we're done.

dipSettings, notify and inputRumble don't matter. But we do also have to
hook inputPoll as well.

The nice idea would be for SuperFamicom::ICD2 to inherit from
Emulator::Platform and provide the desired functions that we need to
overload. After that, we'd just need the GB core to keep an abstraction
over the global Emulator::platform\* handle, to select between the UI
version and the SFC::ICD2 version.

However ... that doesn't work because of Emulator::Video and
Emulator::Audio. They would also have to gain an abstraction over
Emulator::platform\*, and even worse ... you'd have to constantly swap
between the two so that the SFC core uses the UI, and the GB core uses
the ICD2.

And so, for right now, I'm checking Model::SuperGameBoy() -> bool
everywhere, and choosing between the UI and ICD2 targets that way. And
as such, the ICD2 doesn't really need Emulator::Platform inheritance,
although it certainly could do that and just use the functions it needs.

But the SGB is even weirder, because we need additional new signals
beyond just Emulator::Platform, like joypWrite(), etc.

I'd also like to work on the Emulator::Stream for the SGB core. I don't
see why we can't have the GB core create its own stream, and let the
ICD2 just use that instead. We just have to be careful about the ICD2's
CPU soft reset function, to make sure the GB core's Stream object
remains valid. What I think that needs is a way to release an
Emulator::Stream individually, rather than calling
Emulator::Audio::reset() to do it. They are shared\_pointer objects, so
I think if I added a destructor function to remove it from
Emulator::Audio::streams, then that should work.
This commit is contained in:
Tim Allen 2017-01-26 12:06:06 +11:00
parent 186f008574
commit ee7662a8be
59 changed files with 697 additions and 947 deletions

View File

@ -12,7 +12,7 @@ using namespace nall;
namespace Emulator { namespace Emulator {
static const string Name = "higan"; static const string Name = "higan";
static const string Version = "102.03"; static const string Version = "102.04";
static const string Author = "byuu"; static const string Author = "byuu";
static const string License = "GPLv3"; static const string License = "GPLv3";
static const string Website = "http://byuu.org/"; static const string Website = "http://byuu.org/";

View File

@ -25,11 +25,11 @@ auto APU::main() -> void {
hipass(sequencer.left, sequencer.leftBias); hipass(sequencer.left, sequencer.leftBias);
hipass(sequencer.right, sequencer.rightBias); hipass(sequencer.right, sequencer.rightBias);
if(!system.sgb()) { if(!Model::SuperGameBoy()) {
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0); stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
} else { } else {
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0}; double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
//interface->audioSample(samples, 2); superGameBoy->audioSample(samples, 2);
} }
if(cycle == 0) { //512hz if(cycle == 0) { //512hz
@ -63,7 +63,7 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
auto APU::power() -> void { auto APU::power() -> void {
create(Enter, 2 * 1024 * 1024); create(Enter, 2 * 1024 * 1024);
if(!system.sgb()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024); if(!Model::SuperGameBoy()) stream = Emulator::audio.createStream(2, 2 * 1024 * 1024);
for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this; for(uint n = 0xff10; n <= 0xff3f; n++) bus.mmio[n] = this;
square1.power(); square1.power();
@ -91,7 +91,7 @@ auto APU::readIO(uint16 addr) -> uint8 {
auto APU::writeIO(uint16 addr, uint8 data) -> void { auto APU::writeIO(uint16 addr, uint8 data) -> void {
if(!sequencer.enable) { if(!sequencer.enable) {
bool valid = addr == 0xff26; //NR52 bool valid = addr == 0xff26; //NR52
if(!system.cgb()) { if(!Model::GameBoyColor()) {
//NRx1 length is writable only on DMG,SGB; not on CGB //NRx1 length is writable only on DMG,SGB; not on CGB
if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0) if(addr == 0xff11) valid = true, data &= 0x3f; //NR11; duty is not writable (remains 0)
if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0) if(addr == 0xff16) valid = true, data &= 0x3f; //NR21; duty is not writable (remains 0)

View File

@ -91,10 +91,10 @@ auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
if(!enable) { if(!enable) {
//power(bool) resets length counters when true (eg for CGB only) //power(bool) resets length counters when true (eg for CGB only)
apu.square1.power(system.cgb()); apu.square1.power(Model::GameBoyColor());
apu.square2.power(system.cgb()); apu.square2.power(Model::GameBoyColor());
apu.wave.power(system.cgb()); apu.wave.power(Model::GameBoyColor());
apu.noise.power(system.cgb()); apu.noise.power(Model::GameBoyColor());
power(); power();
} else { } else {
apu.phase = 0; apu.phase = 0;

View File

@ -47,7 +47,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
if(addr >= 0xff30 && addr <= 0xff3f) { if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) { if(enable) {
if(!system.cgb() && !patternHold) return 0xff; if(!Model::GameBoyColor() && !patternHold) return 0xff;
return pattern[patternOffset >> 1]; return pattern[patternOffset >> 1];
} else { } else {
return pattern[addr & 15]; return pattern[addr & 15];
@ -84,7 +84,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
frequency.bits(10,8) = data.bits(2,0); frequency.bits(10,8) = data.bits(2,0);
if(data.bit(7)) { if(data.bit(7)) {
if(!system.cgb() && patternHold) { if(!Model::GameBoyColor() && patternHold) {
//DMG,SGB trigger while channel is being read corrupts wave RAM //DMG,SGB trigger while channel is being read corrupts wave RAM
if((patternOffset >> 1) <= 3) { if((patternOffset >> 1) <= 3) {
//if current pattern is with 0-3; only byte 0 is corrupted //if current pattern is with 0-3; only byte 0 is corrupted
@ -113,7 +113,7 @@ auto APU::Wave::write(uint16 addr, uint8 data) -> void {
if(addr >= 0xff30 && addr <= 0xff3f) { if(addr >= 0xff30 && addr <= 0xff3f) {
if(enable) { if(enable) {
if(!system.cgb() && !patternHold) return; if(!Model::GameBoyColor() && !patternHold) return;
pattern[patternOffset >> 1] = data; pattern[patternOffset >> 1] = data;
} else { } else {
pattern[addr & 15] = data; pattern[addr & 15] = data;

View File

@ -14,25 +14,25 @@ namespace GameBoy {
#include "serialization.cpp" #include "serialization.cpp"
Cartridge cartridge; Cartridge cartridge;
auto Cartridge::load(System::Revision revision) -> bool { auto Cartridge::load() -> bool {
information = Information(); information = {};
switch(revision) { if(Model::GameBoy()) {
case System::Revision::GameBoy:
if(auto pathID = platform->load(ID::GameBoy, "Game Boy", "gb")) { if(auto pathID = platform->load(ID::GameBoy, "Game Boy", "gb")) {
information.pathID = pathID(); information.pathID = pathID();
} else return false; } else return false;
break; }
case System::Revision::SuperGameBoy:
if(auto pathID = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) { if(Model::GameBoyColor()) {
information.pathID = pathID();
} else return false;
break;
case System::Revision::GameBoyColor:
if(auto pathID = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) { if(auto pathID = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
information.pathID = pathID(); information.pathID = pathID();
} else return false; } else return false;
break; }
if(Model::SuperGameBoy()) {
if(auto pathID = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
information.pathID = pathID();
} else return false;
} }
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) { if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
@ -136,13 +136,11 @@ auto Cartridge::readIO(uint16 addr) -> uint8 {
if(bootromEnable) { if(bootromEnable) {
const uint8* data = nullptr; const uint8* data = nullptr;
switch(system.revision()) { default: if(Model::GameBoy()) data = system.bootROM.dmg;
case System::Revision::GameBoy: data = system.bootROM.dmg; break; if(Model::GameBoyColor()) data = system.bootROM.cgb;
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break; if(Model::SuperGameBoy()) data = system.bootROM.sgb;
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
}
if(addr >= 0x0000 && addr <= 0x00ff) return data[addr]; if(addr >= 0x0000 && addr <= 0x00ff) return data[addr];
if(addr >= 0x0200 && addr <= 0x08ff && system.cgb()) return data[addr - 256]; if(addr >= 0x0200 && addr <= 0x08ff && Model::GameBoyColor()) return data[addr - 256];
} }
return mapper->readIO(addr); return mapper->readIO(addr);

View File

@ -4,7 +4,7 @@ struct Cartridge : MMIO {
auto manifest() const -> string { return information.manifest; } auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; } auto title() const -> string { return information.title; }
auto load(System::Revision revision) -> bool; auto load() -> bool;
auto save() -> void; auto save() -> void;
auto unload() -> void; auto unload() -> void;

View File

@ -102,7 +102,7 @@ auto CPU::power() -> void {
bus.mmio[0xff0f] = this; //IF bus.mmio[0xff0f] = this; //IF
bus.mmio[0xffff] = this; //IE bus.mmio[0xffff] = this; //IE
if(system.cgb()) { if(Model::GameBoyColor()) {
bus.mmio[0xff4d] = this; //KEY1 bus.mmio[0xff4d] = this; //KEY1
bus.mmio[0xff51] = this; //HDMA1 bus.mmio[0xff51] = this; //HDMA1
bus.mmio[0xff52] = this; //HDMA2 bus.mmio[0xff52] = this; //HDMA2

View File

@ -6,19 +6,22 @@ auto CPU::wramAddress(uint16 addr) const -> uint {
} }
auto CPU::joypPoll() -> void { auto CPU::joypPoll() -> void {
uint button = 0, dpad = 0; function<auto (uint, uint, uint) -> int16> inputPoll = {&Emulator::Platform::inputPoll, platform};
if(Model::SuperGameBoy()) inputPoll = {&SuperGameBoyInterface::inputPoll, superGameBoy};
button |= platform->inputPoll(0, 0, (uint)Input::Start) << 3; uint button = 0;
button |= platform->inputPoll(0, 0, (uint)Input::Select) << 2; button |= inputPoll(0, 0, (uint)Input::Start) << 3;
button |= platform->inputPoll(0, 0, (uint)Input::B) << 1; button |= inputPoll(0, 0, (uint)Input::Select) << 2;
button |= platform->inputPoll(0, 0, (uint)Input::A) << 0; button |= inputPoll(0, 0, (uint)Input::B) << 1;
button |= inputPoll(0, 0, (uint)Input::A) << 0;
dpad |= platform->inputPoll(0, 0, (uint)Input::Down) << 3; uint dpad = 0;
dpad |= platform->inputPoll(0, 0, (uint)Input::Up) << 2; dpad |= inputPoll(0, 0, (uint)Input::Down) << 3;
dpad |= platform->inputPoll(0, 0, (uint)Input::Left) << 1; dpad |= inputPoll(0, 0, (uint)Input::Up) << 2;
dpad |= platform->inputPoll(0, 0, (uint)Input::Right) << 0; dpad |= inputPoll(0, 0, (uint)Input::Left) << 1;
dpad |= inputPoll(0, 0, (uint)Input::Right) << 0;
if(system.revision() != System::Revision::SuperGameBoy) { if(!Model::SuperGameBoy()) {
//D-pad pivot makes it impossible to press opposing directions at the same time //D-pad pivot makes it impossible to press opposing directions at the same time
//however, Super Game Boy BIOS is able to set these bits together //however, Super Game Boy BIOS is able to set these bits together
if(dpad & 4) dpad &= ~8; //disallow up+down if(dpad & 4) dpad &= ~8; //disallow up+down
@ -145,7 +148,7 @@ auto CPU::writeIO(uint16 addr, uint8 data) -> void {
if(addr == 0xff00) { //JOYP if(addr == 0xff00) { //JOYP
status.p15 = data & 0x20; status.p15 = data & 0x20;
status.p14 = data & 0x10; status.p14 = data & 0x10;
//interface->joypWrite(status.p15, status.p14); if(Model::SuperGameBoy()) superGameBoy->joypWrite(status.p15, status.p14);
return; return;
} }

View File

@ -21,7 +21,7 @@ auto CPU::step(uint clocks) -> void {
synchronize(apu); synchronize(apu);
} }
if(system.sgb()) { if(Model::SuperGameBoy()) {
system._clocksExecuted += clocks; system._clocksExecuted += clocks;
scheduler.exit(Scheduler::Event::Step); scheduler.exit(Scheduler::Event::Step);
} }

View File

@ -29,6 +29,12 @@ namespace GameBoy {
} }
}; };
struct Model {
inline static auto GameBoy() -> bool;
inline static auto GameBoyColor() -> bool;
inline static auto SuperGameBoy() -> bool;
};
#include <gb/memory/memory.hpp> #include <gb/memory/memory.hpp>
#include <gb/system/system.hpp> #include <gb/system/system.hpp>
#include <gb/cartridge/cartridge.hpp> #include <gb/cartridge/cartridge.hpp>

View File

@ -3,49 +3,7 @@ GameBoyColorInterface::GameBoyColorInterface() {
information.name = "Game Boy Color"; information.name = "Game Boy Color";
information.overscan = false; information.overscan = false;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::GameBoyColor, "Game Boy Color", "gb"}); media.append({ID::GameBoyColor, "Game Boy Color", "gb"});
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
hardwarePort.devices.append(device);
}
ports.append(move(hardwarePort));
}
auto GameBoyColorInterface::manifest() -> string {
return cartridge.manifest();
}
auto GameBoyColorInterface::title() -> string {
return cartridge.title();
}
auto GameBoyColorInterface::videoSize() -> VideoSize {
return {160, 144};
}
auto GameBoyColorInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 160;
uint h = 144;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto GameBoyColorInterface::videoFrequency() -> double {
return 4194304.0 / (154.0 * 456.0);
} }
auto GameBoyColorInterface::videoColors() -> uint32 { auto GameBoyColorInterface::videoColors() -> uint32 {
@ -73,77 +31,7 @@ auto GameBoyColorInterface::videoColor(uint32 color) -> uint64 {
return R << 32 | G << 16 | B << 0; return R << 32 | G << 16 | B << 0;
} }
auto GameBoyColorInterface::audioFrequency() -> double {
return 4194304.0 / 2.0;
}
auto GameBoyColorInterface::loaded() -> bool {
return system.loaded();
}
auto GameBoyColorInterface::sha256() -> string {
return cartridge.sha256();
}
auto GameBoyColorInterface::load(uint id) -> bool { auto GameBoyColorInterface::load(uint id) -> bool {
if(id == ID::GameBoyColor) return system.load(this, System::Revision::GameBoyColor); if(id == ID::GameBoyColor) return system.load(this, System::Model::GameBoyColor);
return false;
}
auto GameBoyColorInterface::save() -> void {
system.save();
}
auto GameBoyColorInterface::unload() -> void {
save();
system.unload();
}
auto GameBoyColorInterface::power() -> void {
system.power();
}
auto GameBoyColorInterface::run() -> void {
system.run();
}
auto GameBoyColorInterface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto GameBoyColorInterface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto GameBoyColorInterface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto GameBoyColorInterface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto GameBoyColorInterface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto GameBoyColorInterface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false; return false;
} }

View File

@ -3,49 +3,7 @@ GameBoyInterface::GameBoyInterface() {
information.name = "Game Boy"; information.name = "Game Boy";
information.overscan = false; information.overscan = false;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::GameBoy, "Game Boy", "gb"}); media.append({ID::GameBoy, "Game Boy", "gb"});
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
hardwarePort.devices.append(device);
}
ports.append(move(hardwarePort));
}
auto GameBoyInterface::manifest() -> string {
return cartridge.manifest();
}
auto GameBoyInterface::title() -> string {
return cartridge.title();
}
auto GameBoyInterface::videoSize() -> VideoSize {
return {160, 144};
}
auto GameBoyInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 160;
uint h = 144;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto GameBoyInterface::videoFrequency() -> double {
return 4194304.0 / (154.0 * 456.0);
} }
auto GameBoyInterface::videoColors() -> uint32 { auto GameBoyInterface::videoColors() -> uint32 {
@ -88,77 +46,7 @@ auto GameBoyInterface::videoColor(uint32 color) -> uint64 {
} }
} }
auto GameBoyInterface::audioFrequency() -> double {
return 4194304.0 / 2.0;
}
auto GameBoyInterface::loaded() -> bool {
return system.loaded();
}
auto GameBoyInterface::sha256() -> string {
return cartridge.sha256();
}
auto GameBoyInterface::load(uint id) -> bool { auto GameBoyInterface::load(uint id) -> bool {
if(id == ID::GameBoy) return system.load(this, System::Revision::GameBoy); if(id == ID::GameBoy) return system.load(this, System::Model::GameBoy);
return false;
}
auto GameBoyInterface::save() -> void {
system.save();
}
auto GameBoyInterface::unload() -> void {
save();
system.unload();
}
auto GameBoyInterface::power() -> void {
system.power();
}
auto GameBoyInterface::run() -> void {
system.run();
}
auto GameBoyInterface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto GameBoyInterface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto GameBoyInterface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto GameBoyInterface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto GameBoyInterface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto GameBoyInterface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false; return false;
} }

View File

@ -2,8 +2,123 @@
namespace GameBoy { namespace GameBoy {
SuperGameBoyInterface* superGameBoy = nullptr;
Settings settings; Settings settings;
#include "game-boy.cpp" #include "game-boy.cpp"
#include "game-boy-color.cpp" #include "game-boy-color.cpp"
Interface::Interface() {
information.capability.states = true;
information.capability.cheats = true;
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
hardwarePort.devices.append(device);
}
ports.append(move(hardwarePort));
}
auto Interface::manifest() -> string {
return cartridge.manifest();
}
auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::videoSize() -> VideoSize {
return {160, 144};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 160;
uint h = 144;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 4'194'304.0 / (154.0 * 456.0);
}
auto Interface::audioFrequency() -> double {
return 4'194'304.0 / 2.0;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::power() -> void {
system.power();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto Interface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}
} }

View File

@ -17,97 +17,74 @@ struct ID {
};}; };};
}; };
struct GameBoyInterface : Emulator::Interface { struct Interface : Emulator::Interface {
Interface();
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto sha256() -> string override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
};
struct GameBoyInterface : Interface {
using Emulator::Interface::load; using Emulator::Interface::load;
GameBoyInterface(); GameBoyInterface();
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override; auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override; auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override; auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
}; };
struct GameBoyColorInterface : Emulator::Interface { struct GameBoyColorInterface : Interface {
using Emulator::Interface::load; using Emulator::Interface::load;
GameBoyColorInterface(); GameBoyColorInterface();
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override; auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override; auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override; auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
}; };
/* struct SuperGameBoyInterface {
struct Interface : Emulator::Interface { virtual auto audioSample(const double* samples, uint channels) -> void = 0;
//Super Game Boy bindings virtual auto inputPoll(uint port, uint device, uint id) -> int16 = 0;
struct Hook {
virtual auto lcdScanline() -> void {}
virtual auto lcdOutput(uint2 color) -> void {}
virtual auto joypWrite(bool p15, bool p14) -> void {}
};
Hook* hook = nullptr;
auto lcdScanline() -> void; virtual auto lcdScanline() -> void = 0;
auto lcdOutput(uint2 color) -> void; virtual auto lcdOutput(uint2 color) -> void = 0;
auto joypWrite(bool p15, bool p14) -> void; virtual auto joypWrite(bool p15, bool p14) -> void = 0;
}; };
*/
struct Settings { struct Settings {
bool blurEmulation = true; bool blurEmulation = true;
bool colorEmulation = true; bool colorEmulation = true;
}; };
extern SuperGameBoyInterface* superGameBoy;
extern Settings settings; extern Settings settings;
} }

View File

@ -78,7 +78,7 @@ auto PPU::runDMG() -> void {
uint32* output = screen + status.ly * 160 + px++; uint32* output = screen + status.ly * 160 + px++;
*output = color; *output = color;
//interface->lcdOutput(color); //Super Game Boy notification if(Model::SuperGameBoy()) superGameBoy->lcdOutput(color);
} }
auto PPU::runBackgroundDMG() -> void { auto PPU::runBackgroundDMG() -> void {

View File

@ -142,7 +142,7 @@ auto PPU::writeIO(uint16 addr, uint8 data) -> void {
//hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ //hardware bug: writes to STAT on DMG,SGB during vblank triggers STAT IRQ
//note: this behavior isn't entirely correct; more research is needed ... //note: this behavior isn't entirely correct; more research is needed ...
if(!system.cgb() && status.mode == 1) { if(!Model::GameBoyColor() && status.mode == 1) {
cpu.raise(CPU::Interrupt::Stat); cpu.raise(CPU::Interrupt::Stat);
} }

View File

@ -16,7 +16,7 @@ auto PPU::Enter() -> void {
auto PPU::main() -> void { auto PPU::main() -> void {
status.lx = 0; status.lx = 0;
//interface->lcdScanline(); //Super Game Boy notification if(Model::SuperGameBoy()) superGameBoy->lcdScanline();
if(status.ly <= 143) { if(status.ly <= 143) {
mode(2); mode(2);
@ -71,7 +71,7 @@ auto PPU::coincidence() -> bool {
} }
auto PPU::refresh() -> void { auto PPU::refresh() -> void {
if(!system.sgb()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144); if(!Model::SuperGameBoy()) Emulator::video.refresh(screen, 160 * sizeof(uint32), 160, 144);
} }
auto PPU::step(uint clocks) -> void { auto PPU::step(uint clocks) -> void {
@ -109,7 +109,7 @@ auto PPU::hflip(uint data) const -> uint {
auto PPU::power() -> void { auto PPU::power() -> void {
create(Enter, 4 * 1024 * 1024); create(Enter, 4 * 1024 * 1024);
if(system.cgb()) { if(Model::GameBoyColor()) {
scanline = {&PPU::scanlineCGB, this}; scanline = {&PPU::scanlineCGB, this};
run = {&PPU::runCGB, this}; run = {&PPU::runCGB, this};
} else { } else {
@ -133,7 +133,7 @@ auto PPU::power() -> void {
bus.mmio[0xff4a] = this; //WY bus.mmio[0xff4a] = this; //WY
bus.mmio[0xff4b] = this; //WX bus.mmio[0xff4b] = this; //WX
if(system.cgb()) { if(Model::GameBoyColor()) {
bus.mmio[0xff4f] = this; //VBK bus.mmio[0xff4f] = this; //VBK
bus.mmio[0xff68] = this; //BGPI bus.mmio[0xff68] = this; //BGPI
bus.mmio[0xff69] = this; //BGPD bus.mmio[0xff69] = this; //BGPD

View File

@ -22,26 +22,49 @@ auto System::init() -> void {
assert(interface != nullptr); assert(interface != nullptr);
} }
auto System::load(Emulator::Interface* interface, Revision revision) -> bool { auto System::load(Emulator::Interface* interface, Model model_, maybe<uint> systemID) -> bool {
_revision = revision; _model = model_;
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) { if(model() == Model::GameBoy) {
information.manifest = fp->reads(); if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
} else return false; information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest); auto document = BML::unserialize(information.manifest);
string path = "system/cpu/rom/name"; if(auto name = document["system/cpu/rom/name"].text()) {
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name"; if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
fp->read(bootROM.dmg, 256);
if(auto name = document[path].text()) { }
if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
if(revision == Revision::GameBoy) fp->read(bootROM.dmg, 256);
if(revision == Revision::SuperGameBoy) fp->read(bootROM.sgb, 256);
if(revision == Revision::GameBoyColor) fp->read(bootROM.cgb, 2048);
} }
} }
if(!cartridge.load(revision)) return false; if(model() == Model::GameBoyColor) {
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
if(auto name = document["system/cpu/rom/name"].text()) {
if(auto fp = platform->open(ID::System, name, File::Read, File::Required)) {
fp->read(bootROM.cgb, 2048);
}
}
}
if(model() == Model::SuperGameBoy) {
if(auto fp = platform->open(systemID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
auto document = BML::unserialize(information.manifest);
if(auto name = document["board/icd2/rom/name"].text()) {
if(auto fp = platform->open(systemID(), name, File::Read, File::Required)) {
fp->read(bootROM.sgb, 256);
}
}
}
if(!cartridge.load()) return false;
serializeInit(); serializeInit();
this->interface = interface; this->interface = interface;
return _loaded = true; return _loaded = true;
@ -59,7 +82,7 @@ auto System::unload() -> void {
} }
auto System::power() -> void { auto System::power() -> void {
if(!system.sgb()) { if(model() != Model::SuperGameBoy) {
Emulator::video.reset(); Emulator::video.reset();
Emulator::video.setInterface(interface); Emulator::video.setInterface(interface);
configureVideoPalette(); configureVideoPalette();

View File

@ -3,25 +3,21 @@ enum class Input : uint {
}; };
struct System { struct System {
enum class Revision : uint { enum class Model : uint {
GameBoy, GameBoy,
SuperGameBoy,
GameBoyColor, GameBoyColor,
SuperGameBoy,
}; };
auto loaded() const -> bool { return _loaded; } inline auto loaded() const -> bool { return _loaded; }
auto revision() const -> Revision { return _revision; } inline auto model() const -> Model { return _model; }
auto clocksExecuted() const -> uint { return _clocksExecuted; } inline auto clocksExecuted() const -> uint { return _clocksExecuted; }
inline auto dmg() const { return _revision == Revision::GameBoy; }
inline auto sgb() const { return _revision == Revision::SuperGameBoy; }
inline auto cgb() const { return _revision == Revision::GameBoyColor; }
auto run() -> void; auto run() -> void;
auto runToSave() -> void; auto runToSave() -> void;
auto init() -> void; auto init() -> void;
auto load(Emulator::Interface*, Revision) -> bool; auto load(Emulator::Interface*, Model, maybe<uint> = nothing) -> bool;
auto save() -> void; auto save() -> void;
auto unload() -> void; auto unload() -> void;
auto power() -> void; auto power() -> void;
@ -51,7 +47,7 @@ struct System {
} information; } information;
bool _loaded = false; bool _loaded = false;
Revision _revision = Revision::GameBoy; Model _model = Model::GameBoy;
uint _serializeSize = 0; uint _serializeSize = 0;
uint _clocksExecuted = 0; uint _clocksExecuted = 0;
}; };
@ -59,3 +55,7 @@ struct System {
#include <gb/interface/interface.hpp> #include <gb/interface/interface.hpp>
extern System system; extern System system;
auto Model::GameBoy() -> bool { return system.model() == System::Model::GameBoy; }
auto Model::GameBoyColor() -> bool { return system.model() == System::Model::GameBoyColor; }
auto Model::SuperGameBoy() -> bool { return system.model() == System::Model::SuperGameBoy; }

View File

@ -1,9 +1,9 @@
auto System::configureVideoPalette() -> void { auto System::configureVideoPalette() -> void {
if(sgb()) return; if(model() == Model::SuperGameBoy) return;
Emulator::video.setPalette(); Emulator::video.setPalette();
} }
auto System::configureVideoEffects() -> void { auto System::configureVideoEffects() -> void {
if(sgb()) return; if(model() == Model::SuperGameBoy) return;
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation); Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
} }

View File

@ -101,6 +101,7 @@ auto Interface::save() -> void {
} }
auto Interface::unload() -> void { auto Interface::unload() -> void {
save();
system.unload(); system.unload();
} }

View File

@ -3,9 +3,6 @@ GameGearInterface::GameGearInterface() {
information.name = "Game Gear"; information.name = "Game Gear";
information.overscan = false; information.overscan = false;
information.capability.states = false;
information.capability.cheats = false;
media.append({ID::GameGear, "Game Gear", "gg"}); media.append({ID::GameGear, "Game Gear", "gg"});
Port hardware{ID::Port::Hardware, "Hardware"}; Port hardware{ID::Port::Hardware, "Hardware"};
@ -24,14 +21,6 @@ GameGearInterface::GameGearInterface() {
ports.append(move(hardware)); ports.append(move(hardware));
} }
auto GameGearInterface::manifest() -> string {
return cartridge.manifest();
}
auto GameGearInterface::title() -> string {
return cartridge.title();
}
auto GameGearInterface::videoSize() -> VideoSize { auto GameGearInterface::videoSize() -> VideoSize {
return {160, 144}; return {160, 144};
} }
@ -63,55 +52,7 @@ auto GameGearInterface::videoColor(uint32 color) -> uint64 {
return r << 32 | g << 16 | b << 0; return r << 32 | g << 16 | b << 0;
} }
auto GameGearInterface::audioFrequency() -> double {
return 44'100.0;
}
auto GameGearInterface::loaded() -> bool {
return system.loaded();
}
auto GameGearInterface::load(uint id) -> bool { auto GameGearInterface::load(uint id) -> bool {
if(id == ID::GameGear) return system.load(this, Model::GameGear); if(id == ID::GameGear) return system.load(this, Model::GameGear);
return false; return false;
} }
auto GameGearInterface::save() -> void {
system.save();
}
auto GameGearInterface::unload() -> void {
system.unload();
}
auto GameGearInterface::connect(uint port, uint device) -> void {
peripherals.connect(port, device);
}
auto GameGearInterface::power() -> void {
system.power();
}
auto GameGearInterface::run() -> void {
system.run();
}
auto GameGearInterface::serialize() -> serializer {
return {};
}
auto GameGearInterface::unserialize(serializer& s) -> bool {
return false;
}
auto GameGearInterface::cap(const string& name) -> bool {
return false;
}
auto GameGearInterface::get(const string& name) -> any {
return {};
}
auto GameGearInterface::set(const string& name, const any& value) -> bool {
return false;
}

View File

@ -6,4 +6,66 @@ Settings settings;
#include "master-system.cpp" #include "master-system.cpp"
#include "game-gear.cpp" #include "game-gear.cpp"
Interface::Interface() {
information.capability.states = false;
information.capability.cheats = false;
}
auto Interface::manifest() -> string {
return cartridge.manifest();
}
auto Interface::title() -> string {
return cartridge.title();
}
auto Interface::audioFrequency() -> double {
return 44'100.0; //todo: not correct
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::connect(uint port, uint device) -> void {
peripherals.connect(port, device);
}
auto Interface::power() -> void {
system.power();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
return {};
}
auto Interface::unserialize(serializer& s) -> bool {
return false;
}
auto Interface::cap(const string& name) -> bool {
return false;
}
auto Interface::get(const string& name) -> any {
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
return false;
}
} }

View File

@ -21,24 +21,15 @@ struct ID {
};}; };};
}; };
struct MasterSystemInterface : Emulator::Interface { struct Interface : Emulator::Interface {
using Emulator::Interface::load; Interface();
MasterSystemInterface();
auto manifest() -> string override; auto manifest() -> string override;
auto title() -> string override; auto title() -> string override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override; auto audioFrequency() -> double override;
auto loaded() -> bool override; auto loaded() -> bool override;
auto load(uint id) -> bool override;
auto save() -> void override; auto save() -> void override;
auto unload() -> void override; auto unload() -> void override;
@ -54,13 +45,10 @@ struct MasterSystemInterface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override; auto set(const string& name, const any& value) -> bool override;
}; };
struct GameGearInterface : Emulator::Interface { struct MasterSystemInterface : Interface {
using Emulator::Interface::load; using Emulator::Interface::load;
GameGearInterface(); MasterSystemInterface();
auto manifest() -> string override;
auto title() -> string override;
auto videoSize() -> VideoSize override; auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
@ -68,23 +56,21 @@ struct GameGearInterface : Emulator::Interface {
auto videoColors() -> uint32 override; auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override; auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto load(uint id) -> bool override; auto load(uint id) -> bool override;
auto save() -> void override; };
auto unload() -> void override;
auto connect(uint port, uint device) -> void override; struct GameGearInterface : Interface {
auto power() -> void override; using Emulator::Interface::load;
auto run() -> void override;
auto serialize() -> serializer override; GameGearInterface();
auto unserialize(serializer&) -> bool override;
auto cap(const string& name) -> bool override; auto videoSize() -> VideoSize override;
auto get(const string& name) -> any override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto set(const string& name, const any& value) -> bool override; auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto load(uint id) -> bool override;
}; };
struct Settings { struct Settings {

View File

@ -3,9 +3,6 @@ MasterSystemInterface::MasterSystemInterface() {
information.name = "Master System"; information.name = "Master System";
information.overscan = true; information.overscan = true;
information.capability.states = false;
information.capability.cheats = false;
media.append({ID::MasterSystem, "Master System", "ms"}); media.append({ID::MasterSystem, "Master System", "ms"});
Port hardware{ID::Port::Hardware, "Hardware"}; Port hardware{ID::Port::Hardware, "Hardware"};
@ -39,14 +36,6 @@ MasterSystemInterface::MasterSystemInterface() {
ports.append(move(controllerPort2)); ports.append(move(controllerPort2));
} }
auto MasterSystemInterface::manifest() -> string {
return cartridge.manifest();
}
auto MasterSystemInterface::title() -> string {
return cartridge.title();
}
auto MasterSystemInterface::videoSize() -> VideoSize { auto MasterSystemInterface::videoSize() -> VideoSize {
return {256, 240}; return {256, 240};
} }
@ -79,55 +68,7 @@ auto MasterSystemInterface::videoColor(uint32 color) -> uint64 {
return r << 32 | g << 16 | b << 0; return r << 32 | g << 16 | b << 0;
} }
auto MasterSystemInterface::audioFrequency() -> double {
return 44'100.0;
}
auto MasterSystemInterface::loaded() -> bool {
return system.loaded();
}
auto MasterSystemInterface::load(uint id) -> bool { auto MasterSystemInterface::load(uint id) -> bool {
if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem); if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem);
return false; return false;
} }
auto MasterSystemInterface::save() -> void {
system.save();
}
auto MasterSystemInterface::unload() -> void {
system.unload();
}
auto MasterSystemInterface::connect(uint port, uint device) -> void {
peripherals.connect(port, device);
}
auto MasterSystemInterface::power() -> void {
system.power();
}
auto MasterSystemInterface::run() -> void {
system.run();
}
auto MasterSystemInterface::serialize() -> serializer {
return {};
}
auto MasterSystemInterface::unserialize(serializer& s) -> bool {
return false;
}
auto MasterSystemInterface::cap(const string& name) -> bool {
return false;
}
auto MasterSystemInterface::get(const string& name) -> any {
return {};
}
auto MasterSystemInterface::set(const string& name, const any& value) -> bool {
return false;
}

View File

@ -38,6 +38,7 @@ auto Cartridge::load() -> bool {
} }
} }
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
return true; return true;
} }

View File

@ -3,6 +3,7 @@
namespace PCEngine { namespace PCEngine {
CPU cpu; CPU cpu;
#include "memory.cpp"
#include "io.cpp" #include "io.cpp"
#include "irq.cpp" #include "irq.cpp"
#include "timer.cpp" #include "timer.cpp"

View File

@ -4,10 +4,13 @@ struct CPU : Processor::HuC6280, Thread {
static auto Enter() -> void; static auto Enter() -> void;
auto main() -> void; auto main() -> void;
auto step(uint clocks) -> void override; auto step(uint clocks) -> void override;
auto power() -> void; auto power() -> void;
auto lastCycle() -> void override; auto lastCycle() -> void override;
//memory.cpp
auto load() -> void;
auto save() -> void;
//io.cpp //io.cpp
auto read(uint8 bank, uint13 addr) -> uint8 override; auto read(uint8 bank, uint13 addr) -> uint8 override;
auto write(uint8 bank, uint13 addr, uint8 data) -> void override; auto write(uint8 bank, uint13 addr, uint8 data) -> void override;
@ -19,22 +22,15 @@ struct CPU : Processor::HuC6280, Thread {
vector<Thread*> peripherals; vector<Thread*> peripherals;
struct IRQ { struct IRQ {
enum class Line : uint { External, VDC, Timer };
//irq.cpp //irq.cpp
auto pending() const -> bool; auto pending() const -> bool;
auto vector() const -> uint16; auto vector() const -> uint16;
auto poll() -> void; auto poll() -> void;
auto level(Line, bool = 1) -> void;
private: private:
bool disableExternal; bool disableExternal;
bool disableVDC; bool disableVDC;
bool disableTimer; bool disableTimer;
bool pendingExternal;
bool pendingVDC;
bool pendingTimer;
bool pendingIRQ; bool pendingIRQ;
uint16 pendingVector; uint16 pendingVector;
@ -43,6 +39,8 @@ struct CPU : Processor::HuC6280, Thread {
} irq; } irq;
struct Timer { struct Timer {
inline auto irqLine() const { return line; }
//timer.cpp //timer.cpp
auto start() -> void; auto start() -> void;
auto step(uint clocks) -> void; auto step(uint clocks) -> void;
@ -53,6 +51,8 @@ struct CPU : Processor::HuC6280, Thread {
uint7 value; uint7 value;
uint clock; uint clock;
bool line;
friend class CPU; friend class CPU;
} timer; } timer;
@ -62,6 +62,7 @@ struct CPU : Processor::HuC6280, Thread {
private: private:
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
}; };
extern CPU cpu; extern CPU cpu;

View File

@ -4,6 +4,11 @@ auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
return cartridge.read(bank << 13 | addr); return cartridge.read(bank << 13 | addr);
} }
//$f7 BRAM
if(bank == 0xf7) {
return bram[addr.bits(0,10)];
}
//$f8-fb RAM //$f8-fb RAM
if(bank >= 0xf8 && bank <= 0xfb) { if(bank >= 0xf8 && bank <= 0xfb) {
if(Model::PCEngine()) return ram[addr]; if(Model::PCEngine()) return ram[addr];
@ -35,12 +40,17 @@ auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
//$1000-13ff I/O //$1000-13ff I/O
if((addr & 0x1c00) == 0x1000) { if((addr & 0x1c00) == 0x1000) {
//note 1: Turbografx-16 games check this bit for region protection.
//yet PC Engine games do not. since we cannot tell the games apart,
//it's more compatible to always identify as a Turbografx-16 system.
//note 2: we state that the CD-ROM drive is present.
//this is so games can use its backup RAM for save data.
return ( return (
PCEngine::peripherals.controllerPort->readData() << 0 PCEngine::peripherals.controllerPort->readData() << 0
| 1 << 4 | 1 << 4
| 1 << 5 | 1 << 5
| 0 << 6 //device (0 = Turbografx-16; 1 = PC Engine) | 0 << 6 //device (0 = Turbografx-16; 1 = PC Engine)
| 1 << 7 //add-on (0 = CD-ROM; 1 = nothing) | 0 << 7 //add-on (0 = CD-ROM; 1 = nothing)
); );
} }
@ -64,10 +74,13 @@ auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
} }
if(addr.bits(0,1) == 3) { if(addr.bits(0,1) == 3) {
bool pendingExternal = 0;
bool pendingVDC = vdc0.irqLine() | vdc1.irqLine();
bool pendingTimer = timer.irqLine();
return ( return (
irq.pendingExternal << 0 pendingExternal << 0
| irq.pendingVDC << 1 | pendingVDC << 1
| irq.pendingTimer << 2 | pendingTimer << 2
| (io.mdr & 0xf8) | (io.mdr & 0xf8)
); );
} }
@ -93,6 +106,12 @@ auto CPU::write(uint8 bank, uint13 addr, uint8 data) -> void {
return cartridge.write(bank << 13 | addr, data); return cartridge.write(bank << 13 | addr, data);
} }
//$f7 BRAM
if(bank == 0xf7) {
bram[addr.bits(0,10)] = data;
return;
}
//$f8-fb RAM //$f8-fb RAM
if(bank >= 0xf8 && bank <= 0xfb) { if(bank >= 0xf8 && bank <= 0xfb) {
if(Model::PCEngine()) ram[addr] = data; if(Model::PCEngine()) ram[addr] = data;
@ -149,7 +168,7 @@ auto CPU::write(uint8 bank, uint13 addr, uint8 data) -> void {
} }
if(addr.bits(0,1) == 3) { if(addr.bits(0,1) == 3) {
irq.level(IRQ::Line::Timer, 0); timer.line = 0;
return; return;
} }
} }

View File

@ -10,28 +10,18 @@ auto CPU::IRQ::poll() -> void {
pendingIRQ = false; pendingIRQ = false;
if(cpu.r.p.i) return; if(cpu.r.p.i) return;
if(!disableExternal && pendingExternal) { if(0) { //external IRQ sources
pendingIRQ = true; pendingIRQ = !disableExternal;
pendingVector = 0xfff6; pendingVector = 0xfff6;
} else if(!disableVDC && pendingVDC) { }
pendingIRQ = true;
if(!disableVDC && (vdc0.irqLine() | vdc1.irqLine())) {
pendingIRQ = !disableVDC;
pendingVector = 0xfff8; pendingVector = 0xfff8;
} else if(!disableTimer && pendingTimer) { }
pendingIRQ = true;
if(cpu.timer.irqLine()) {
pendingIRQ = !disableTimer;
pendingVector = 0xfffa; pendingVector = 0xfffa;
} }
} }
auto CPU::IRQ::level(Line line, bool level) -> void {
if(line == Line::External) {
pendingExternal = level;
}
if(line == Line::VDC) {
pendingVDC = level;
}
if(line == Line::Timer) {
pendingTimer = level;
}
}

23
higan/pce/cpu/memory.cpp Normal file
View File

@ -0,0 +1,23 @@
//PC Engine HuCards lack save RAM on them due to the card size and cost savings.
//The PC Engine CD adds 2KB of backup RAM that most HuCard games can use for saves.
//However, all games must share this small amount of RAM.
//Since this is an emulator, we can make this process nicer by storing BRAM per-game.
//This does hard-code the save.ram name, rather than using a manifest file name.
//It also creates a save.ram file no matter what, even for games that don't save data.
//Unfortunately, we can't know in advance if a game supports BRAM saves or not.
//So because of this, we have to always create it.
//Thankfully, the file is very small so it should not prove to be a burden in practice.
auto CPU::load() -> void {
for(auto& byte : bram) byte = 0xff;
if(auto fp = platform->open(cartridge.pathID(), "save.ram", File::Read)) {
fp->read(bram, 0x800);
}
}
auto CPU::save() -> void {
if(auto fp = platform->open(cartridge.pathID(), "save.ram", File::Write)) {
fp->write(bram, 0x800);
}
}

View File

@ -10,7 +10,7 @@ auto CPU::Timer::step(uint clocks) -> void {
clock -= 1024; clock -= 1024;
if(!value--) { if(!value--) {
value = latch; value = latch;
cpu.irq.level(CPU::IRQ::Line::Timer, 1); line = 1;
} }
} }
} }

View File

@ -82,11 +82,16 @@ auto Interface::loaded() -> bool {
return system.loaded(); return system.loaded();
} }
auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::save() -> void { auto Interface::save() -> void {
system.save(); system.save();
} }
auto Interface::unload() -> void { auto Interface::unload() -> void {
save();
system.unload(); system.unload();
} }

View File

@ -18,8 +18,6 @@ struct ID {
}; };
struct Interface : Emulator::Interface { struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface(); Interface();
auto manifest() -> string override; auto manifest() -> string override;
@ -34,6 +32,7 @@ struct Interface : Emulator::Interface {
auto audioFrequency() -> double override; auto audioFrequency() -> double override;
auto loaded() -> bool override; auto loaded() -> bool override;
auto sha256() -> string override;
auto save() -> void override; auto save() -> void override;
auto unload() -> void override; auto unload() -> void override;

View File

@ -6,6 +6,6 @@ PCEngineInterface::PCEngineInterface() {
} }
auto PCEngineInterface::load(uint id) -> bool { auto PCEngineInterface::load(uint id) -> bool {
if(id == ID::PCEngine) return system.load(this, id); if(id == ID::PCEngine) return system.load(this, System::Model::PCEngine);
return false; return false;
} }

View File

@ -6,6 +6,6 @@ SuperGrafxInterface::SuperGrafxInterface() {
} }
auto SuperGrafxInterface::load(uint id) -> bool { auto SuperGrafxInterface::load(uint id) -> bool {
if(id == ID::SuperGrafx) return system.load(this, id); if(id == ID::SuperGrafx) return system.load(this, System::Model::SuperGrafx);
return false; return false;
} }

View File

@ -27,9 +27,8 @@ namespace PCEngine {
}; };
struct Model { struct Model {
inline static auto PCEngine() -> bool { return id == 1; } inline static auto PCEngine() -> bool;
inline static auto SuperGrafx() -> bool { return id == 2; } inline static auto SuperGrafx() -> bool;
static uint id;
}; };
#include <pce/controller/controller.hpp> #include <pce/controller/controller.hpp>

View File

@ -2,7 +2,6 @@
namespace PCEngine { namespace PCEngine {
uint Model::id;
System system; System system;
Scheduler scheduler; Scheduler scheduler;
#include "peripherals.cpp" #include "peripherals.cpp"
@ -11,9 +10,9 @@ auto System::run() -> void {
if(scheduler.enter() == Scheduler::Event::Frame) vce.refresh(); if(scheduler.enter() == Scheduler::Event::Frame) vce.refresh();
} }
auto System::load(Emulator::Interface* interface, uint id) -> bool { auto System::load(Emulator::Interface* interface, Model model) -> bool {
Model::id = id;
information = {}; information = {};
information.model = model;
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) { if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads(); information.manifest = fp->reads();
@ -22,6 +21,7 @@ auto System::load(Emulator::Interface* interface, uint id) -> bool {
auto document = BML::unserialize(information.manifest); auto document = BML::unserialize(information.manifest);
if(!cartridge.load()) return false; if(!cartridge.load()) return false;
cpu.load();
this->interface = interface; this->interface = interface;
information.colorburst = Emulator::Constants::Colorburst::NTSC; information.colorburst = Emulator::Constants::Colorburst::NTSC;
return information.loaded = true; return information.loaded = true;
@ -29,6 +29,7 @@ auto System::load(Emulator::Interface* interface, uint id) -> bool {
auto System::save() -> void { auto System::save() -> void {
cartridge.save(); cartridge.save();
cpu.save();
} }
auto System::unload() -> void { auto System::unload() -> void {
@ -47,8 +48,8 @@ auto System::power() -> void {
scheduler.reset(); scheduler.reset();
cartridge.power(); cartridge.power();
cpu.power(); cpu.power();
vpc.power();
vce.power(); vce.power();
vpc.power();
vdc0.power(); vdc0.power();
vdc1.power(); vdc1.power();
psg.power(); psg.power();

View File

@ -1,10 +1,13 @@
struct System { struct System {
auto loaded() const -> bool { return information.loaded; } enum class Model : uint { PCEngine, SuperGrafx };
auto colorburst() const -> double { return information.colorburst; }
inline auto loaded() const -> bool { return information.loaded; }
inline auto model() const -> Model { return information.model; }
inline auto colorburst() const -> double { return information.colorburst; }
auto run() -> void; auto run() -> void;
auto load(Emulator::Interface*, uint) -> bool; auto load(Emulator::Interface*, Model) -> bool;
auto save() -> void; auto save() -> void;
auto unload() -> void; auto unload() -> void;
@ -15,6 +18,7 @@ private:
struct Information { struct Information {
bool loaded = false; bool loaded = false;
Model model = Model::PCEngine;
string manifest; string manifest;
double colorburst = 0.0; double colorburst = 0.0;
} information; } information;
@ -30,3 +34,6 @@ struct Peripherals {
extern System system; extern System system;
extern Peripherals peripherals; extern Peripherals peripherals;
auto Model::PCEngine() -> bool { return system.model() == System::Model::PCEngine; }
auto Model::SuperGrafx() -> bool { return system.model() == System::Model::SuperGrafx; }

View File

@ -48,6 +48,8 @@ auto VCE::main() -> void {
auto VCE::step(uint clocks) -> void { auto VCE::step(uint clocks) -> void {
Thread::step(clocks); Thread::step(clocks);
synchronize(cpu); synchronize(cpu);
synchronize(vdc0);
synchronize(vdc1);
timing.hclock += clocks; timing.hclock += clocks;
} }

View File

@ -6,7 +6,7 @@ auto VDC::IRQ::poll() -> void {
pending |= pendingVblank; pending |= pendingVblank;
pending |= pendingTransferVRAM; pending |= pendingTransferVRAM;
pending |= pendingTransferSATB; pending |= pendingTransferSATB;
cpu.irq.level(CPU::IRQ::Line::VDC, pending); line = pending;
} }
auto VDC::IRQ::raise(Line line) -> void { auto VDC::IRQ::raise(Line line) -> void {
@ -44,6 +44,5 @@ auto VDC::IRQ::lower() -> void {
pendingVblank = false; pendingVblank = false;
pendingTransferVRAM = false; pendingTransferVRAM = false;
pendingTransferSATB = false; pendingTransferSATB = false;
line = false;
poll();
} }

View File

@ -91,6 +91,7 @@ auto VDC::frame() -> void {
auto VDC::step(uint clocks) -> void { auto VDC::step(uint clocks) -> void {
Thread::step(clocks); Thread::step(clocks);
synchronize(cpu); synchronize(cpu);
synchronize(vce);
timing.hclock += clocks; timing.hclock += clocks;
dma.step(clocks); dma.step(clocks);

View File

@ -2,6 +2,7 @@
struct VDC : Thread { struct VDC : Thread {
inline auto bus() const -> uint9 { return data; } inline auto bus() const -> uint9 { return data; }
inline auto irqLine() const -> bool { return irq.line; }
static auto Enter() -> void; static auto Enter() -> void;
auto main() -> void; auto main() -> void;
@ -98,6 +99,8 @@ private:
bool pendingVblank; bool pendingVblank;
bool pendingTransferVRAM; bool pendingTransferVRAM;
bool pendingTransferSATB; bool pendingTransferSATB;
bool line;
} irq; } irq;
struct DMA { struct DMA {

View File

@ -5,49 +5,48 @@ namespace PCEngine {
VPC vpc; VPC vpc;
auto VPC::bus(uint hclock) -> uint9 { auto VPC::bus(uint hclock) -> uint9 {
//bus values are direct CRAM entry indexes:
//d0-d3 => color (0 = neither background nor sprite)
//d4-d7 => palette
//d8 => source (0 = background; 1 = sprite)
auto bus0 = vdc0.bus(); auto bus0 = vdc0.bus();
auto bus1 = vdc1.bus(); auto bus1 = vdc1.bus();
auto color0 = bus0.bits(0,3); //note: timing may not be correct here; unable to test behavior
auto color1 = bus1.bits(0,3); //no official SuperGrafx games ever use partial screen-width windows
bool window0 = window[0] >= 64 && (window[0] - 64) >= hclock / 2;
bool window1 = window[1] >= 64 && (window[1] - 64) >= hclock / 2;
auto palette0 = bus0.bits(4,7); uint2 mode = !window0 << 0 | !window1 << 1;
auto palette1 = bus1.bits(4,7); auto enableVDC0 = settings[mode].enableVDC0 && bus0.bits(0,3);
auto enableVDC1 = settings[mode].enableVDC1 && bus1.bits(0,3);
auto mode0 = bus0.bit(8);
auto mode1 = bus1.bit(8);
//todo: I am unsure how the window coordinates relate to raw screen pixels ...
bool window0 = window[0] >= 64 && (window[0] - 64) >= hclock;
bool window1 = window[1] >= 64 && (window[1] - 64) >= hclock;
uint2 mode;
if(!window0 && !window1) mode = 1;
if( window0 && !window1) mode = 0;
if(!window0 && window1) mode = 3;
if( window0 && window1) mode = 2;
auto enableVDC0 = settings[mode].enableVDC0;
auto enableVDC1 = settings[mode].enableVDC1;
auto priority = settings[mode].priority; auto priority = settings[mode].priority;
//todo: I am unsure how this should work ...
if(priority == 0 || priority == 3) { if(priority == 0 || priority == 3) {
if(color1) return bus1; //SP0 > BG0 > SP1 > BG1
return bus0; if(bus0.bit(8) == 1 && enableVDC0) return bus0;
if(bus0.bit(8) == 0 && enableVDC0) return bus0;
if(bus1.bit(8) == 1 && enableVDC1) return bus1;
if(bus1.bit(8) == 0 && enableVDC1) return bus1;
} }
if(priority == 1) { if(priority == 1) {
if(color1) return bus1; //SP0 > SP1 > BG0 > BG1
return bus0; if(bus0.bit(8) == 1 && enableVDC0) return bus0;
if(bus1.bit(8) == 1 && enableVDC1) return bus1;
if(bus0.bit(8) == 0 && enableVDC0) return bus0;
if(bus1.bit(8) == 0 && enableVDC1) return bus1;
} }
if(priority == 2) { if(priority == 2) {
if(color1) return bus1; //BG0 > SP1 > BG1 > SP0
return bus0; if(bus0.bit(8) == 0 && enableVDC0) return bus0;
if(bus1.bit(8) == 1 && enableVDC1) return bus1;
if(bus1.bit(8) == 0 && enableVDC1) return bus1;
if(bus0.bit(8) == 1 && enableVDC0) return bus0;
} }
unreachable; return 0x000;
} }
auto VPC::power() -> void { auto VPC::power() -> void {
@ -68,7 +67,7 @@ auto VPC::power() -> void {
settings[3].priority = 0; settings[3].priority = 0;
window[0] = 0; window[0] = 0;
window[1] = 1; window[1] = 0;
select = 0; select = 0;
} }

View File

@ -16,7 +16,7 @@ private:
} settings[4]; } settings[4];
uint10 window[2]; uint10 window[2];
bool select; bool select;
}; };
extern VPC vpc; extern VPC vpc;

View File

@ -73,7 +73,6 @@ struct HuC6280 {
auto instruction_indirectYStore(uint8) -> void; auto instruction_indirectYStore(uint8) -> void;
auto instruction_memory(fp) -> void; auto instruction_memory(fp) -> void;
auto instruction_pull(uint8&) -> void; auto instruction_pull(uint8&) -> void;
auto instruction_pullP() -> void;
auto instruction_push(uint8) -> void; auto instruction_push(uint8) -> void;
auto instruction_set(bool&) -> void; auto instruction_set(bool&) -> void;
auto instruction_swap(uint8&, uint8&) -> void; auto instruction_swap(uint8&, uint8&) -> void;
@ -92,7 +91,7 @@ struct HuC6280 {
auto instruction_JMP_indirect(uint8 = 0) -> void; auto instruction_JMP_indirect(uint8 = 0) -> void;
auto instruction_JSR() -> void; auto instruction_JSR() -> void;
auto instruction_NOP() -> void; auto instruction_NOP() -> void;
auto instruction_PHP() -> void; auto instruction_PLP() -> void;
auto instruction_RMB(uint3) -> void; auto instruction_RMB(uint3) -> void;
auto instruction_RTI() -> void; auto instruction_RTI() -> void;
auto instruction_RTS() -> void; auto instruction_RTS() -> void;

View File

@ -68,7 +68,7 @@ U op(0x1b, NOP)
op(0x25, zeropageLoad, fp(AND), A) op(0x25, zeropageLoad, fp(AND), A)
op(0x26, zeropageModify, fp(ROL)) op(0x26, zeropageModify, fp(ROL))
op(0x27, RMB, 2) op(0x27, RMB, 2)
op(0x28, pullP) op(0x28, PLP)
op(0x29, immediate, fp(AND), A) op(0x29, immediate, fp(AND), A)
op(0x2a, implied, fp(ROL), A) op(0x2a, implied, fp(ROL), A)
U op(0x2b, NOP) U op(0x2b, NOP)

View File

@ -314,12 +314,8 @@ auto HuC6280::instruction_pull(uint8& data) -> void {
io(); io();
io(); io();
L data = pull(); L data = pull();
} Z = data == 0;
N = data.bit(7);
auto HuC6280::instruction_pullP() -> void {
io();
io();
L P = pull();
} }
auto HuC6280::instruction_push(uint8 data) -> void { auto HuC6280::instruction_push(uint8 data) -> void {
@ -453,6 +449,12 @@ auto HuC6280::instruction_NOP() -> void {
L io(); L io();
} }
auto HuC6280::instruction_PLP() -> void {
io();
io();
L P = pull();
}
auto HuC6280::instruction_RMB(uint3 index) -> void { auto HuC6280::instruction_RMB(uint3 index) -> void {
auto zeropage = operand(); auto zeropage = operand();
io(); io();

View File

@ -89,9 +89,9 @@ auto Cartridge::load() -> bool {
auto Cartridge::loadGameBoy() -> bool { auto Cartridge::loadGameBoy() -> bool {
#if defined(SFC_SUPERGAMEBOY) #if defined(SFC_SUPERGAMEBOY)
//invoked from ICD2::load() //invoked from ICD2::load()
information.sha256 = GameBoy::interface->sha256(); information.sha256 = GameBoy::cartridge.sha256();
information.manifest.gameBoy = GameBoy::interface->manifest(); information.manifest.gameBoy = GameBoy::cartridge.manifest();
information.title.gameBoy = GameBoy::interface->title(); information.title.gameBoy = GameBoy::cartridge.title();
loadGameBoy(BML::unserialize(information.manifest.gameBoy)); loadGameBoy(BML::unserialize(information.manifest.gameBoy));
return true; return true;
#endif #endif

View File

@ -6,6 +6,7 @@ ICD2 icd2;
#if defined(SFC_SUPERGAMEBOY) #if defined(SFC_SUPERGAMEBOY)
#include "platform.cpp"
#include "interface.cpp" #include "interface.cpp"
#include "io.cpp" #include "io.cpp"
#include "serialization.cpp" #include "serialization.cpp"
@ -34,18 +35,14 @@ auto ICD2::init() -> void {
} }
auto ICD2::load() -> bool { auto ICD2::load() -> bool {
bind = GameBoy::interface->bind; GameBoy::superGameBoy = this;
hook = GameBoy::interface->hook; GameBoy::system.load(&gameBoyInterface, GameBoy::System::Model::SuperGameBoy, cartridge.pathID());
GameBoy::interface->bind = this;
GameBoy::interface->hook = this;
GameBoy::interface->load(GameBoy::ID::SuperGameBoy);
return cartridge.loadGameBoy(); return cartridge.loadGameBoy();
} }
auto ICD2::unload() -> void { auto ICD2::unload() -> void {
GameBoy::interface->unload(); GameBoy::system.save();
GameBoy::interface->bind = bind; GameBoy::system.unload();
GameBoy::interface->hook = hook;
} }
auto ICD2::power() -> void { auto ICD2::power() -> void {
@ -78,7 +75,31 @@ auto ICD2::power() -> void {
} }
auto ICD2::reset() -> void { auto ICD2::reset() -> void {
//todo: same as power() but without re-creating the audio stream auto frequency = system.colorburst() * 6.0;
create(ICD2::Enter, frequency / 5);
r6003 = 0x00;
r6004 = 0xff;
r6005 = 0xff;
r6006 = 0xff;
r6007 = 0xff;
for(auto& r : r7000) r = 0x00;
mltReq = 0;
for(auto& n : output) n = 0xff;
readBank = 0;
readAddress = 0;
writeBank = 0;
writeAddress = 0;
packetSize = 0;
joypID = 3;
joyp15Lock = 0;
joyp14Lock = 0;
pulseLock = true;
GameBoy::system.init();
GameBoy::system.power();
} }
#endif #endif

View File

@ -1,6 +1,6 @@
#if defined(SFC_SUPERGAMEBOY) #if defined(SFC_SUPERGAMEBOY)
struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Thread { struct ICD2 : Emulator::Platform, GameBoy::SuperGameBoyInterface, Thread {
shared_pointer<Emulator::Stream> stream; shared_pointer<Emulator::Stream> stream;
static auto Enter() -> void; static auto Enter() -> void;
@ -12,18 +12,15 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Thread {
auto power() -> void; auto power() -> void;
auto reset() -> void; //software reset auto reset() -> void; //software reset
//platform.cpp
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint id) -> int16 override;
//interface.cpp //interface.cpp
auto lcdScanline() -> void override; auto lcdScanline() -> void override;
auto lcdOutput(uint2 color) -> void override; auto lcdOutput(uint2 color) -> void override;
auto joypWrite(bool p15, bool p14) -> void override; auto joypWrite(bool p15, bool p14) -> void override;
auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override;
auto load(uint id, string name, string type) -> maybe<uint> override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint id) -> int16 override;
//io.cpp //io.cpp
auto readIO(uint24 addr, uint8 data) -> uint8; auto readIO(uint24 addr, uint8 data) -> uint8;
auto writeIO(uint24 addr, uint8 data) -> void; auto writeIO(uint24 addr, uint8 data) -> void;
@ -34,9 +31,6 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Thread {
uint revision; uint revision;
private: private:
Emulator::Interface::Bind* bind = nullptr;
GameBoy::Interface::Hook* hook = nullptr;
struct Packet { struct Packet {
auto operator[](uint addr) -> uint8& { return data[addr & 15]; } auto operator[](uint addr) -> uint8& { return data[addr & 15]; }
uint8 data[16]; uint8 data[16];
@ -67,6 +61,8 @@ private:
uint readAddress; uint readAddress;
uint writeBank; uint writeBank;
uint writeAddress; uint writeAddress;
GameBoy::GameBoyInterface gameBoyInterface;
}; };
#else #else

View File

@ -84,46 +84,3 @@ auto ICD2::joypWrite(bool p15, bool p14) -> void {
if(++packetOffset < 16) return; if(++packetOffset < 16) return;
packetLock = true; packetLock = true;
} }
auto ICD2::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file {
//redirect system folder to cartridge folder:
//expects "GameBoy.sys"; but this would be "Super Famicom.sys"; redirect to "Super Game Boy.sfc/"
if(id == ID::System) id = cartridge.pathID();
return interface->open(id, name, mode, required);
}
auto ICD2::load(uint id, string name, string type) -> maybe<uint> {
return interface->load(id, name, type);
}
auto ICD2::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
}
auto ICD2::audioSample(const double* samples, uint channels) -> void {
stream->write(samples);
}
auto ICD2::inputPoll(uint port, uint device, uint id) -> int16 {
GameBoy::cpu.status.mltReq = joypID & mltReq;
uint data = 0x00;
switch(joypID & mltReq) {
case 0: data = ~r6004; break;
case 1: data = ~r6005; break;
case 2: data = ~r6006; break;
case 3: data = ~r6007; break;
}
switch((GameBoy::Input)id) {
case GameBoy::Input::Start: return (bool)(data & 0x80);
case GameBoy::Input::Select: return (bool)(data & 0x40);
case GameBoy::Input::B: return (bool)(data & 0x20);
case GameBoy::Input::A: return (bool)(data & 0x10);
case GameBoy::Input::Down: return (bool)(data & 0x08);
case GameBoy::Input::Up: return (bool)(data & 0x04);
case GameBoy::Input::Left: return (bool)(data & 0x02);
case GameBoy::Input::Right: return (bool)(data & 0x01);
}
return 0;
}

View File

@ -54,7 +54,7 @@ auto ICD2::writeIO(uint24 addr, uint8 data) -> void {
//d1,d0: 0 = frequency divider (clock rate adjust) //d1,d0: 0 = frequency divider (clock rate adjust)
if(addr == 0x6003) { if(addr == 0x6003) {
if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) { if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) {
reset(true); reset();
} }
auto frequency = system.colorburst() * 6.0; auto frequency = system.colorburst() * 6.0;
switch(data & 3) { switch(data & 3) {

View File

@ -0,0 +1,28 @@
auto ICD2::audioSample(const double* samples, uint channels) -> void {
stream->write(samples);
}
auto ICD2::inputPoll(uint port, uint device, uint id) -> int16 {
GameBoy::cpu.status.mltReq = joypID & mltReq;
uint data = 0x00;
switch(joypID & mltReq) {
case 0: data = ~r6004; break;
case 1: data = ~r6005; break;
case 2: data = ~r6006; break;
case 3: data = ~r6007; break;
}
switch((GameBoy::Input)id) {
case GameBoy::Input::Start: return (bool)(data & 0x80);
case GameBoy::Input::Select: return (bool)(data & 0x40);
case GameBoy::Input::B: return (bool)(data & 0x20);
case GameBoy::Input::A: return (bool)(data & 0x10);
case GameBoy::Input::Down: return (bool)(data & 0x08);
case GameBoy::Input::Up: return (bool)(data & 0x04);
case GameBoy::Input::Left: return (bool)(data & 0x02);
case GameBoy::Input::Right: return (bool)(data & 0x01);
}
return 0;
}

View File

@ -1,5 +1,5 @@
name := higan name := higan
#flags += -DSFC_SUPERGAMEBOY flags += -DSFC_SUPERGAMEBOY
include fc/GNUmakefile include fc/GNUmakefile
include sfc/GNUmakefile include sfc/GNUmakefile

View File

@ -6,4 +6,125 @@ Settings settings;
#include "wonderswan.cpp" #include "wonderswan.cpp"
#include "wonderswan-color.cpp" #include "wonderswan-color.cpp"
Interface::Interface() {
information.capability.states = true;
information.capability.cheats = true;
Port hardwareHorizontalPort{ID::Port::HardwareHorizontal, "Hardware - Horizontal"};
Port hardwareVerticalPort{ID::Port::HardwareVertical, "Hardware - Vertical"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Y1"});
device.inputs.append({0, "Y2"});
device.inputs.append({0, "Y3"});
device.inputs.append({0, "Y4"});
device.inputs.append({0, "X1"});
device.inputs.append({0, "X2"});
device.inputs.append({0, "X3"});
device.inputs.append({0, "X4"});
device.inputs.append({0, "B"});
device.inputs.append({0, "A"});
device.inputs.append({0, "Start"});
device.inputs.append({0, "Rotate"});
hardwareHorizontalPort.devices.append(device);
hardwareVerticalPort.devices.append(device);
}
ports.append(move(hardwareHorizontalPort));
ports.append(move(hardwareVerticalPort));
}
auto Interface::manifest() -> string {
return cartridge.information.manifest;
}
auto Interface::title() -> string {
return cartridge.information.title;
}
auto Interface::videoSize() -> VideoSize {
return {224, 224};
}
auto Interface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 224;
uint h = 224;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto Interface::videoFrequency() -> double {
return 3'072'000.0 / (159.0 * 256.0); //~75.47hz
}
auto Interface::audioFrequency() -> double {
return 3'072'000.0;
}
auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::sha256() -> string {
return cartridge.information.sha256;
}
auto Interface::save() -> void {
system.save();
}
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::power() -> void {
system.power();
}
auto Interface::run() -> void {
system.run();
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto Interface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto Interface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto Interface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto Interface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto Interface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}
} }

View File

@ -17,10 +17,8 @@ struct ID {
};}; };};
}; };
struct WonderSwanInterface : Emulator::Interface { struct Interface : Emulator::Interface {
using Emulator::Interface::load; Interface();
WonderSwanInterface();
auto manifest() -> string override; auto manifest() -> string override;
auto title() -> string override; auto title() -> string override;
@ -28,14 +26,11 @@ struct WonderSwanInterface : Emulator::Interface {
auto videoSize() -> VideoSize override; auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override; auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override; auto videoFrequency() -> double override;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double override; auto audioFrequency() -> double override;
auto loaded() -> bool override; auto loaded() -> bool override;
auto sha256() -> string override; auto sha256() -> string override;
auto load(uint id) -> bool override;
auto save() -> void override; auto save() -> void override;
auto unload() -> void override; auto unload() -> void override;
@ -52,39 +47,26 @@ struct WonderSwanInterface : Emulator::Interface {
auto set(const string& name, const any& value) -> bool override; auto set(const string& name, const any& value) -> bool override;
}; };
struct WonderSwanColorInterface : Emulator::Interface { struct WonderSwanInterface : Interface {
using Emulator::Interface::load;
WonderSwanInterface();
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto load(uint id) -> bool override;
};
struct WonderSwanColorInterface : Interface {
using Emulator::Interface::load; using Emulator::Interface::load;
WonderSwanColorInterface(); WonderSwanColorInterface();
auto manifest() -> string override; auto videoColors() -> uint32 override;
auto title() -> string override; auto videoColor(uint32 color) -> uint64 override;
auto videoSize() -> VideoSize override;
auto videoSize(uint width, uint height, bool arc) -> VideoSize override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double override;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override; auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void override;
auto run() -> void override;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const string_vector&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
}; };
struct Settings { struct Settings {

View File

@ -3,56 +3,7 @@ WonderSwanColorInterface::WonderSwanColorInterface() {
information.name = "WonderSwan Color"; information.name = "WonderSwan Color";
information.overscan = false; information.overscan = false;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc"}); media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc"});
Port hardwareHorizontalPort{ID::Port::HardwareHorizontal, "Hardware - Horizontal"};
Port hardwareVerticalPort{ID::Port::HardwareVertical, "Hardware - Vertical"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Y1"});
device.inputs.append({0, "Y2"});
device.inputs.append({0, "Y3"});
device.inputs.append({0, "Y4"});
device.inputs.append({0, "X1"});
device.inputs.append({0, "X2"});
device.inputs.append({0, "X3"});
device.inputs.append({0, "X4"});
device.inputs.append({0, "B"});
device.inputs.append({0, "A"});
device.inputs.append({0, "Start"});
device.inputs.append({0, "Rotate"});
hardwareHorizontalPort.devices.append(device);
hardwareVerticalPort.devices.append(device);
}
ports.append(move(hardwareHorizontalPort));
ports.append(move(hardwareVerticalPort));
}
auto WonderSwanColorInterface::manifest() -> string {
return cartridge.information.manifest;
}
auto WonderSwanColorInterface::title() -> string {
return cartridge.information.title;
}
auto WonderSwanColorInterface::videoSize() -> VideoSize {
return {224, 224};
}
auto WonderSwanColorInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 224;
uint h = 224;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto WonderSwanColorInterface::videoFrequency() -> double {
return 3072000.0 / (159.0 * 256.0); //~75.47hz
} }
auto WonderSwanColorInterface::videoColors() -> uint32 { auto WonderSwanColorInterface::videoColors() -> uint32 {
@ -80,77 +31,7 @@ auto WonderSwanColorInterface::videoColor(uint32 color) -> uint64 {
return R << 32 | G << 16 | B << 0; return R << 32 | G << 16 | B << 0;
} }
auto WonderSwanColorInterface::audioFrequency() -> double {
return 3072000.0;
}
auto WonderSwanColorInterface::loaded() -> bool {
return system.loaded();
}
auto WonderSwanColorInterface::sha256() -> string {
return cartridge.information.sha256;
}
auto WonderSwanColorInterface::load(uint id) -> bool { auto WonderSwanColorInterface::load(uint id) -> bool {
if(id == ID::WonderSwanColor) return system.load(this, Model::WonderSwanColor); if(id == ID::WonderSwanColor) return system.load(this, Model::WonderSwanColor);
return false; return false;
} }
auto WonderSwanColorInterface::save() -> void {
system.save();
}
auto WonderSwanColorInterface::unload() -> void {
save();
system.unload();
}
auto WonderSwanColorInterface::power() -> void {
system.power();
}
auto WonderSwanColorInterface::run() -> void {
system.run();
}
auto WonderSwanColorInterface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto WonderSwanColorInterface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto WonderSwanColorInterface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto WonderSwanColorInterface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto WonderSwanColorInterface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto WonderSwanColorInterface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}

View File

@ -3,57 +3,11 @@ WonderSwanInterface::WonderSwanInterface() {
information.name = "WonderSwan"; information.name = "WonderSwan";
information.overscan = false; information.overscan = false;
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::WonderSwan, "WonderSwan", "ws"}); media.append({ID::WonderSwan, "WonderSwan", "ws"});
Port hardwareHorizontalPort{ID::Port::HardwareHorizontal, "Hardware - Horizontal"};
Port hardwareVerticalPort{ID::Port::HardwareVertical, "Hardware - Vertical"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Y1"});
device.inputs.append({0, "Y2"});
device.inputs.append({0, "Y3"});
device.inputs.append({0, "Y4"});
device.inputs.append({0, "X1"});
device.inputs.append({0, "X2"});
device.inputs.append({0, "X3"});
device.inputs.append({0, "X4"});
device.inputs.append({0, "B"});
device.inputs.append({0, "A"});
device.inputs.append({0, "Start"});
device.inputs.append({0, "Rotate"});
hardwareHorizontalPort.devices.append(device);
hardwareVerticalPort.devices.append(device);
}
ports.append(move(hardwareHorizontalPort));
ports.append(move(hardwareVerticalPort));
} }
auto WonderSwanInterface::manifest() -> string { //todo: this should be generating grayscale colors
return cartridge.information.manifest; //instead, the PPU is selecting grayscale colors from the color palette
}
auto WonderSwanInterface::title() -> string {
return cartridge.information.title;
}
auto WonderSwanInterface::videoSize() -> VideoSize {
return {224, 224};
}
auto WonderSwanInterface::videoSize(uint width, uint height, bool arc) -> VideoSize {
uint w = 224;
uint h = 224;
uint m = min(width / w, height / h);
return {w * m, h * m};
}
auto WonderSwanInterface::videoFrequency() -> double {
return 3072000.0 / (159.0 * 256.0); //~75.47hz
}
auto WonderSwanInterface::videoColors() -> uint32 { auto WonderSwanInterface::videoColors() -> uint32 {
return 1 << 12; return 1 << 12;
@ -80,77 +34,7 @@ auto WonderSwanInterface::videoColor(uint32 color) -> uint64 {
return R << 32 | G << 16 | B << 0; return R << 32 | G << 16 | B << 0;
} }
auto WonderSwanInterface::audioFrequency() -> double {
return 3072000.0;
}
auto WonderSwanInterface::loaded() -> bool {
return system.loaded();
}
auto WonderSwanInterface::sha256() -> string {
return cartridge.information.sha256;
}
auto WonderSwanInterface::load(uint id) -> bool { auto WonderSwanInterface::load(uint id) -> bool {
if(id == ID::WonderSwan) return system.load(this, Model::WonderSwan); if(id == ID::WonderSwan) return system.load(this, Model::WonderSwan);
return false; return false;
} }
auto WonderSwanInterface::save() -> void {
system.save();
}
auto WonderSwanInterface::unload() -> void {
save();
system.unload();
}
auto WonderSwanInterface::power() -> void {
system.power();
}
auto WonderSwanInterface::run() -> void {
system.run();
}
auto WonderSwanInterface::serialize() -> serializer {
system.runToSave();
return system.serialize();
}
auto WonderSwanInterface::unserialize(serializer& s) -> bool {
return system.unserialize(s);
}
auto WonderSwanInterface::cheatSet(const string_vector& list) -> void {
cheat.assign(list);
}
auto WonderSwanInterface::cap(const string& name) -> bool {
if(name == "Blur Emulation") return true;
if(name == "Color Emulation") return true;
return false;
}
auto WonderSwanInterface::get(const string& name) -> any {
if(name == "Blur Emulation") return settings.blurEmulation;
if(name == "Color Emulation") return settings.colorEmulation;
return {};
}
auto WonderSwanInterface::set(const string& name, const any& value) -> bool {
if(name == "Blur Emulation" && value.is<bool>()) {
settings.blurEmulation = value.get<bool>();
system.configureVideoEffects();
return true;
}
if(name == "Color Emulation" && value.is<bool>()) {
settings.colorEmulation = value.get<bool>();
system.configureVideoPalette();
return true;
}
return false;
}