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 {
|
||||
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 License = "GPLv3";
|
||||
static const string Website = "http://byuu.org/";
|
||||
|
|
|
@ -25,11 +25,11 @@ auto APU::main() -> void {
|
|||
hipass(sequencer.left, sequencer.leftBias);
|
||||
hipass(sequencer.right, sequencer.rightBias);
|
||||
|
||||
if(!system.sgb()) {
|
||||
if(!Model::SuperGameBoy()) {
|
||||
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||
} else {
|
||||
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||
//interface->audioSample(samples, 2);
|
||||
superGameBoy->audioSample(samples, 2);
|
||||
}
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
|
@ -63,7 +63,7 @@ auto APU::hipass(int16& sample, int64& bias) -> void {
|
|||
|
||||
auto APU::power() -> void {
|
||||
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;
|
||||
|
||||
square1.power();
|
||||
|
@ -91,7 +91,7 @@ auto APU::readIO(uint16 addr) -> uint8 {
|
|||
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
if(!sequencer.enable) {
|
||||
bool valid = addr == 0xff26; //NR52
|
||||
if(!system.cgb()) {
|
||||
if(!Model::GameBoyColor()) {
|
||||
//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 == 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) {
|
||||
//power(bool) resets length counters when true (eg for CGB only)
|
||||
apu.square1.power(system.cgb());
|
||||
apu.square2.power(system.cgb());
|
||||
apu.wave.power(system.cgb());
|
||||
apu.noise.power(system.cgb());
|
||||
apu.square1.power(Model::GameBoyColor());
|
||||
apu.square2.power(Model::GameBoyColor());
|
||||
apu.wave.power(Model::GameBoyColor());
|
||||
apu.noise.power(Model::GameBoyColor());
|
||||
power();
|
||||
} else {
|
||||
apu.phase = 0;
|
||||
|
|
|
@ -47,7 +47,7 @@ auto APU::Wave::read(uint16 addr) -> uint8 {
|
|||
|
||||
if(addr >= 0xff30 && addr <= 0xff3f) {
|
||||
if(enable) {
|
||||
if(!system.cgb() && !patternHold) return 0xff;
|
||||
if(!Model::GameBoyColor() && !patternHold) return 0xff;
|
||||
return pattern[patternOffset >> 1];
|
||||
} else {
|
||||
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);
|
||||
|
||||
if(data.bit(7)) {
|
||||
if(!system.cgb() && patternHold) {
|
||||
if(!Model::GameBoyColor() && patternHold) {
|
||||
//DMG,SGB trigger while channel is being read corrupts wave RAM
|
||||
if((patternOffset >> 1) <= 3) {
|
||||
//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(enable) {
|
||||
if(!system.cgb() && !patternHold) return;
|
||||
if(!Model::GameBoyColor() && !patternHold) return;
|
||||
pattern[patternOffset >> 1] = data;
|
||||
} else {
|
||||
pattern[addr & 15] = data;
|
||||
|
|
|
@ -14,25 +14,25 @@ namespace GameBoy {
|
|||
#include "serialization.cpp"
|
||||
Cartridge cartridge;
|
||||
|
||||
auto Cartridge::load(System::Revision revision) -> bool {
|
||||
information = Information();
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
|
||||
switch(revision) {
|
||||
case System::Revision::GameBoy:
|
||||
if(Model::GameBoy()) {
|
||||
if(auto pathID = platform->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::SuperGameBoy:
|
||||
if(auto pathID = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = pathID();
|
||||
} else return false;
|
||||
break;
|
||||
case System::Revision::GameBoyColor:
|
||||
}
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
if(auto pathID = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
information.pathID = pathID();
|
||||
} 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)) {
|
||||
|
@ -136,13 +136,11 @@ auto Cartridge::readIO(uint16 addr) -> uint8 {
|
|||
|
||||
if(bootromEnable) {
|
||||
const uint8* data = nullptr;
|
||||
switch(system.revision()) { default:
|
||||
case System::Revision::GameBoy: data = system.bootROM.dmg; break;
|
||||
case System::Revision::SuperGameBoy: data = system.bootROM.sgb; break;
|
||||
case System::Revision::GameBoyColor: data = system.bootROM.cgb; break;
|
||||
}
|
||||
if(Model::GameBoy()) data = system.bootROM.dmg;
|
||||
if(Model::GameBoyColor()) data = system.bootROM.cgb;
|
||||
if(Model::SuperGameBoy()) data = system.bootROM.sgb;
|
||||
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);
|
||||
|
|
|
@ -4,7 +4,7 @@ struct Cartridge : MMIO {
|
|||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
|
||||
auto load(System::Revision revision) -> bool;
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
|
|
|
@ -102,7 +102,7 @@ auto CPU::power() -> void {
|
|||
bus.mmio[0xff0f] = this; //IF
|
||||
bus.mmio[0xffff] = this; //IE
|
||||
|
||||
if(system.cgb()) {
|
||||
if(Model::GameBoyColor()) {
|
||||
bus.mmio[0xff4d] = this; //KEY1
|
||||
bus.mmio[0xff51] = this; //HDMA1
|
||||
bus.mmio[0xff52] = this; //HDMA2
|
||||
|
|
|
@ -6,19 +6,22 @@ auto CPU::wramAddress(uint16 addr) const -> uint {
|
|||
}
|
||||
|
||||
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;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::Select) << 2;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::B) << 1;
|
||||
button |= platform->inputPoll(0, 0, (uint)Input::A) << 0;
|
||||
uint button = 0;
|
||||
button |= inputPoll(0, 0, (uint)Input::Start) << 3;
|
||||
button |= inputPoll(0, 0, (uint)Input::Select) << 2;
|
||||
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;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Up) << 2;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Left) << 1;
|
||||
dpad |= platform->inputPoll(0, 0, (uint)Input::Right) << 0;
|
||||
uint dpad = 0;
|
||||
dpad |= inputPoll(0, 0, (uint)Input::Down) << 3;
|
||||
dpad |= inputPoll(0, 0, (uint)Input::Up) << 2;
|
||||
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
|
||||
//however, Super Game Boy BIOS is able to set these bits together
|
||||
if(dpad & 4) dpad &= ~8; //disallow up+down
|
||||
|
@ -145,7 +148,7 @@ auto CPU::writeIO(uint16 addr, uint8 data) -> void {
|
|||
if(addr == 0xff00) { //JOYP
|
||||
status.p15 = data & 0x20;
|
||||
status.p14 = data & 0x10;
|
||||
//interface->joypWrite(status.p15, status.p14);
|
||||
if(Model::SuperGameBoy()) superGameBoy->joypWrite(status.p15, status.p14);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ auto CPU::step(uint clocks) -> void {
|
|||
synchronize(apu);
|
||||
}
|
||||
|
||||
if(system.sgb()) {
|
||||
if(Model::SuperGameBoy()) {
|
||||
system._clocksExecuted += clocks;
|
||||
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/system/system.hpp>
|
||||
#include <gb/cartridge/cartridge.hpp>
|
||||
|
|
|
@ -3,49 +3,7 @@ GameBoyColorInterface::GameBoyColorInterface() {
|
|||
information.name = "Game Boy Color";
|
||||
information.overscan = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
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 {
|
||||
|
@ -73,77 +31,7 @@ auto GameBoyColorInterface::videoColor(uint32 color) -> uint64 {
|
|||
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 {
|
||||
if(id == ID::GameBoyColor) return system.load(this, System::Revision::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;
|
||||
}
|
||||
|
||||
if(id == ID::GameBoyColor) return system.load(this, System::Model::GameBoyColor);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,49 +3,7 @@ GameBoyInterface::GameBoyInterface() {
|
|||
information.name = "Game Boy";
|
||||
information.overscan = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
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 {
|
||||
|
@ -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 {
|
||||
if(id == ID::GameBoy) return system.load(this, System::Revision::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;
|
||||
}
|
||||
|
||||
if(id == ID::GameBoy) return system.load(this, System::Model::GameBoy);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -2,8 +2,123 @@
|
|||
|
||||
namespace GameBoy {
|
||||
|
||||
SuperGameBoyInterface* superGameBoy = nullptr;
|
||||
Settings settings;
|
||||
#include "game-boy.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;
|
||||
|
||||
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 videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string 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;
|
||||
|
||||
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 videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string 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 Interface : Emulator::Interface {
|
||||
//Super Game Boy bindings
|
||||
struct Hook {
|
||||
virtual auto lcdScanline() -> void {}
|
||||
virtual auto lcdOutput(uint2 color) -> void {}
|
||||
virtual auto joypWrite(bool p15, bool p14) -> void {}
|
||||
};
|
||||
Hook* hook = nullptr;
|
||||
struct SuperGameBoyInterface {
|
||||
virtual auto audioSample(const double* samples, uint channels) -> void = 0;
|
||||
virtual auto inputPoll(uint port, uint device, uint id) -> int16 = 0;
|
||||
|
||||
auto lcdScanline() -> void;
|
||||
auto lcdOutput(uint2 color) -> void;
|
||||
auto joypWrite(bool p15, bool p14) -> void;
|
||||
virtual auto lcdScanline() -> void = 0;
|
||||
virtual auto lcdOutput(uint2 color) -> void = 0;
|
||||
virtual auto joypWrite(bool p15, bool p14) -> void = 0;
|
||||
};
|
||||
*/
|
||||
|
||||
struct Settings {
|
||||
bool blurEmulation = true;
|
||||
bool colorEmulation = true;
|
||||
};
|
||||
|
||||
extern SuperGameBoyInterface* superGameBoy;
|
||||
extern Settings settings;
|
||||
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ auto PPU::runDMG() -> void {
|
|||
|
||||
uint32* output = screen + status.ly * 160 + px++;
|
||||
*output = color;
|
||||
//interface->lcdOutput(color); //Super Game Boy notification
|
||||
if(Model::SuperGameBoy()) superGameBoy->lcdOutput(color);
|
||||
}
|
||||
|
||||
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
|
||||
//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);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ auto PPU::Enter() -> void {
|
|||
|
||||
auto PPU::main() -> void {
|
||||
status.lx = 0;
|
||||
//interface->lcdScanline(); //Super Game Boy notification
|
||||
if(Model::SuperGameBoy()) superGameBoy->lcdScanline();
|
||||
|
||||
if(status.ly <= 143) {
|
||||
mode(2);
|
||||
|
@ -71,7 +71,7 @@ auto PPU::coincidence() -> bool {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -109,7 +109,7 @@ auto PPU::hflip(uint data) const -> uint {
|
|||
auto PPU::power() -> void {
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
|
||||
if(system.cgb()) {
|
||||
if(Model::GameBoyColor()) {
|
||||
scanline = {&PPU::scanlineCGB, this};
|
||||
run = {&PPU::runCGB, this};
|
||||
} else {
|
||||
|
@ -133,7 +133,7 @@ auto PPU::power() -> void {
|
|||
bus.mmio[0xff4a] = this; //WY
|
||||
bus.mmio[0xff4b] = this; //WX
|
||||
|
||||
if(system.cgb()) {
|
||||
if(Model::GameBoyColor()) {
|
||||
bus.mmio[0xff4f] = this; //VBK
|
||||
bus.mmio[0xff68] = this; //BGPI
|
||||
bus.mmio[0xff69] = this; //BGPD
|
||||
|
|
|
@ -22,26 +22,49 @@ auto System::init() -> void {
|
|||
assert(interface != nullptr);
|
||||
}
|
||||
|
||||
auto System::load(Emulator::Interface* interface, Revision revision) -> bool {
|
||||
_revision = revision;
|
||||
auto System::load(Emulator::Interface* interface, Model model_, maybe<uint> systemID) -> bool {
|
||||
_model = model_;
|
||||
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
if(model() == Model::GameBoy) {
|
||||
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);
|
||||
string path = "system/cpu/rom/name";
|
||||
if(revision == Revision::SuperGameBoy) path = "board/icd2/rom/name";
|
||||
|
||||
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);
|
||||
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.dmg, 256);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
this->interface = interface;
|
||||
return _loaded = true;
|
||||
|
@ -59,7 +82,7 @@ auto System::unload() -> void {
|
|||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
if(!system.sgb()) {
|
||||
if(model() != Model::SuperGameBoy) {
|
||||
Emulator::video.reset();
|
||||
Emulator::video.setInterface(interface);
|
||||
configureVideoPalette();
|
||||
|
|
|
@ -3,25 +3,21 @@ enum class Input : uint {
|
|||
};
|
||||
|
||||
struct System {
|
||||
enum class Revision : uint {
|
||||
enum class Model : uint {
|
||||
GameBoy,
|
||||
SuperGameBoy,
|
||||
GameBoyColor,
|
||||
SuperGameBoy,
|
||||
};
|
||||
|
||||
auto loaded() const -> bool { return _loaded; }
|
||||
auto revision() const -> Revision { return _revision; }
|
||||
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; }
|
||||
inline auto loaded() const -> bool { return _loaded; }
|
||||
inline auto model() const -> Model { return _model; }
|
||||
inline auto clocksExecuted() const -> uint { return _clocksExecuted; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto load(Emulator::Interface*, Revision) -> bool;
|
||||
auto load(Emulator::Interface*, Model, maybe<uint> = nothing) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
@ -51,7 +47,7 @@ struct System {
|
|||
} information;
|
||||
|
||||
bool _loaded = false;
|
||||
Revision _revision = Revision::GameBoy;
|
||||
Model _model = Model::GameBoy;
|
||||
uint _serializeSize = 0;
|
||||
uint _clocksExecuted = 0;
|
||||
};
|
||||
|
@ -59,3 +55,7 @@ struct System {
|
|||
#include <gb/interface/interface.hpp>
|
||||
|
||||
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 {
|
||||
if(sgb()) return;
|
||||
if(model() == Model::SuperGameBoy) return;
|
||||
Emulator::video.setPalette();
|
||||
}
|
||||
|
||||
auto System::configureVideoEffects() -> void {
|
||||
if(sgb()) return;
|
||||
if(model() == Model::SuperGameBoy) return;
|
||||
Emulator::video.setEffect(Emulator::Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
}
|
||||
|
|
|
@ -101,6 +101,7 @@ auto Interface::save() -> void {
|
|||
}
|
||||
|
||||
auto Interface::unload() -> void {
|
||||
save();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,6 @@ GameGearInterface::GameGearInterface() {
|
|||
information.name = "Game Gear";
|
||||
information.overscan = false;
|
||||
|
||||
information.capability.states = false;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::GameGear, "Game Gear", "gg"});
|
||||
|
||||
Port hardware{ID::Port::Hardware, "Hardware"};
|
||||
|
@ -24,14 +21,6 @@ GameGearInterface::GameGearInterface() {
|
|||
ports.append(move(hardware));
|
||||
}
|
||||
|
||||
auto GameGearInterface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto GameGearInterface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto GameGearInterface::videoSize() -> VideoSize {
|
||||
return {160, 144};
|
||||
}
|
||||
|
@ -63,55 +52,7 @@ auto GameGearInterface::videoColor(uint32 color) -> uint64 {
|
|||
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 {
|
||||
if(id == ID::GameGear) return system.load(this, Model::GameGear);
|
||||
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 "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 {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
MasterSystemInterface();
|
||||
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 videoColors() -> uint32 override;
|
||||
auto videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
|
@ -54,13 +45,10 @@ struct MasterSystemInterface : Emulator::Interface {
|
|||
auto set(const string& name, const any& value) -> bool override;
|
||||
};
|
||||
|
||||
struct GameGearInterface : Emulator::Interface {
|
||||
struct MasterSystemInterface : Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
GameGearInterface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
MasterSystemInterface();
|
||||
|
||||
auto videoSize() -> 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 videoColor(uint32 color) -> uint64 override;
|
||||
|
||||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto load(uint id) -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
};
|
||||
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto power() -> void override;
|
||||
auto run() -> void override;
|
||||
struct GameGearInterface : Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
GameGearInterface();
|
||||
|
||||
auto cap(const string& name) -> bool override;
|
||||
auto get(const string& name) -> any override;
|
||||
auto set(const string& name, const any& value) -> bool 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 load(uint id) -> bool override;
|
||||
};
|
||||
|
||||
struct Settings {
|
||||
|
|
|
@ -3,9 +3,6 @@ MasterSystemInterface::MasterSystemInterface() {
|
|||
information.name = "Master System";
|
||||
information.overscan = true;
|
||||
|
||||
information.capability.states = false;
|
||||
information.capability.cheats = false;
|
||||
|
||||
media.append({ID::MasterSystem, "Master System", "ms"});
|
||||
|
||||
Port hardware{ID::Port::Hardware, "Hardware"};
|
||||
|
@ -39,14 +36,6 @@ MasterSystemInterface::MasterSystemInterface() {
|
|||
ports.append(move(controllerPort2));
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::manifest() -> string {
|
||||
return cartridge.manifest();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto MasterSystemInterface::videoSize() -> VideoSize {
|
||||
return {256, 240};
|
||||
}
|
||||
|
@ -79,55 +68,7 @@ auto MasterSystemInterface::videoColor(uint32 color) -> uint64 {
|
|||
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 {
|
||||
if(id == ID::MasterSystem) return system.load(this, Model::MasterSystem);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace PCEngine {
|
||||
|
||||
CPU cpu;
|
||||
#include "memory.cpp"
|
||||
#include "io.cpp"
|
||||
#include "irq.cpp"
|
||||
#include "timer.cpp"
|
||||
|
|
|
@ -4,10 +4,13 @@ struct CPU : Processor::HuC6280, Thread {
|
|||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void override;
|
||||
|
||||
auto power() -> void;
|
||||
auto lastCycle() -> void override;
|
||||
|
||||
//memory.cpp
|
||||
auto load() -> void;
|
||||
auto save() -> void;
|
||||
|
||||
//io.cpp
|
||||
auto read(uint8 bank, uint13 addr) -> uint8 override;
|
||||
auto write(uint8 bank, uint13 addr, uint8 data) -> void override;
|
||||
|
@ -19,22 +22,15 @@ struct CPU : Processor::HuC6280, Thread {
|
|||
vector<Thread*> peripherals;
|
||||
|
||||
struct IRQ {
|
||||
enum class Line : uint { External, VDC, Timer };
|
||||
|
||||
//irq.cpp
|
||||
auto pending() const -> bool;
|
||||
auto vector() const -> uint16;
|
||||
auto poll() -> void;
|
||||
auto level(Line, bool = 1) -> void;
|
||||
|
||||
private:
|
||||
bool disableExternal;
|
||||
bool disableVDC;
|
||||
bool disableTimer;
|
||||
|
||||
bool pendingExternal;
|
||||
bool pendingVDC;
|
||||
bool pendingTimer;
|
||||
bool disableExternal;
|
||||
bool disableVDC;
|
||||
bool disableTimer;
|
||||
|
||||
bool pendingIRQ;
|
||||
uint16 pendingVector;
|
||||
|
@ -43,6 +39,8 @@ struct CPU : Processor::HuC6280, Thread {
|
|||
} irq;
|
||||
|
||||
struct Timer {
|
||||
inline auto irqLine() const { return line; }
|
||||
|
||||
//timer.cpp
|
||||
auto start() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
@ -53,6 +51,8 @@ struct CPU : Processor::HuC6280, Thread {
|
|||
uint7 value;
|
||||
uint clock;
|
||||
|
||||
bool line;
|
||||
|
||||
friend class CPU;
|
||||
} timer;
|
||||
|
||||
|
@ -62,6 +62,7 @@ struct CPU : Processor::HuC6280, Thread {
|
|||
|
||||
private:
|
||||
uint8 ram[0x8000]; //PC Engine = 8KB, SuperGrafx = 32KB
|
||||
uint8 bram[0x800]; //PC Engine CD-ROM Backup RAM = 2KB
|
||||
};
|
||||
|
||||
extern CPU cpu;
|
||||
|
|
|
@ -4,6 +4,11 @@ auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
|
|||
return cartridge.read(bank << 13 | addr);
|
||||
}
|
||||
|
||||
//$f7 BRAM
|
||||
if(bank == 0xf7) {
|
||||
return bram[addr.bits(0,10)];
|
||||
}
|
||||
|
||||
//$f8-fb RAM
|
||||
if(bank >= 0xf8 && bank <= 0xfb) {
|
||||
if(Model::PCEngine()) return ram[addr];
|
||||
|
@ -35,12 +40,17 @@ auto CPU::read(uint8 bank, uint13 addr) -> uint8 {
|
|||
|
||||
//$1000-13ff I/O
|
||||
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 (
|
||||
PCEngine::peripherals.controllerPort->readData() << 0
|
||||
| 1 << 4
|
||||
| 1 << 5
|
||||
| 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) {
|
||||
bool pendingExternal = 0;
|
||||
bool pendingVDC = vdc0.irqLine() | vdc1.irqLine();
|
||||
bool pendingTimer = timer.irqLine();
|
||||
return (
|
||||
irq.pendingExternal << 0
|
||||
| irq.pendingVDC << 1
|
||||
| irq.pendingTimer << 2
|
||||
pendingExternal << 0
|
||||
| pendingVDC << 1
|
||||
| pendingTimer << 2
|
||||
| (io.mdr & 0xf8)
|
||||
);
|
||||
}
|
||||
|
@ -93,6 +106,12 @@ auto CPU::write(uint8 bank, uint13 addr, uint8 data) -> void {
|
|||
return cartridge.write(bank << 13 | addr, data);
|
||||
}
|
||||
|
||||
//$f7 BRAM
|
||||
if(bank == 0xf7) {
|
||||
bram[addr.bits(0,10)] = data;
|
||||
return;
|
||||
}
|
||||
|
||||
//$f8-fb RAM
|
||||
if(bank >= 0xf8 && bank <= 0xfb) {
|
||||
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) {
|
||||
irq.level(IRQ::Line::Timer, 0);
|
||||
timer.line = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,28 +10,18 @@ auto CPU::IRQ::poll() -> void {
|
|||
pendingIRQ = false;
|
||||
if(cpu.r.p.i) return;
|
||||
|
||||
if(!disableExternal && pendingExternal) {
|
||||
pendingIRQ = true;
|
||||
if(0) { //external IRQ sources
|
||||
pendingIRQ = !disableExternal;
|
||||
pendingVector = 0xfff6;
|
||||
} else if(!disableVDC && pendingVDC) {
|
||||
pendingIRQ = true;
|
||||
}
|
||||
|
||||
if(!disableVDC && (vdc0.irqLine() | vdc1.irqLine())) {
|
||||
pendingIRQ = !disableVDC;
|
||||
pendingVector = 0xfff8;
|
||||
} else if(!disableTimer && pendingTimer) {
|
||||
pendingIRQ = true;
|
||||
}
|
||||
|
||||
if(cpu.timer.irqLine()) {
|
||||
pendingIRQ = !disableTimer;
|
||||
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;
|
||||
if(!value--) {
|
||||
value = latch;
|
||||
cpu.irq.level(CPU::IRQ::Line::Timer, 1);
|
||||
line = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,11 +82,16 @@ 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@ struct ID {
|
|||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
|
@ -34,6 +32,7 @@ struct Interface : Emulator::Interface {
|
|||
auto audioFrequency() -> double override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto sha256() -> string override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
|
|
|
@ -6,6 +6,6 @@ PCEngineInterface::PCEngineInterface() {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,6 @@ SuperGrafxInterface::SuperGrafxInterface() {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -27,9 +27,8 @@ namespace PCEngine {
|
|||
};
|
||||
|
||||
struct Model {
|
||||
inline static auto PCEngine() -> bool { return id == 1; }
|
||||
inline static auto SuperGrafx() -> bool { return id == 2; }
|
||||
static uint id;
|
||||
inline static auto PCEngine() -> bool;
|
||||
inline static auto SuperGrafx() -> bool;
|
||||
};
|
||||
|
||||
#include <pce/controller/controller.hpp>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace PCEngine {
|
||||
|
||||
uint Model::id;
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
#include "peripherals.cpp"
|
||||
|
@ -11,9 +10,9 @@ auto System::run() -> void {
|
|||
if(scheduler.enter() == Scheduler::Event::Frame) vce.refresh();
|
||||
}
|
||||
|
||||
auto System::load(Emulator::Interface* interface, uint id) -> bool {
|
||||
Model::id = id;
|
||||
auto System::load(Emulator::Interface* interface, Model model) -> bool {
|
||||
information = {};
|
||||
information.model = model;
|
||||
|
||||
if(auto fp = platform->open(ID::System, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
|
@ -22,6 +21,7 @@ auto System::load(Emulator::Interface* interface, uint id) -> bool {
|
|||
auto document = BML::unserialize(information.manifest);
|
||||
if(!cartridge.load()) return false;
|
||||
|
||||
cpu.load();
|
||||
this->interface = interface;
|
||||
information.colorburst = Emulator::Constants::Colorburst::NTSC;
|
||||
return information.loaded = true;
|
||||
|
@ -29,6 +29,7 @@ auto System::load(Emulator::Interface* interface, uint id) -> bool {
|
|||
|
||||
auto System::save() -> void {
|
||||
cartridge.save();
|
||||
cpu.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
|
@ -47,8 +48,8 @@ auto System::power() -> void {
|
|||
scheduler.reset();
|
||||
cartridge.power();
|
||||
cpu.power();
|
||||
vpc.power();
|
||||
vce.power();
|
||||
vpc.power();
|
||||
vdc0.power();
|
||||
vdc1.power();
|
||||
psg.power();
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
struct System {
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
auto colorburst() const -> double { return information.colorburst; }
|
||||
enum class Model : uint { PCEngine, SuperGrafx };
|
||||
|
||||
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 load(Emulator::Interface*, uint) -> bool;
|
||||
auto load(Emulator::Interface*, Model) -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
|
@ -15,6 +18,7 @@ private:
|
|||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
Model model = Model::PCEngine;
|
||||
string manifest;
|
||||
double colorburst = 0.0;
|
||||
} information;
|
||||
|
@ -30,3 +34,6 @@ struct Peripherals {
|
|||
|
||||
extern System system;
|
||||
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 {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
synchronize(vdc0);
|
||||
synchronize(vdc1);
|
||||
|
||||
timing.hclock += clocks;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ auto VDC::IRQ::poll() -> void {
|
|||
pending |= pendingVblank;
|
||||
pending |= pendingTransferVRAM;
|
||||
pending |= pendingTransferSATB;
|
||||
cpu.irq.level(CPU::IRQ::Line::VDC, pending);
|
||||
line = pending;
|
||||
}
|
||||
|
||||
auto VDC::IRQ::raise(Line line) -> void {
|
||||
|
@ -44,6 +44,5 @@ auto VDC::IRQ::lower() -> void {
|
|||
pendingVblank = false;
|
||||
pendingTransferVRAM = false;
|
||||
pendingTransferSATB = false;
|
||||
|
||||
poll();
|
||||
line = false;
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ auto VDC::frame() -> void {
|
|||
auto VDC::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
synchronize(vce);
|
||||
|
||||
timing.hclock += clocks;
|
||||
dma.step(clocks);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
struct VDC : Thread {
|
||||
inline auto bus() const -> uint9 { return data; }
|
||||
inline auto irqLine() const -> bool { return irq.line; }
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
|
@ -98,6 +99,8 @@ private:
|
|||
bool pendingVblank;
|
||||
bool pendingTransferVRAM;
|
||||
bool pendingTransferSATB;
|
||||
|
||||
bool line;
|
||||
} irq;
|
||||
|
||||
struct DMA {
|
||||
|
|
|
@ -5,49 +5,48 @@ namespace PCEngine {
|
|||
VPC vpc;
|
||||
|
||||
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 bus1 = vdc1.bus();
|
||||
|
||||
auto color0 = bus0.bits(0,3);
|
||||
auto color1 = bus1.bits(0,3);
|
||||
//note: timing may not be correct here; unable to test behavior
|
||||
//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);
|
||||
auto palette1 = bus1.bits(4,7);
|
||||
|
||||
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;
|
||||
uint2 mode = !window0 << 0 | !window1 << 1;
|
||||
auto enableVDC0 = settings[mode].enableVDC0 && bus0.bits(0,3);
|
||||
auto enableVDC1 = settings[mode].enableVDC1 && bus1.bits(0,3);
|
||||
auto priority = settings[mode].priority;
|
||||
|
||||
//todo: I am unsure how this should work ...
|
||||
if(priority == 0 || priority == 3) {
|
||||
if(color1) return bus1;
|
||||
return bus0;
|
||||
//SP0 > BG0 > SP1 > BG1
|
||||
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(color1) return bus1;
|
||||
return bus0;
|
||||
//SP0 > SP1 > BG0 > BG1
|
||||
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(color1) return bus1;
|
||||
return bus0;
|
||||
//BG0 > SP1 > BG1 > SP0
|
||||
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 {
|
||||
|
@ -68,7 +67,7 @@ auto VPC::power() -> void {
|
|||
settings[3].priority = 0;
|
||||
|
||||
window[0] = 0;
|
||||
window[1] = 1;
|
||||
window[1] = 0;
|
||||
|
||||
select = 0;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ private:
|
|||
} settings[4];
|
||||
|
||||
uint10 window[2];
|
||||
bool select;
|
||||
bool select;
|
||||
};
|
||||
|
||||
extern VPC vpc;
|
||||
|
|
|
@ -73,7 +73,6 @@ struct HuC6280 {
|
|||
auto instruction_indirectYStore(uint8) -> void;
|
||||
auto instruction_memory(fp) -> void;
|
||||
auto instruction_pull(uint8&) -> void;
|
||||
auto instruction_pullP() -> void;
|
||||
auto instruction_push(uint8) -> void;
|
||||
auto instruction_set(bool&) -> void;
|
||||
auto instruction_swap(uint8&, uint8&) -> void;
|
||||
|
@ -92,7 +91,7 @@ struct HuC6280 {
|
|||
auto instruction_JMP_indirect(uint8 = 0) -> void;
|
||||
auto instruction_JSR() -> void;
|
||||
auto instruction_NOP() -> void;
|
||||
auto instruction_PHP() -> void;
|
||||
auto instruction_PLP() -> void;
|
||||
auto instruction_RMB(uint3) -> void;
|
||||
auto instruction_RTI() -> void;
|
||||
auto instruction_RTS() -> void;
|
||||
|
|
|
@ -68,7 +68,7 @@ U op(0x1b, NOP)
|
|||
op(0x25, zeropageLoad, fp(AND), A)
|
||||
op(0x26, zeropageModify, fp(ROL))
|
||||
op(0x27, RMB, 2)
|
||||
op(0x28, pullP)
|
||||
op(0x28, PLP)
|
||||
op(0x29, immediate, fp(AND), A)
|
||||
op(0x2a, implied, fp(ROL), A)
|
||||
U op(0x2b, NOP)
|
||||
|
|
|
@ -314,12 +314,8 @@ auto HuC6280::instruction_pull(uint8& data) -> void {
|
|||
io();
|
||||
io();
|
||||
L data = pull();
|
||||
}
|
||||
|
||||
auto HuC6280::instruction_pullP() -> void {
|
||||
io();
|
||||
io();
|
||||
L P = pull();
|
||||
Z = data == 0;
|
||||
N = data.bit(7);
|
||||
}
|
||||
|
||||
auto HuC6280::instruction_push(uint8 data) -> void {
|
||||
|
@ -453,6 +449,12 @@ auto HuC6280::instruction_NOP() -> void {
|
|||
L io();
|
||||
}
|
||||
|
||||
auto HuC6280::instruction_PLP() -> void {
|
||||
io();
|
||||
io();
|
||||
L P = pull();
|
||||
}
|
||||
|
||||
auto HuC6280::instruction_RMB(uint3 index) -> void {
|
||||
auto zeropage = operand();
|
||||
io();
|
||||
|
|
|
@ -89,9 +89,9 @@ auto Cartridge::load() -> bool {
|
|||
auto Cartridge::loadGameBoy() -> bool {
|
||||
#if defined(SFC_SUPERGAMEBOY)
|
||||
//invoked from ICD2::load()
|
||||
information.sha256 = GameBoy::interface->sha256();
|
||||
information.manifest.gameBoy = GameBoy::interface->manifest();
|
||||
information.title.gameBoy = GameBoy::interface->title();
|
||||
information.sha256 = GameBoy::cartridge.sha256();
|
||||
information.manifest.gameBoy = GameBoy::cartridge.manifest();
|
||||
information.title.gameBoy = GameBoy::cartridge.title();
|
||||
loadGameBoy(BML::unserialize(information.manifest.gameBoy));
|
||||
return true;
|
||||
#endif
|
||||
|
|
|
@ -6,6 +6,7 @@ ICD2 icd2;
|
|||
|
||||
#if defined(SFC_SUPERGAMEBOY)
|
||||
|
||||
#include "platform.cpp"
|
||||
#include "interface.cpp"
|
||||
#include "io.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
@ -34,18 +35,14 @@ auto ICD2::init() -> void {
|
|||
}
|
||||
|
||||
auto ICD2::load() -> bool {
|
||||
bind = GameBoy::interface->bind;
|
||||
hook = GameBoy::interface->hook;
|
||||
GameBoy::interface->bind = this;
|
||||
GameBoy::interface->hook = this;
|
||||
GameBoy::interface->load(GameBoy::ID::SuperGameBoy);
|
||||
GameBoy::superGameBoy = this;
|
||||
GameBoy::system.load(&gameBoyInterface, GameBoy::System::Model::SuperGameBoy, cartridge.pathID());
|
||||
return cartridge.loadGameBoy();
|
||||
}
|
||||
|
||||
auto ICD2::unload() -> void {
|
||||
GameBoy::interface->unload();
|
||||
GameBoy::interface->bind = bind;
|
||||
GameBoy::interface->hook = hook;
|
||||
GameBoy::system.save();
|
||||
GameBoy::system.unload();
|
||||
}
|
||||
|
||||
auto ICD2::power() -> void {
|
||||
|
@ -78,7 +75,31 @@ auto ICD2::power() -> 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#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;
|
||||
|
||||
static auto Enter() -> void;
|
||||
|
@ -12,18 +12,15 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Thread {
|
|||
auto power() -> void;
|
||||
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
|
||||
auto lcdScanline() -> void override;
|
||||
auto lcdOutput(uint2 color) -> 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
|
||||
auto readIO(uint24 addr, uint8 data) -> uint8;
|
||||
auto writeIO(uint24 addr, uint8 data) -> void;
|
||||
|
@ -34,9 +31,6 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Thread {
|
|||
uint revision;
|
||||
|
||||
private:
|
||||
Emulator::Interface::Bind* bind = nullptr;
|
||||
GameBoy::Interface::Hook* hook = nullptr;
|
||||
|
||||
struct Packet {
|
||||
auto operator[](uint addr) -> uint8& { return data[addr & 15]; }
|
||||
uint8 data[16];
|
||||
|
@ -67,6 +61,8 @@ private:
|
|||
uint readAddress;
|
||||
uint writeBank;
|
||||
uint writeAddress;
|
||||
|
||||
GameBoy::GameBoyInterface gameBoyInterface;
|
||||
};
|
||||
|
||||
#else
|
||||
|
|
|
@ -84,46 +84,3 @@ auto ICD2::joypWrite(bool p15, bool p14) -> void {
|
|||
if(++packetOffset < 16) return;
|
||||
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)
|
||||
if(addr == 0x6003) {
|
||||
if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) {
|
||||
reset(true);
|
||||
reset();
|
||||
}
|
||||
auto frequency = system.colorburst() * 6.0;
|
||||
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
|
||||
#flags += -DSFC_SUPERGAMEBOY
|
||||
flags += -DSFC_SUPERGAMEBOY
|
||||
|
||||
include fc/GNUmakefile
|
||||
include sfc/GNUmakefile
|
||||
|
|
|
@ -6,4 +6,125 @@ Settings settings;
|
|||
#include "wonderswan.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 {
|
||||
using Emulator::Interface::load;
|
||||
|
||||
WonderSwanInterface();
|
||||
struct Interface : Emulator::Interface {
|
||||
Interface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
|
@ -28,14 +26,11 @@ struct WonderSwanInterface : Emulator::Interface {
|
|||
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 save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
|
@ -52,39 +47,26 @@ struct WonderSwanInterface : Emulator::Interface {
|
|||
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;
|
||||
|
||||
WonderSwanColorInterface();
|
||||
|
||||
auto manifest() -> string override;
|
||||
auto title() -> string override;
|
||||
auto videoColors() -> uint32 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 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 {
|
||||
|
|
|
@ -3,56 +3,7 @@ WonderSwanColorInterface::WonderSwanColorInterface() {
|
|||
information.name = "WonderSwan Color";
|
||||
information.overscan = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
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 {
|
||||
|
@ -80,77 +31,7 @@ auto WonderSwanColorInterface::videoColor(uint32 color) -> uint64 {
|
|||
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 {
|
||||
if(id == ID::WonderSwanColor) return system.load(this, Model::WonderSwanColor);
|
||||
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.overscan = false;
|
||||
|
||||
information.capability.states = true;
|
||||
information.capability.cheats = true;
|
||||
|
||||
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 {
|
||||
return cartridge.information.manifest;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
//todo: this should be generating grayscale colors
|
||||
//instead, the PPU is selecting grayscale colors from the color palette
|
||||
|
||||
auto WonderSwanInterface::videoColors() -> uint32 {
|
||||
return 1 << 12;
|
||||
|
@ -80,77 +34,7 @@ auto WonderSwanInterface::videoColor(uint32 color) -> uint64 {
|
|||
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 {
|
||||
if(id == ID::WonderSwan) return system.load(this, Model::WonderSwan);
|
||||
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