mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
186f008574
commit
ee7662a8be
|
@ -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/";
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ auto Interface::save() -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Interface::unload() -> void {
|
auto Interface::unload() -> void {
|
||||||
|
save();
|
||||||
system.unload();
|
system.unload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ auto Cartridge::load() -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
information.sha256 = Hash::SHA256(rom.data, rom.size).digest();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue