mirror of
https://github.com/libretro/higan.git
synced 2025-02-21 09:20:47 +00:00
Update to v106r105 release.
byuu says: 8.5 hours today. I took too long to get on the PC after waking up, so I wore out a bit sooner than I wanted. I ported the Game Boy+Game Boy Color+Super Game Boy and Mega Drive cores over to the new API. The Super Game Boy support is very unstable, and will die horribly if you have no cartridge connected, or if you try disconnecting a cartridge after the system is powered on. Lots of work to do there. I didn't test Mega Drive cartridge chaining. The Game Gear gained smarter up+down and left+right masking. I added removeWhere() and findWhere() to nall::vector. There's also been a lot of cleanups to various things. There's four cores left to go, and two are skeletons so they shouldn't be too terribly difficult.
This commit is contained in:
parent
27ac13ef84
commit
a1e9bed75a
@ -40,7 +40,7 @@ obj/libco.o: ../libco/libco.c
|
||||
obj/emulator.o: emulator/emulator.cpp
|
||||
|
||||
ifeq ($(target),higan)
|
||||
cores := sfc ms gba
|
||||
cores := sfc ms md gb gba ws
|
||||
endif
|
||||
|
||||
ifeq ($(target),legacy)
|
||||
|
0
higan/System/Mega Drive/Controller/Control Pad/.gitignore
vendored
Normal file
0
higan/System/Mega Drive/Controller/Control Pad/.gitignore
vendored
Normal file
0
higan/System/Mega Drive/Controller/Fighting Pad/.gitignore
vendored
Normal file
0
higan/System/Mega Drive/Controller/Fighting Pad/.gitignore
vendored
Normal file
@ -37,7 +37,7 @@ using namespace nall;
|
||||
|
||||
namespace higan {
|
||||
static const string Name = "higan";
|
||||
static const string Version = "106.104";
|
||||
static const string Version = "106.105";
|
||||
static const string Author = "byuu";
|
||||
static const string License = "GPLv3";
|
||||
static const string Website = "https://byuu.org/";
|
||||
|
@ -33,7 +33,7 @@ struct Scheduler {
|
||||
}
|
||||
|
||||
auto remove(Thread& thread) -> void {
|
||||
_threads.removeValue(&thread);
|
||||
removeWhere(_threads) == &thread;
|
||||
}
|
||||
|
||||
auto enter(Mode mode = Mode::Run) -> Event {
|
||||
|
@ -10,10 +10,6 @@ namespace higan::GameBoy {
|
||||
#include "serialization.cpp"
|
||||
APU apu;
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
square1.run();
|
||||
square2.run();
|
||||
@ -25,7 +21,7 @@ auto APU::main() -> void {
|
||||
stream->sample(sequencer.left / 32768.0, sequencer.right / 32768.0);
|
||||
} else {
|
||||
double samples[] = {sequencer.left / 32768.0, sequencer.right / 32768.0};
|
||||
superGameBoy->audioSample(samples, 2);
|
||||
superGameBoy->audio(samples, 2);
|
||||
}
|
||||
|
||||
if(cycle == 0) { //512hz
|
||||
@ -52,7 +48,9 @@ auto APU::main() -> void {
|
||||
}
|
||||
|
||||
auto APU::power() -> void {
|
||||
create(Enter, 2 * 1024 * 1024);
|
||||
Thread::create(2 * 1024 * 1024, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
if(!Model::SuperGameBoy()) {
|
||||
stream = audio.createStream(2, frequency());
|
||||
stream->addHighPassFilter(20.0, Filter::Order::First);
|
||||
|
@ -1,7 +1,6 @@
|
||||
struct APU : Thread, MMIO {
|
||||
shared_pointer<Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
|
@ -17,20 +17,22 @@ Cartridge cartridge;
|
||||
#include "tama/tama.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cartridge.main();
|
||||
auto Cartridge::load(Node::Object parent, Node::Object from) -> void {
|
||||
port = Node::Port::create("Cartridge Slot", "Cartridge");
|
||||
port->attach = [&](auto node) { connect(node); };
|
||||
port->detach = [&](auto node) { disconnect(); };
|
||||
if(from = Node::load(port, from)) {
|
||||
if(auto node = from->find<Node::Peripheral>(0)) port->connect(node);
|
||||
}
|
||||
parent->append(port);
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
mapper->main();
|
||||
}
|
||||
auto Cartridge::connect(Node::Peripheral with) -> void {
|
||||
if(!Model::SuperGameBoy()) {
|
||||
node = Node::Peripheral::create("Cartridge", port->type);
|
||||
node->load(with);
|
||||
}
|
||||
|
||||
auto Cartridge::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
information = {};
|
||||
rom = {};
|
||||
ram = {};
|
||||
@ -39,30 +41,11 @@ auto Cartridge::load() -> bool {
|
||||
accelerometer = false;
|
||||
rumble = false;
|
||||
|
||||
if(Model::GameBoy()) {
|
||||
if(auto loaded = platform->load(ID::GameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
if(auto loaded = platform->load(ID::GameBoyColor, "Game Boy Color", "gbc")) {
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
if(Model::SuperGameBoy()) {
|
||||
if(auto loaded = platform->load(ID::SuperGameBoy, "Game Boy", "gb")) {
|
||||
information.pathID = loaded.pathID;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
} else return;
|
||||
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
information.title = document["game/label"].text();
|
||||
|
||||
auto mapperID = document["game/board"].text();
|
||||
if(mapperID == "MBC0" ) mapper = &mbc0;
|
||||
@ -84,7 +67,7 @@ auto Cartridge::load() -> bool {
|
||||
if(auto memory = Game::Memory{document["game/board/memory(type=ROM,content=Program)"]}) {
|
||||
rom.size = max(0x4000, (uint)memory.size);
|
||||
rom.data = memory::allocate<uint8>(rom.size, 0xff);
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, memory.name(), File::Read, File::Required)) {
|
||||
fp->read(rom.data, min(rom.size, fp->size()));
|
||||
}
|
||||
}
|
||||
@ -93,7 +76,7 @@ auto Cartridge::load() -> bool {
|
||||
ram.size = memory.size;
|
||||
ram.data = memory::allocate<uint8>(ram.size, 0xff);
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
if(auto fp = platform->open(node, memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(ram.data, min(ram.size, fp->size()));
|
||||
}
|
||||
}
|
||||
@ -103,7 +86,7 @@ auto Cartridge::load() -> bool {
|
||||
rtc.size = memory.size;
|
||||
rtc.data = memory::allocate<uint8>(rtc.size, 0xff);
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
if(auto fp = platform->open(node, memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(rtc.data, min(rtc.size, fp->size()));
|
||||
}
|
||||
}
|
||||
@ -111,15 +94,29 @@ auto Cartridge::load() -> bool {
|
||||
|
||||
information.sha256 = Hash::SHA256({rom.data, rom.size}).digest();
|
||||
mapper->load(document);
|
||||
return true;
|
||||
|
||||
power();
|
||||
port->prepend(node);
|
||||
}
|
||||
|
||||
auto Cartridge::disconnect() -> void {
|
||||
if(!node) return;
|
||||
delete[] rom.data;
|
||||
delete[] ram.data;
|
||||
delete[] rtc.data;
|
||||
rom = {};
|
||||
ram = {};
|
||||
rtc = {};
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
if(!node) return;
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto memory = Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
|
||||
if(auto fp = platform->open(node, memory.name(), File::Write)) {
|
||||
fp->write(ram.data, ram.size);
|
||||
}
|
||||
}
|
||||
@ -127,7 +124,7 @@ auto Cartridge::save() -> void {
|
||||
|
||||
if(auto memory = Game::Memory{document["game/board/memory(type=RTC,content=Time)"]}) {
|
||||
if(memory.nonVolatile) {
|
||||
if(auto fp = platform->open(pathID(), memory.name(), File::Write)) {
|
||||
if(auto fp = platform->open(node, memory.name(), File::Write)) {
|
||||
fp->write(rtc.data, rtc.size);
|
||||
}
|
||||
}
|
||||
@ -136,13 +133,31 @@ auto Cartridge::save() -> void {
|
||||
mapper->save(document);
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
delete[] rom.data;
|
||||
delete[] ram.data;
|
||||
delete[] rtc.data;
|
||||
rom = {};
|
||||
ram = {};
|
||||
rtc = {};
|
||||
auto Cartridge::power() -> void {
|
||||
Thread::create(4 * 1024 * 1024, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
|
||||
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
|
||||
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
|
||||
bus.mmio[0xff50] = this;
|
||||
|
||||
bootromEnable = true;
|
||||
|
||||
if(mapper) mapper->power();
|
||||
}
|
||||
|
||||
auto Cartridge::main() -> void {
|
||||
mapper->main();
|
||||
}
|
||||
|
||||
auto Cartridge::step(uint clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Cartridge::second() -> void {
|
||||
mapper->second();
|
||||
}
|
||||
|
||||
auto Cartridge::readIO(uint16 address) -> uint8 {
|
||||
@ -159,22 +174,6 @@ auto Cartridge::writeIO(uint16 address, uint8 data) -> void {
|
||||
mapper->write(address, data);
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
|
||||
for(uint n = 0x0000; n <= 0x7fff; n++) bus.mmio[n] = this;
|
||||
for(uint n = 0xa000; n <= 0xbfff; n++) bus.mmio[n] = this;
|
||||
bus.mmio[0xff50] = this;
|
||||
|
||||
bootromEnable = true;
|
||||
|
||||
mapper->power();
|
||||
}
|
||||
|
||||
auto Cartridge::second() -> void {
|
||||
mapper->second();
|
||||
}
|
||||
|
||||
auto Cartridge::Memory::read(uint address) const -> uint8 {
|
||||
if(!size) return 0xff;
|
||||
if(address >= size) address %= size;
|
||||
|
@ -1,22 +1,21 @@
|
||||
struct Cartridge : Thread, MMIO {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto hash() const -> string { return information.sha256; }
|
||||
auto manifest() const -> string { return information.manifest; }
|
||||
auto title() const -> string { return information.title; }
|
||||
Node::Port port;
|
||||
Node::Peripheral node;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto load() -> bool;
|
||||
//cartridge.cpp
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto connect(Node::Peripheral) -> void;
|
||||
auto disconnect() -> void;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
|
||||
auto readIO(uint16 address) -> uint8;
|
||||
auto writeIO(uint16 address, uint8 data) -> void;
|
||||
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto power() -> void;
|
||||
auto second() -> void;
|
||||
|
||||
auto readIO(uint16 address) -> uint8;
|
||||
auto writeIO(uint16 address, uint8 data) -> void;
|
||||
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
|
@ -1,3 +1,7 @@
|
||||
auto Cartridge::MBC5::load(Node::Object parent, Node::Object from) -> void {
|
||||
rumble = Node::append<Node::Rumble>(parent, from, "Rumble");
|
||||
}
|
||||
|
||||
auto Cartridge::MBC5::read(uint16 address) -> uint8 {
|
||||
if((address & 0xc000) == 0x0000) { //$0000-3fff
|
||||
return cartridge.rom.read(address.bits(0,13));
|
||||
@ -32,7 +36,11 @@ auto Cartridge::MBC5::write(uint16 address, uint8 data) -> void {
|
||||
}
|
||||
|
||||
if((address & 0xe000) == 0x4000) { //$4000-5fff
|
||||
if(cartridge.rumble) platform->inputRumble(ID::Port::Cartridge, ID::Device::MBC5, 0, data.bit(3));
|
||||
if(cartridge.rumble) {
|
||||
//todo: add rumble timeout?
|
||||
rumble->enable = data.bit(3);
|
||||
platform->input(rumble);
|
||||
}
|
||||
io.ram.bank = data.bits(0,3);
|
||||
return;
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
struct MBC5 : Mapper {
|
||||
Node::Rumble rumble;
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
|
||||
auto read(uint16 address) -> uint8;
|
||||
auto write(uint16 address, uint8 data) -> void;
|
||||
auto power() -> void;
|
||||
|
@ -13,7 +13,7 @@ auto Cartridge::MBC7::EEPROM::load(Markup::Node document) -> void {
|
||||
if(memory.size == 256) size = 256;
|
||||
if(memory.size == 512) size = 512;
|
||||
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Read, File::Optional)) {
|
||||
if(auto fp = platform->open(cartridge.node, memory.name(), File::Read, File::Optional)) {
|
||||
fp->read(data, min(fp->size(), sizeof(data)));
|
||||
}
|
||||
}
|
||||
@ -27,7 +27,7 @@ auto Cartridge::MBC7::EEPROM::load(Markup::Node document) -> void {
|
||||
|
||||
auto Cartridge::MBC7::EEPROM::save(Markup::Node document) -> void {
|
||||
if(auto memory = Game::Memory{document["game/board/memory(type=EEPROM,content=Save)"]}) {
|
||||
if(auto fp = platform->open(cartridge.pathID(), memory.name(), File::Write)) {
|
||||
if(auto fp = platform->open(cartridge.node, memory.name(), File::Write)) {
|
||||
fp->write(data, size);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,11 @@
|
||||
#include "eeprom.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::MBC7::load(Node::Object parent, Node::Object from) -> void {
|
||||
x = Node::append<Node::Axis>(parent, from, "X");
|
||||
y = Node::append<Node::Axis>(parent, from, "Y");
|
||||
}
|
||||
|
||||
auto Cartridge::MBC7::load(Markup::Node document) -> void {
|
||||
eeprom.load(document);
|
||||
}
|
||||
@ -72,8 +77,10 @@ auto Cartridge::MBC7::write(uint16 address, uint8 data) -> void {
|
||||
|
||||
case 1: {
|
||||
if(data != 0xaa) break;
|
||||
io.accelerometer.x = Center - platform->inputPoll(ID::Port::Cartridge, ID::Device::MBC7, 0);
|
||||
io.accelerometer.y = Center + platform->inputPoll(ID::Port::Cartridge, ID::Device::MBC7, 1);
|
||||
platform->input(x);
|
||||
platform->input(y);
|
||||
io.accelerometer.x = Center - x->value;
|
||||
io.accelerometer.y = Center + y->value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
struct MBC7 : Mapper {
|
||||
Node::Axis x;
|
||||
Node::Axis y;
|
||||
|
||||
enum : uint { Center = 0x81d0 }; //not 0x8000
|
||||
|
||||
//mbc7.cpp
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto load(Markup::Node document) -> void override;
|
||||
auto save(Markup::Node document) -> void override;
|
||||
auto main() -> void override;
|
||||
|
@ -5,5 +5,5 @@ auto Cartridge::serialize(serializer& s) -> void {
|
||||
|
||||
s.integer(bootromEnable);
|
||||
|
||||
mapper->serialize(s);
|
||||
if(mapper) mapper->serialize(s);
|
||||
}
|
||||
|
@ -8,10 +8,6 @@ namespace higan::GameBoy {
|
||||
#include "serialization.cpp"
|
||||
CPU cpu;
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
interruptTest();
|
||||
instruction();
|
||||
@ -85,7 +81,9 @@ auto CPU::stop() -> bool {
|
||||
}
|
||||
|
||||
auto CPU::power() -> void {
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
Thread::create(4 * 1024 * 1024, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
SM83::power();
|
||||
|
||||
for(uint n = 0xc000; n <= 0xdfff; n++) bus.mmio[n] = this; //WRAM
|
||||
|
@ -1,7 +1,6 @@
|
||||
struct CPU : SM83, Thread, MMIO {
|
||||
enum class Interrupt : uint { Vblank, Stat, Timer, Serial, Joypad };
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto raise(Interrupt id) -> void;
|
||||
auto interruptTest() -> void;
|
||||
|
@ -6,27 +6,19 @@ auto CPU::wramAddress(uint16 addr) const -> uint {
|
||||
}
|
||||
|
||||
auto CPU::joypPoll() -> void {
|
||||
function<auto (uint, uint, uint) -> int16> inputPoll = {&Platform::inputPoll, platform};
|
||||
if(Model::SuperGameBoy()) inputPoll = {&SuperGameBoyInterface::inputPoll, superGameBoy};
|
||||
controls.poll();
|
||||
|
||||
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;
|
||||
uint4 dpad;
|
||||
dpad.bit(0) = controls.rightLatch;
|
||||
dpad.bit(1) = controls.leftLatch;
|
||||
dpad.bit(2) = controls.upLatch;
|
||||
dpad.bit(3) = controls.downLatch;
|
||||
|
||||
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(!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
|
||||
if(dpad & 2) dpad &= ~1; //disallow left+right
|
||||
}
|
||||
uint4 button;
|
||||
button.bit(0) = controls.a->value;
|
||||
button.bit(1) = controls.b->value;
|
||||
button.bit(2) = controls.select->value;
|
||||
button.bit(3) = controls.start->value;
|
||||
|
||||
status.joyp = 0x0f;
|
||||
if(status.p15 == 1 && status.p14 == 1 && Model::SuperGameBoy()) {
|
||||
|
@ -23,7 +23,7 @@ auto CPU::step(uint clocks) -> void {
|
||||
}
|
||||
|
||||
if(Model::SuperGameBoy()) {
|
||||
system._clocksExecuted += clocks;
|
||||
system.information.clocksExecuted += clocks;
|
||||
scheduler.exit(Scheduler::Event::Step);
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,16 @@ namespace higan::GameBoy {
|
||||
extern Cheat cheat;
|
||||
|
||||
struct Thread : higan::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
higan::Thread::create(entrypoint, frequency);
|
||||
auto create(double frequency, function<void ()> entryPoint) -> void {
|
||||
higan::Thread::create(frequency, entryPoint);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
auto destroy() -> void {
|
||||
scheduler.remove(*this);
|
||||
higan::Thread::destroy();
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
GameBoyColorInterface::GameBoyColorInterface() {
|
||||
propertyGameBoyColor.memory.size(2048);
|
||||
}
|
||||
|
||||
auto GameBoyColorInterface::information() -> Information {
|
||||
Information information;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy Color";
|
||||
information.extension = "gbc";
|
||||
return information;
|
||||
}
|
||||
|
||||
auto GameBoyColorInterface::color(uint32 color) -> uint64 {
|
||||
uint r = color.bits( 0, 4);
|
||||
uint g = color.bits( 5, 9);
|
||||
uint b = color.bits(10,14);
|
||||
|
||||
uint64_t R = image::normalize(r, 5, 16);
|
||||
uint64_t G = image::normalize(g, 5, 16);
|
||||
uint64_t B = image::normalize(b, 5, 16);
|
||||
|
||||
if(option.video.colorEmulation()) {
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
|
||||
auto GameBoyColorInterface::load() -> bool {
|
||||
return system.load(this, System::Model::GameBoyColor);
|
||||
}
|
||||
|
||||
auto GameBoyColorInterface::properties() -> Settings& {
|
||||
return propertyGameBoyColor;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
GameBoyInterface::GameBoyInterface() {
|
||||
propertyGameBoy.memory.size(256);
|
||||
}
|
||||
|
||||
auto GameBoyInterface::information() -> Information {
|
||||
Information information;
|
||||
information.manufacturer = "Nintendo";
|
||||
information.name = "Game Boy";
|
||||
information.extension = "gb";
|
||||
return information;
|
||||
}
|
||||
|
||||
auto GameBoyInterface::color(uint32 color) -> uint64 {
|
||||
if(!option.video.colorEmulation()) {
|
||||
uint64 L = image::normalize(3 - color, 2, 16);
|
||||
return L << 32 | L << 16 | L << 0;
|
||||
} else {
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#elif defined(DMG_PALETTE_WHITE)
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint64 R = monochrome[color][0];
|
||||
uint64 G = monochrome[color][1];
|
||||
uint64 B = monochrome[color][2];
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto GameBoyInterface::load() -> bool {
|
||||
return system.load(this, System::Model::GameBoy);
|
||||
}
|
||||
|
||||
auto GameBoyInterface::properties() -> Settings& {
|
||||
return propertyGameBoy;
|
||||
}
|
@ -2,94 +2,26 @@
|
||||
|
||||
namespace higan::GameBoy {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
SuperGameBoyInterface* superGameBoy = nullptr;
|
||||
Options option;
|
||||
Properties propertyGameBoy;
|
||||
Properties propertyGameBoyColor;
|
||||
#include "game-boy.cpp"
|
||||
#include "game-boy-color.cpp"
|
||||
|
||||
auto AbstractInterface::display() -> Display {
|
||||
Display display;
|
||||
display.type = Display::Type::LCD;
|
||||
display.colors = Model::GameBoyColor() ? 1 << 15 : 1 << 2;
|
||||
display.width = 160;
|
||||
display.height = 144;
|
||||
display.internalWidth = 160;
|
||||
display.internalHeight = 144;
|
||||
display.aspectCorrection = 1.0;
|
||||
return display;
|
||||
auto AbstractInterface::root() -> Node::Object {
|
||||
return system.node;
|
||||
}
|
||||
|
||||
auto AbstractInterface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
auto AbstractInterface::load(string tree) -> void {
|
||||
interface = this;
|
||||
system.load(Node::unserialize(tree));
|
||||
}
|
||||
|
||||
auto AbstractInterface::hashes() -> vector<string> {
|
||||
return {cartridge.hash()};
|
||||
}
|
||||
|
||||
auto AbstractInterface::manifests() -> vector<string> {
|
||||
return {cartridge.manifest()};
|
||||
}
|
||||
|
||||
auto AbstractInterface::titles() -> vector<string> {
|
||||
return {cartridge.title()};
|
||||
auto AbstractInterface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto AbstractInterface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto AbstractInterface::unload() -> void {
|
||||
save();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto AbstractInterface::ports() -> vector<Port> { return {
|
||||
{ID::Port::Hardware, "Hardware"},
|
||||
{ID::Port::Cartridge, "Cartridge"}};
|
||||
}
|
||||
|
||||
auto AbstractInterface::devices(uint port) -> vector<Device> {
|
||||
if(port == ID::Port::Hardware) return {
|
||||
{ID::Device::Controls, "Controls"}
|
||||
};
|
||||
|
||||
if(port == ID::Port::Cartridge) return {
|
||||
{ID::Device::MBC5, "MBC5"},
|
||||
{ID::Device::MBC7, "MBC7"}
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AbstractInterface::inputs(uint device) -> vector<Input> {
|
||||
using Type = Input::Type;
|
||||
|
||||
if(device == ID::Device::Controls) return {
|
||||
{Type::Hat, "Up" },
|
||||
{Type::Hat, "Down" },
|
||||
{Type::Hat, "Left" },
|
||||
{Type::Hat, "Right" },
|
||||
{Type::Button, "B" },
|
||||
{Type::Button, "A" },
|
||||
{Type::Control, "Select"},
|
||||
{Type::Control, "Start" }
|
||||
};
|
||||
|
||||
if(device == ID::Device::MBC5) return {
|
||||
{Type::Rumble, "Rumble"}
|
||||
};
|
||||
|
||||
if(device == ID::Device::MBC7) return {
|
||||
{Type::Axis, "Accelerometer - X-axis"},
|
||||
{Type::Axis, "Accelerometer - Y-axis"}
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AbstractInterface::power() -> void {
|
||||
system.power();
|
||||
}
|
||||
@ -111,40 +43,4 @@ auto AbstractInterface::cheats(const vector<string>& list) -> void {
|
||||
cheat.assign(list);
|
||||
}
|
||||
|
||||
auto AbstractInterface::options() -> Settings& {
|
||||
return option;
|
||||
}
|
||||
|
||||
/*
|
||||
auto AbstractInterface::cap(const string& name) -> bool {
|
||||
if(name == "Blur Emulation") return true;
|
||||
if(name == "Color Emulation") return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto AbstractInterface::get(const string& name) -> any {
|
||||
if(name == "Blur Emulation") return settings.blurEmulation;
|
||||
if(name == "Color Emulation") return settings.colorEmulation;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto AbstractInterface::set(const string& name, const any& value) -> bool {
|
||||
if(name == "Blur Emulation" && value.is<bool>()) {
|
||||
settings.blurEmulation = value.get<bool>();
|
||||
if(Model::SuperGameBoy()) return true;
|
||||
video.setEffect(Video::Effect::InterframeBlending, settings.blurEmulation);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(name == "Color Emulation" && value.is<bool>()) {
|
||||
settings.colorEmulation = value.get<bool>();
|
||||
if(Model::SuperGameBoy()) return true;
|
||||
video.setPalette();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
@ -2,41 +2,13 @@
|
||||
|
||||
namespace higan::GameBoy {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
GameBoy,
|
||||
SuperGameBoy,
|
||||
GameBoyColor,
|
||||
};
|
||||
|
||||
struct Port { enum : uint {
|
||||
Hardware,
|
||||
Cartridge,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
Controls,
|
||||
MBC5,
|
||||
MBC7,
|
||||
};};
|
||||
};
|
||||
extern Interface* interface;
|
||||
|
||||
struct AbstractInterface : Interface {
|
||||
auto display() -> Display override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto hashes() -> vector<string> override;
|
||||
auto manifests() -> vector<string> override;
|
||||
auto titles() -> vector<string> override;
|
||||
|
||||
auto root() -> Node::Object override;
|
||||
auto load(string tree = {}) -> void override;
|
||||
auto unload() -> void;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto ports() -> vector<Port> override;
|
||||
auto devices(uint port) -> vector<Device> override;
|
||||
auto inputs(uint device) -> vector<Input> override;
|
||||
|
||||
auto power() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
@ -44,35 +16,19 @@ struct AbstractInterface : Interface {
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cheats(const vector<string>&) -> void override;
|
||||
|
||||
auto options() -> Settings& override;
|
||||
};
|
||||
|
||||
struct GameBoyInterface : AbstractInterface {
|
||||
GameBoyInterface();
|
||||
auto information() -> Information override;
|
||||
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto load() -> bool override;
|
||||
|
||||
auto properties() -> Settings& override;
|
||||
auto name() -> string override { return "Game Boy"; }
|
||||
};
|
||||
|
||||
struct GameBoyColorInterface : AbstractInterface {
|
||||
GameBoyColorInterface();
|
||||
auto information() -> Information override;
|
||||
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto load() -> bool override;
|
||||
|
||||
auto properties() -> Settings& override;
|
||||
auto name() -> string override { return "Game Boy Color"; }
|
||||
};
|
||||
|
||||
struct SuperGameBoyInterface {
|
||||
virtual auto audioSample(const double* samples, uint channels) -> void = 0;
|
||||
virtual auto inputPoll(uint port, uint device, uint id) -> int16 = 0;
|
||||
virtual auto audio(const double* samples, uint channels) -> void = 0;
|
||||
virtual auto input() -> uint8 = 0;
|
||||
|
||||
virtual auto lcdScanline() -> void = 0;
|
||||
virtual auto lcdOutput(uint2 color) -> void = 0;
|
||||
@ -82,9 +38,6 @@ struct SuperGameBoyInterface {
|
||||
|
||||
extern SuperGameBoyInterface* superGameBoy;
|
||||
|
||||
#include "options.hpp"
|
||||
#include "properties.hpp"
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,20 +0,0 @@
|
||||
struct Options : Setting<> {
|
||||
struct Video : Setting<> { using Setting::Setting;
|
||||
Setting<boolean> interframeBlending{this, "interframeBlending", true};
|
||||
Setting<boolean> colorEmulation{this, "colorEmulation", true};
|
||||
} video{this, "video"};
|
||||
|
||||
Options() : Setting{"options"} {
|
||||
//todo: why is ::higan needed? fc/interface/options.hpp works fine with just higan
|
||||
video.interframeBlending.onModify([&] {
|
||||
//if(Model::SuperGameBoy()) return;
|
||||
::higan::video.setEffect(::higan::Video::Effect::InterframeBlending, video.interframeBlending());
|
||||
});
|
||||
video.colorEmulation.onModify([&] {
|
||||
//if(Model::SuperGameBoy()) return;
|
||||
::higan::video.setPalette();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
extern Options option;
|
@ -1,13 +0,0 @@
|
||||
struct Properties : Setting<> {
|
||||
struct Memory : Setting<> { using Setting::Setting;
|
||||
Setting<string> type{this, "type", "ROM"};
|
||||
Setting<natural> size{this, "size"};
|
||||
Setting<string> content{this, "content", "Boot"};
|
||||
} memory{this, "memory"};
|
||||
|
||||
Properties() : Setting{"system"} {
|
||||
}
|
||||
};
|
||||
|
||||
extern Properties propertyGameBoy;
|
||||
extern Properties propertyGameBoyColor;
|
@ -120,7 +120,9 @@ auto PPU::writeIO(uint16 addr, uint8 data) -> void {
|
||||
|
||||
//restart cothread to begin new frame
|
||||
auto clock = Thread::clock();
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
Thread::create(4 * 1024 * 1024, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
Thread::setClock(clock);
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,6 @@ PPU ppu;
|
||||
#include "cgb.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ppu.main();
|
||||
}
|
||||
|
||||
auto PPU::main() -> void {
|
||||
if(!status.displayEnable) {
|
||||
for(uint n : range(160 * 144)) screen[n] = Model::GameBoy() ? 0 : 0x7fff;
|
||||
@ -73,7 +69,7 @@ auto PPU::coincidence() -> bool {
|
||||
}
|
||||
|
||||
auto PPU::refresh() -> void {
|
||||
if(!Model::SuperGameBoy()) video.refresh(screen, 160 * sizeof(uint32), 160, 144);
|
||||
if(!Model::SuperGameBoy()) display.screen->refresh(screen, 160 * sizeof(uint32), 160, 144);
|
||||
}
|
||||
|
||||
auto PPU::step(uint clocks) -> void {
|
||||
@ -109,7 +105,9 @@ auto PPU::hflip(uint data) const -> uint {
|
||||
}
|
||||
|
||||
auto PPU::power() -> void {
|
||||
create(Enter, 4 * 1024 * 1024);
|
||||
Thread::create(4 * 1024 * 1024, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
scanline = {&PPU::scanlineCGB, this};
|
||||
|
@ -1,5 +1,4 @@
|
||||
struct PPU : Thread, MMIO {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto stat() -> void;
|
||||
auto coincidence() -> bool;
|
||||
|
50
higan/gb/system/controls.cpp
Normal file
50
higan/gb/system/controls.cpp
Normal file
@ -0,0 +1,50 @@
|
||||
Controls controls;
|
||||
|
||||
auto Controls::load(Node::Object parent, Node::Object from) -> void {
|
||||
if(Model::SuperGameBoy()) return; //inputs provided by SNES gamepad(s)
|
||||
|
||||
up = Node::append<Node::Button>(parent, from, "Up");
|
||||
down = Node::append<Node::Button>(parent, from, "Down");
|
||||
left = Node::append<Node::Button>(parent, from, "Left");
|
||||
right = Node::append<Node::Button>(parent, from, "Right");
|
||||
b = Node::append<Node::Button>(parent, from, "B");
|
||||
a = Node::append<Node::Button>(parent, from, "A");
|
||||
select = Node::append<Node::Button>(parent, from, "Select");
|
||||
start = Node::append<Node::Button>(parent, from, "Start");
|
||||
}
|
||||
|
||||
auto Controls::poll() -> void {
|
||||
if(Model::SuperGameBoy()) {
|
||||
auto data = superGameBoy->input();
|
||||
rightLatch = data.bit(0);
|
||||
leftLatch = data.bit(1);
|
||||
upLatch = data.bit(2);
|
||||
downLatch = data.bit(3);
|
||||
a->value = data.bit(4);
|
||||
b->value = data.bit(5);
|
||||
select->value = data.bit(6);
|
||||
start->value = data.bit(7);
|
||||
return;
|
||||
}
|
||||
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(b);
|
||||
platform->input(a);
|
||||
platform->input(select);
|
||||
platform->input(start);
|
||||
|
||||
if(!(up->value & down->value)) {
|
||||
yHold = 0, upLatch = up->value, downLatch = down->value;
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value & right->value)) {
|
||||
xHold = 0, leftLatch = left->value, rightLatch = right->value;
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, downLatch);
|
||||
}
|
||||
}
|
23
higan/gb/system/controls.hpp
Normal file
23
higan/gb/system/controls.hpp
Normal file
@ -0,0 +1,23 @@
|
||||
struct Controls {
|
||||
Node::Button up;
|
||||
Node::Button down;
|
||||
Node::Button left;
|
||||
Node::Button right;
|
||||
Node::Button b;
|
||||
Node::Button a;
|
||||
Node::Button select;
|
||||
Node::Button start;
|
||||
|
||||
//controls.cpp
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto poll() -> void;
|
||||
|
||||
bool yHold = 0;
|
||||
bool upLatch = 0;
|
||||
bool downLatch = 0;
|
||||
bool xHold = 0;
|
||||
bool leftLatch = 0;
|
||||
bool rightLatch = 0;
|
||||
};
|
||||
|
||||
extern Controls controls;
|
91
higan/gb/system/display.cpp
Normal file
91
higan/gb/system/display.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
Display display;
|
||||
|
||||
auto Display::load(Node::Object parent, Node::Object from) -> void {
|
||||
node = Node::Video::create("Display");
|
||||
node->type = "LCD";
|
||||
node->width = 160;
|
||||
node->height = 144;
|
||||
node->aspect = 1.0;
|
||||
parent->append(node);
|
||||
|
||||
if(Model::GameBoy()) {
|
||||
node->colors = 1 << 2;
|
||||
node->color = [&](auto index) { return colorGameBoy(index); };
|
||||
}
|
||||
|
||||
if(Model::GameBoyColor()) {
|
||||
node->colors = 1 << 15;
|
||||
node->color = [&](auto index) { return colorGameBoyColor(index); };
|
||||
}
|
||||
|
||||
colorEmulation = Node::Boolean::create("Color Emulation", true, [&](auto value) {
|
||||
screen->setPalette();
|
||||
});
|
||||
colorEmulation->dynamic = true;
|
||||
node->append(colorEmulation);
|
||||
|
||||
interframeBlending = Node::Boolean::create("Interframe Blending", true, [&](auto value) {
|
||||
screen->setInterframeBlending(value);
|
||||
});
|
||||
interframeBlending->dynamic = true;
|
||||
node->append(interframeBlending);
|
||||
|
||||
Node::load(node, from);
|
||||
}
|
||||
|
||||
auto Display::colorGameBoy(uint32 color) -> uint64 {
|
||||
if(!colorEmulation->value()) {
|
||||
uint64 L = image::normalize(3 - color, 2, 16);
|
||||
return L << 32 | L << 16 | L << 0;
|
||||
} else {
|
||||
#define DMG_PALETTE_GREEN
|
||||
//#define DMG_PALETTE_YELLOW
|
||||
//#define DMG_PALETTE_WHITE
|
||||
|
||||
const uint16 monochrome[4][3] = {
|
||||
#if defined(DMG_PALETTE_GREEN)
|
||||
{0xaeae, 0xd9d9, 0x2727},
|
||||
{0x5858, 0xa0a0, 0x2828},
|
||||
{0x2020, 0x6262, 0x2929},
|
||||
{0x1a1a, 0x4545, 0x2a2a},
|
||||
#elif defined(DMG_PALETTE_YELLOW)
|
||||
{0xffff, 0xf7f7, 0x7b7b},
|
||||
{0xb5b5, 0xaeae, 0x4a4a},
|
||||
{0x6b6b, 0x6969, 0x3131},
|
||||
{0x2121, 0x2020, 0x1010},
|
||||
#elif defined(DMG_PALETTE_WHITE)
|
||||
{0xffff, 0xffff, 0xffff},
|
||||
{0xaaaa, 0xaaaa, 0xaaaa},
|
||||
{0x5555, 0x5555, 0x5555},
|
||||
{0x0000, 0x0000, 0x0000},
|
||||
#endif
|
||||
};
|
||||
|
||||
uint64 R = monochrome[color][0];
|
||||
uint64 G = monochrome[color][1];
|
||||
uint64 B = monochrome[color][2];
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Display::colorGameBoyColor(uint32 color) -> uint64 {
|
||||
uint r = color.bits( 0, 4);
|
||||
uint g = color.bits( 5, 9);
|
||||
uint b = color.bits(10,14);
|
||||
|
||||
uint64_t R = image::normalize(r, 5, 16);
|
||||
uint64_t G = image::normalize(g, 5, 16);
|
||||
uint64_t B = image::normalize(b, 5, 16);
|
||||
|
||||
if(colorEmulation->value()) {
|
||||
R = (r * 26 + g * 4 + b * 2);
|
||||
G = ( g * 24 + b * 8);
|
||||
B = (r * 6 + g * 4 + b * 22);
|
||||
R = image::normalize(min(960, R), 10, 16);
|
||||
G = image::normalize(min(960, G), 10, 16);
|
||||
B = image::normalize(min(960, B), 10, 16);
|
||||
}
|
||||
|
||||
return R << 32 | G << 16 | B << 0;
|
||||
}
|
13
higan/gb/system/display.hpp
Normal file
13
higan/gb/system/display.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
struct Display {
|
||||
Node::Video node;
|
||||
Node::Boolean colorEmulation;
|
||||
Node::Boolean interframeBlending;
|
||||
shared_pointer<Screen> screen;
|
||||
|
||||
//display.cpp
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto colorGameBoy(uint32) -> uint64;
|
||||
auto colorGameBoyColor(uint32) -> uint64;
|
||||
};
|
||||
|
||||
extern Display display;
|
@ -1,5 +1,5 @@
|
||||
auto System::serialize() -> serializer {
|
||||
serializer s(_serializeSize);
|
||||
serializer s(information.serializeSize);
|
||||
|
||||
uint signature = 0x31545342;
|
||||
char version[16] = {0};
|
||||
@ -32,7 +32,7 @@ auto System::unserialize(serializer& s) -> bool {
|
||||
}
|
||||
|
||||
auto System::serialize(serializer& s) -> void {
|
||||
s.integer(_clocksExecuted);
|
||||
s.integer(information.clocksExecuted);
|
||||
}
|
||||
|
||||
auto System::serializeAll(serializer& s) -> void {
|
||||
@ -55,5 +55,5 @@ auto System::serializeInit() -> void {
|
||||
s.array(description);
|
||||
|
||||
serializeAll(s);
|
||||
_serializeSize = s.size();
|
||||
information.serializeSize = s.size();
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
|
||||
namespace higan::GameBoy {
|
||||
|
||||
#include "serialization.cpp"
|
||||
System system;
|
||||
Scheduler scheduler;
|
||||
Cheat cheat;
|
||||
#include "controls.cpp"
|
||||
#include "display.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto System::run() -> void {
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) ppu.refresh();
|
||||
@ -18,46 +20,46 @@ auto System::runToSave() -> void {
|
||||
scheduler.synchronize(cartridge);
|
||||
}
|
||||
|
||||
auto System::init() -> void {
|
||||
assert(interface != nullptr);
|
||||
}
|
||||
auto System::load(Node::Object from) -> void {
|
||||
if(node) unload();
|
||||
|
||||
auto System::load(Interface* interface, Model model_, maybe<uint> systemID) -> bool {
|
||||
this->interface = interface;
|
||||
_model = model_;
|
||||
information = {};
|
||||
if(interface->name() == "Game Boy" ) information.model = Model::GameBoy;
|
||||
if(interface->name() == "Game Boy Color") information.model = Model::GameBoyColor;
|
||||
|
||||
auto document = BML::unserialize(interface->properties().serialize());
|
||||
if(auto memory = document["system/memory(type=ROM,content=Boot)"]) {
|
||||
bootROM.allocate(memory["size"].natural());
|
||||
uint id = model() != Model::SuperGameBoy ? ID::System : systemID();
|
||||
string name = model() != Model::SuperGameBoy ? "boot.rom" : "lr35902.boot.rom";
|
||||
if(auto fp = platform->open(id, name, File::Read, File::Required)) {
|
||||
bootROM.load(fp);
|
||||
} else return false;
|
||||
} else return false;
|
||||
node = Node::System::create(interface->name());
|
||||
node->load(from);
|
||||
|
||||
if(!cartridge.load()) return false;
|
||||
serializeInit();
|
||||
return _loaded = true;
|
||||
controls.load(node, from);
|
||||
display.load(node, from);
|
||||
cartridge.load(node, from);
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
if(!loaded()) return;
|
||||
if(!node) return;
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!loaded()) return;
|
||||
cartridge.unload();
|
||||
if(!node) return;
|
||||
save();
|
||||
cartridge.disconnect();
|
||||
bootROM.reset();
|
||||
_loaded = false;
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
if(model() != Model::SuperGameBoy) {
|
||||
for(auto& setting : node->find<Node::Setting>()) setting->setLatch();
|
||||
|
||||
bootROM.allocate(!GameBoy::Model::GameBoyColor() ? 256 : 2048);
|
||||
string name = !GameBoy::Model::SuperGameBoy() ? "boot.rom" : "lr35902.boot.rom";
|
||||
if(auto fp = platform->open(node, name, File::Read, File::Required)) {
|
||||
bootROM.load(fp);
|
||||
} else return;
|
||||
|
||||
if(!GameBoy::Model::SuperGameBoy()) {
|
||||
video.reset(interface);
|
||||
video.setPalette();
|
||||
video.setEffect(Video::Effect::InterframeBlending, option.video.interframeBlending());
|
||||
display.screen = video.createScreen(display.node, 160, 144);
|
||||
audio.reset(interface);
|
||||
}
|
||||
|
||||
@ -69,7 +71,7 @@ auto System::power() -> void {
|
||||
apu.power();
|
||||
scheduler.primary(cpu);
|
||||
|
||||
_clocksExecuted = 0;
|
||||
serializeInit();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
enum class Input : uint {
|
||||
Up, Down, Left, Right, B, A, Select, Start,
|
||||
};
|
||||
#include "controls.hpp"
|
||||
#include "display.hpp"
|
||||
|
||||
struct System {
|
||||
Node::Object node;
|
||||
|
||||
enum class Model : uint {
|
||||
GameBoy,
|
||||
GameBoyColor,
|
||||
@ -10,23 +11,18 @@ struct System {
|
||||
};
|
||||
higan::Memory::Readable<uint8> bootROM; //todo: no higan:: prefix
|
||||
|
||||
inline auto loaded() const -> bool { return _loaded; }
|
||||
inline auto model() const -> Model { return _model; }
|
||||
inline auto clocksExecuted() const -> uint { return _clocksExecuted; }
|
||||
inline auto model() const -> Model { return information.model; }
|
||||
inline auto clocksExecuted() const -> uint { return information.clocksExecuted; }
|
||||
|
||||
//system.cpp
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto init() -> void;
|
||||
auto load(Interface*, Model, maybe<uint> = nothing) -> bool;
|
||||
auto save() -> void;
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto save() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
//video.cpp
|
||||
auto configureVideoPalette() -> void;
|
||||
auto configureVideoEffects() -> void;
|
||||
|
||||
//serialization.cpp
|
||||
auto serialize() -> serializer;
|
||||
auto unserialize(serializer&) -> bool;
|
||||
@ -35,16 +31,11 @@ struct System {
|
||||
auto serializeAll(serializer&) -> void;
|
||||
auto serializeInit() -> void;
|
||||
|
||||
Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
string manifest;
|
||||
Model model = Model::GameBoy;
|
||||
uint serializeSize = 0;
|
||||
uint clocksExecuted = 0;
|
||||
} information;
|
||||
|
||||
bool _loaded = false;
|
||||
Model _model = Model::GameBoy;
|
||||
uint _serializeSize = 0;
|
||||
uint _clocksExecuted = 0;
|
||||
};
|
||||
|
||||
#include <gb/interface/interface.hpp>
|
||||
|
@ -119,6 +119,7 @@ auto Cartridge::disconnect() -> void {
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
if(!node) return;
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(auto memory = Game::Memory{document["game/board/memory(type=RAM,content=Save)"]}) {
|
||||
|
@ -5,7 +5,7 @@ namespace higan::GameBoyAdvance {
|
||||
Interface* interface = nullptr;
|
||||
|
||||
auto GameBoyAdvanceInterface::root() -> Node::Object {
|
||||
return system.root;
|
||||
return system.node;
|
||||
}
|
||||
|
||||
auto GameBoyAdvanceInterface::load(string tree) -> void {
|
||||
@ -14,7 +14,6 @@ auto GameBoyAdvanceInterface::load(string tree) -> void {
|
||||
}
|
||||
|
||||
auto GameBoyAdvanceInterface::unload() -> void {
|
||||
system.save();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
|
@ -22,30 +22,36 @@ auto System::runToSave() -> void {
|
||||
}
|
||||
|
||||
auto System::load(Node::Object from) -> void {
|
||||
if(root) save(), unload();
|
||||
if(node) unload();
|
||||
|
||||
root = Node::System::create(interface->name());
|
||||
root->load(from);
|
||||
information = {};
|
||||
if(interface->name() == "Game Boy Advance") information.model = Model::GameBoyAdvance;
|
||||
if(interface->name() == "Game Boy Player" ) information.model = Model::GameBoyPlayer;
|
||||
|
||||
controls.load(root, from);
|
||||
display.load(root, from);
|
||||
cartridge.load(root, from);
|
||||
node = Node::System::create(interface->name());
|
||||
node->load(from);
|
||||
|
||||
controls.load(node, from);
|
||||
display.load(node, from);
|
||||
cartridge.load(node, from);
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!node) return;
|
||||
save();
|
||||
cartridge.disconnect();
|
||||
root = {};
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
if(!node) return;
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
for(auto& setting : root->find<Node::Setting>()) setting->setLatch();
|
||||
information = {};
|
||||
for(auto& setting : node->find<Node::Setting>()) setting->setLatch();
|
||||
|
||||
if(auto fp = platform->open(root, "bios.rom", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, "bios.rom", File::Read, File::Required)) {
|
||||
fp->read(bios.data, bios.size);
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,7 @@
|
||||
#include "display.hpp"
|
||||
|
||||
struct System {
|
||||
Node::Object root;
|
||||
|
||||
Node::Object node;
|
||||
enum class Model : uint { GameBoyAdvance, GameBoyPlayer };
|
||||
|
||||
inline auto model() const -> Model { return information.model; }
|
||||
|
@ -6,10 +6,6 @@ APU apu;
|
||||
#include "bus.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto APU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), apu.main();
|
||||
}
|
||||
|
||||
auto APU::main() -> void {
|
||||
if(!state.enabled) {
|
||||
return step(1);
|
||||
@ -56,8 +52,9 @@ auto APU::power(bool reset) -> void {
|
||||
Z80::bus = this;
|
||||
Z80::power();
|
||||
bus->grant(false);
|
||||
create(APU::Enter, system.frequency() / 15.0);
|
||||
|
||||
Thread::create(system.frequency() / 15.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
if(!reset) memory::fill(ram, sizeof(ram));
|
||||
state = {};
|
||||
}
|
||||
@ -65,7 +62,9 @@ auto APU::power(bool reset) -> void {
|
||||
auto APU::reset() -> void {
|
||||
Z80::power();
|
||||
bus->grant(false);
|
||||
create(APU::Enter, system.frequency() / 15.0);
|
||||
Thread::create(system.frequency() / 15.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
state = {};
|
||||
}
|
||||
|
||||
|
@ -5,62 +5,41 @@ namespace higan::MegaDrive {
|
||||
Cartridge cartridge;
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto Cartridge::hashes() const -> vector<string> {
|
||||
vector<string> hashes;
|
||||
hashes.append(information.hash);
|
||||
if(slot) for(auto& hash : slot->hashes()) hashes.append(hash);
|
||||
return hashes;
|
||||
auto Cartridge::load(Node::Object parent, Node::Object from) -> void {
|
||||
port = Node::Port::create("Cartridge Slot", "Cartridge");
|
||||
port->attach = [&](auto node) { connect(node); };
|
||||
port->detach = [&](auto node) { disconnect(); };
|
||||
if(from = Node::load(port, from)) {
|
||||
if(auto node = from->find<Node::Peripheral>(0)) port->connect(node);
|
||||
}
|
||||
parent->append(port);
|
||||
}
|
||||
|
||||
auto Cartridge::manifests() const -> vector<string> {
|
||||
vector<string> manifests;
|
||||
manifests.append(information.manifest);
|
||||
if(slot) for(auto& manifest : slot->manifests()) manifests.append(manifest);
|
||||
return manifests;
|
||||
}
|
||||
auto Cartridge::connect(Node::Peripheral with) -> void {
|
||||
node = Node::Peripheral::create("Cartridge", port->type);
|
||||
node->load(with);
|
||||
|
||||
auto Cartridge::titles() const -> vector<string> {
|
||||
vector<string> titles;
|
||||
titles.append(information.title);
|
||||
if(slot) for(auto& title : slot->titles()) titles.append(title);
|
||||
return titles;
|
||||
}
|
||||
information = {};
|
||||
|
||||
auto Cartridge::load() -> bool {
|
||||
unload();
|
||||
|
||||
if(auto loaded = platform->load(ID::MegaDrive, "Mega Drive", "md", {"Auto", "NTSC-J", "NTSC-U", "PAL"})) {
|
||||
information.pathID = loaded.pathID;
|
||||
information.region = loaded.option;
|
||||
} else return false;
|
||||
|
||||
if(auto fp = platform->open(pathID(), "manifest.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, "manifest.bml", File::Read, File::Required)) {
|
||||
information.manifest = fp->reads();
|
||||
} else return false;
|
||||
} else return;
|
||||
|
||||
information.document = BML::unserialize(information.manifest);
|
||||
information.hash = information.document["game/sha256"].text();
|
||||
information.title = information.document["game/label"].text();
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
|
||||
if(!loadROM(rom, information.document["game/board/memory(type=ROM,content=Program)"])) {
|
||||
return unload(), false;
|
||||
if(!loadROM(rom, document["game/board/memory(type=ROM,content=Program)"])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!loadROM(patch, information.document["game/board/memory(type=ROM,content=Patch)"])) {
|
||||
if(!loadROM(patch, document["game/board/memory(type=ROM,content=Patch)"])) {
|
||||
patch.reset();
|
||||
}
|
||||
|
||||
if(!loadRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"])) {
|
||||
if(!loadRAM(ram, document["game/board/memory(type=RAM,content=Save)"])) {
|
||||
ram.reset();
|
||||
}
|
||||
|
||||
if(information.region == "Auto") {
|
||||
if(auto region = information.document["game/region"].text()) {
|
||||
information.region = region.upcase();
|
||||
} else {
|
||||
information.region = "NTSC-J";
|
||||
}
|
||||
}
|
||||
information.region = document["game/region"].text();
|
||||
|
||||
read = {&Cartridge::readLinear, this};
|
||||
write = {&Cartridge::writeLinear, this};
|
||||
@ -70,9 +49,9 @@ auto Cartridge::load() -> bool {
|
||||
write = {&Cartridge::writeBanked, this};
|
||||
}
|
||||
|
||||
if(information.document["game/board/slot(type=MegaDrive)"]) {
|
||||
if(document["game/board/slot(type=MegaDrive)"]) {
|
||||
slot = new Cartridge{depth + 1};
|
||||
if(!slot->load()) slot.reset();
|
||||
slot->load(node, with);
|
||||
|
||||
if(patch) {
|
||||
read = {&Cartridge::readLockOn, this};
|
||||
@ -101,22 +80,27 @@ auto Cartridge::load() -> bool {
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
power();
|
||||
port->prepend(node);
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
saveRAM(ram, information.document["game/board/memory(type=RAM,content=Save)"]);
|
||||
if(slot) slot->save();
|
||||
}
|
||||
|
||||
auto Cartridge::unload() -> void {
|
||||
auto Cartridge::disconnect() -> void {
|
||||
if(!node) return;
|
||||
rom.reset();
|
||||
patch.reset();
|
||||
ram.reset();
|
||||
read.reset();
|
||||
write.reset();
|
||||
if(slot) slot->unload();
|
||||
if(slot) slot->disconnect();
|
||||
slot.reset();
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto Cartridge::save() -> void {
|
||||
if(!node) return;
|
||||
auto document = BML::unserialize(information.manifest);
|
||||
saveRAM(ram, document["game/board/memory(type=RAM,content=Save)"]);
|
||||
if(slot) slot->save();
|
||||
}
|
||||
|
||||
auto Cartridge::power() -> void {
|
||||
@ -135,7 +119,7 @@ auto Cartridge::loadROM(Memory& rom, Markup::Node memory) -> bool {
|
||||
rom.size = memory["size"].natural() >> 1;
|
||||
rom.mask = bit::round(rom.size) - 1;
|
||||
rom.data = new uint16[rom.mask + 1]();
|
||||
if(auto fp = platform->open(pathID(), name, File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, name, File::Read, File::Required)) {
|
||||
for(uint n : range(rom.size)) rom.data[n] = fp->readm(2);
|
||||
} else return false;
|
||||
|
||||
@ -155,7 +139,7 @@ auto Cartridge::loadRAM(Memory& ram, Markup::Node memory) -> bool {
|
||||
ram.mask = bit::round(ram.size) - 1;
|
||||
ram.data = new uint16[ram.mask + 1]();
|
||||
if(!(bool)memory["volatile"]) {
|
||||
if(auto fp = platform->open(pathID(), name, File::Read)) {
|
||||
if(auto fp = platform->open(node, name, File::Read)) {
|
||||
for(uint n : range(ram.size)) {
|
||||
if(ram.bits != 0xffff) ram.data[n] = fp->readm(1) * 0x0101;
|
||||
if(ram.bits == 0xffff) ram.data[n] = fp->readm(2);
|
||||
@ -171,7 +155,7 @@ auto Cartridge::saveRAM(Memory& ram, Markup::Node memory) -> bool {
|
||||
if((bool)memory["volatile"]) return true;
|
||||
|
||||
auto name = string{memory["content"].text(), ".", memory["type"].text()}.downcase();
|
||||
if(auto fp = platform->open(pathID(), name, File::Write)) {
|
||||
if(auto fp = platform->open(node, name, File::Write)) {
|
||||
for(uint n : range(ram.size)) {
|
||||
if(ram.bits != 0xffff) fp->writem(ram.data[n], 1);
|
||||
if(ram.bits == 0xffff) fp->writem(ram.data[n], 2);
|
||||
|
@ -1,20 +1,21 @@
|
||||
struct Cartridge {
|
||||
auto pathID() const -> uint { return information.pathID; }
|
||||
auto region() const -> string { return information.region; }
|
||||
Node::Port port;
|
||||
Node::Peripheral node;
|
||||
|
||||
inline auto region() const -> string { return information.region; }
|
||||
|
||||
//cartridge.cpp
|
||||
Cartridge() = default;
|
||||
Cartridge(uint depth) : depth(depth) {}
|
||||
|
||||
auto hashes() const -> vector<string>;
|
||||
auto manifests() const -> vector<string>;
|
||||
auto titles() const -> vector<string>;
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto connect(Node::Peripheral) -> void;
|
||||
auto disconnect() -> void;
|
||||
|
||||
struct Memory;
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
auto unload() -> void;
|
||||
auto power() -> void;
|
||||
|
||||
struct Memory;
|
||||
auto loadROM(Memory& rom, Markup::Node memory) -> bool;
|
||||
auto loadRAM(Memory& ram, Markup::Node memory) -> bool;
|
||||
auto saveRAM(Memory& ram, Markup::Node memory) -> bool;
|
||||
@ -38,12 +39,8 @@ struct Cartridge {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
struct Information {
|
||||
uint pathID = 0;
|
||||
string region;
|
||||
string hash;
|
||||
string manifest;
|
||||
string title;
|
||||
Markup::Node document;
|
||||
string region;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
|
@ -1,22 +1,54 @@
|
||||
ControlPad::ControlPad(uint port) : Controller(port) {
|
||||
ControlPad::ControlPad(Node::Port parent, Node::Peripheral with) {
|
||||
node = Node::Peripheral::create("Control Pad", parent->type);
|
||||
node->load(with);
|
||||
up = Node::append<Node::Button>(node, with, "Up");
|
||||
down = Node::append<Node::Button>(node, with, "Down");
|
||||
left = Node::append<Node::Button>(node, with, "Left");
|
||||
right = Node::append<Node::Button>(node, with, "Right");
|
||||
a = Node::append<Node::Button>(node, with, "A");
|
||||
b = Node::append<Node::Button>(node, with, "B");
|
||||
c = Node::append<Node::Button>(node, with, "C");
|
||||
start = Node::append<Node::Button>(node, with, "Start");
|
||||
parent->prepend(node);
|
||||
}
|
||||
|
||||
auto ControlPad::readData() -> uint8 {
|
||||
uint6 data;
|
||||
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(a);
|
||||
platform->input(b);
|
||||
platform->input(c);
|
||||
platform->input(start);
|
||||
|
||||
if(!(up->value & down->value)) {
|
||||
yHold = 0, upLatch = up->value, downLatch = down->value;
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value & right->value)) {
|
||||
xHold = 0, leftLatch = left->value, rightLatch = right->value;
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, rightLatch);
|
||||
}
|
||||
|
||||
if(select == 0) {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::ControlPad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::ControlPad, Down);
|
||||
data.bit(0) = upLatch;
|
||||
data.bit(1) = downLatch;
|
||||
data.bits(2,3) = ~0;
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::ControlPad, A);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::ControlPad, Start);
|
||||
data.bit(4) = a->value;
|
||||
data.bit(5) = start->value;
|
||||
} else {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::ControlPad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::ControlPad, Down);
|
||||
data.bit(2) = platform->inputPoll(port, ID::Device::ControlPad, Left);
|
||||
data.bit(3) = platform->inputPoll(port, ID::Device::ControlPad, Right);
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::ControlPad, B);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::ControlPad, C);
|
||||
data.bit(0) = upLatch;
|
||||
data.bit(1) = downLatch;
|
||||
data.bit(2) = leftLatch;
|
||||
data.bit(3) = rightLatch;
|
||||
data.bit(4) = b->value;
|
||||
data.bit(5) = c->value;
|
||||
}
|
||||
|
||||
data = ~data;
|
||||
|
@ -1,13 +1,26 @@
|
||||
struct ControlPad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, A, B, C, Start,
|
||||
};
|
||||
Node::Button up;
|
||||
Node::Button down;
|
||||
Node::Button left;
|
||||
Node::Button right;
|
||||
Node::Button a;
|
||||
Node::Button b;
|
||||
Node::Button c;
|
||||
Node::Button start;
|
||||
|
||||
ControlPad(uint port);
|
||||
ControlPad(Node::Port, Node::Peripheral);
|
||||
|
||||
auto readData() -> uint8 override;
|
||||
auto writeData(uint8 data) -> void override;
|
||||
|
||||
private:
|
||||
uint1 select = 1;
|
||||
uint1 latch;
|
||||
|
||||
bool yHold = 0;
|
||||
bool upLatch = 0;
|
||||
bool downLatch = 0;
|
||||
bool xHold = 0;
|
||||
bool leftLatch = 0;
|
||||
bool rightLatch = 0;
|
||||
};
|
||||
|
@ -2,27 +2,20 @@
|
||||
|
||||
namespace higan::MegaDrive {
|
||||
|
||||
ControllerPort controllerPort1;
|
||||
ControllerPort controllerPort2;
|
||||
ControllerPort extensionPort;
|
||||
#include "port.cpp"
|
||||
#include "control-pad/control-pad.cpp"
|
||||
#include "fighting-pad/fighting-pad.cpp"
|
||||
|
||||
Controller::Controller(uint port) : port(port) {
|
||||
if(!handle()) create(Controller::Enter, 1);
|
||||
Controller::Controller() {
|
||||
if(!handle()) Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.peripherals.append(this);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
scheduler.remove(*this);
|
||||
}
|
||||
|
||||
auto Controller::Enter() -> void {
|
||||
while(true) {
|
||||
scheduler.synchronize();
|
||||
if(controllerPort1.device->active()) controllerPort1.device->main();
|
||||
if(controllerPort2.device->active()) controllerPort2.device->main();
|
||||
if(extensionPort.device->active()) extensionPort.device->main();
|
||||
}
|
||||
removeWhere(cpu.peripherals) == this;
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto Controller::main() -> void {
|
||||
@ -30,44 +23,4 @@ auto Controller::main() -> void {
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
auto ControllerPort::connect(uint deviceID) -> void {
|
||||
if(!system.loaded()) return;
|
||||
delete device;
|
||||
|
||||
switch(deviceID) { default:
|
||||
case ID::Device::None: device = new Controller(port); break;
|
||||
case ID::Device::ControlPad: device = new ControlPad(port); break;
|
||||
case ID::Device::FightingPad: device = new FightingPad(port); break;
|
||||
}
|
||||
|
||||
cpu.peripherals.reset();
|
||||
if(auto device = controllerPort1.device) cpu.peripherals.append(device);
|
||||
if(auto device = controllerPort2.device) cpu.peripherals.append(device);
|
||||
if(auto device = extensionPort.device) cpu.peripherals.append(device);
|
||||
}
|
||||
|
||||
auto ControllerPort::readControl() -> uint8 {
|
||||
return control;
|
||||
}
|
||||
|
||||
auto ControllerPort::writeControl(uint8 data) -> void {
|
||||
control = data;
|
||||
}
|
||||
|
||||
auto ControllerPort::power(uint port) -> void {
|
||||
this->port = port;
|
||||
control = 0x00;
|
||||
}
|
||||
|
||||
auto ControllerPort::unload() -> void {
|
||||
delete device;
|
||||
device = nullptr;
|
||||
}
|
||||
|
||||
auto ControllerPort::serialize(serializer& s) -> void {
|
||||
s.integer(control);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,34 +1,14 @@
|
||||
struct Controller : Thread {
|
||||
Controller(uint port);
|
||||
Node::Peripheral node;
|
||||
|
||||
Controller();
|
||||
virtual ~Controller();
|
||||
|
||||
static auto Enter() -> void;
|
||||
virtual auto main() -> void;
|
||||
|
||||
virtual auto readData() -> uint8 { return 0xff; }
|
||||
virtual auto writeData(uint8 data) -> void {}
|
||||
|
||||
const uint port;
|
||||
};
|
||||
|
||||
struct ControllerPort {
|
||||
auto connect(uint deviceID) -> void;
|
||||
|
||||
auto readControl() -> uint8;
|
||||
auto writeControl(uint8 data) -> void;
|
||||
|
||||
auto power(uint port) -> void;
|
||||
auto unload() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint port;
|
||||
uint8 control;
|
||||
Controller* device = nullptr;
|
||||
};
|
||||
|
||||
extern ControllerPort controllerPort1;
|
||||
extern ControllerPort controllerPort2;
|
||||
extern ControllerPort extensionPort;
|
||||
|
||||
#include "port.hpp"
|
||||
#include "control-pad/control-pad.hpp"
|
||||
#include "fighting-pad/fighting-pad.hpp"
|
||||
|
@ -1,5 +1,23 @@
|
||||
FightingPad::FightingPad(uint port) : Controller(port) {
|
||||
create(Controller::Enter, 1'000'000);
|
||||
FightingPad::FightingPad(Node::Port parent, Node::Peripheral with) {
|
||||
node = Node::Peripheral::create("Fighting Pad", parent->type);
|
||||
node->load(with);
|
||||
up = Node::append<Node::Button>(node, with, "Up");
|
||||
down = Node::append<Node::Button>(node, with, "Down");
|
||||
left = Node::append<Node::Button>(node, with, "Left");
|
||||
right = Node::append<Node::Button>(node, with, "Right");
|
||||
a = Node::append<Node::Button>(node, with, "A");
|
||||
b = Node::append<Node::Button>(node, with, "B");
|
||||
c = Node::append<Node::Button>(node, with, "C");
|
||||
x = Node::append<Node::Button>(node, with, "X");
|
||||
y = Node::append<Node::Button>(node, with, "Y");
|
||||
z = Node::append<Node::Button>(node, with, "Z");
|
||||
mode = Node::append<Node::Button>(node, with, "Mode");
|
||||
start = Node::append<Node::Button>(node, with, "Start");
|
||||
parent->prepend(node);
|
||||
|
||||
Thread::create(1'000'000, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
}
|
||||
|
||||
auto FightingPad::main() -> void {
|
||||
@ -13,12 +31,37 @@ auto FightingPad::main() -> void {
|
||||
}
|
||||
|
||||
auto FightingPad::readData() -> uint8 {
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(a);
|
||||
platform->input(b);
|
||||
platform->input(c);
|
||||
platform->input(x);
|
||||
platform->input(y);
|
||||
platform->input(z);
|
||||
platform->input(mode);
|
||||
platform->input(start);
|
||||
|
||||
if(!(up->value & down->value)) {
|
||||
yHold = 0, upLatch = up->value, downLatch = down->value;
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value & right->value)) {
|
||||
xHold = 0, leftLatch = left->value, rightLatch = right->value;
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, rightLatch);
|
||||
}
|
||||
|
||||
uint6 data;
|
||||
|
||||
if(select == 0) {
|
||||
if(counter == 0 || counter == 1 || counter == 4) {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::FightingPad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::FightingPad, Down);
|
||||
data.bit(0) = upLatch;
|
||||
data.bit(1) = downLatch;
|
||||
data.bits(2,3) = ~0;
|
||||
}
|
||||
|
||||
@ -30,23 +73,23 @@ auto FightingPad::readData() -> uint8 {
|
||||
data.bits(0,3) = 0;
|
||||
}
|
||||
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::FightingPad, A);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::FightingPad, Start);
|
||||
data.bit(4) = a->value;
|
||||
data.bit(5) = start->value;
|
||||
} else {
|
||||
if(counter == 0 || counter == 1 || counter == 2 || counter == 4) {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::FightingPad, Up);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::FightingPad, Down);
|
||||
data.bit(2) = platform->inputPoll(port, ID::Device::FightingPad, Left);
|
||||
data.bit(3) = platform->inputPoll(port, ID::Device::FightingPad, Right);
|
||||
data.bit(4) = platform->inputPoll(port, ID::Device::FightingPad, B);
|
||||
data.bit(5) = platform->inputPoll(port, ID::Device::FightingPad, C);
|
||||
data.bit(0) = upLatch;
|
||||
data.bit(1) = downLatch;
|
||||
data.bit(2) = leftLatch;
|
||||
data.bit(3) = rightLatch;
|
||||
data.bit(4) = b->value;
|
||||
data.bit(5) = c->value;
|
||||
}
|
||||
|
||||
if(counter == 3) {
|
||||
data.bit(0) = platform->inputPoll(port, ID::Device::FightingPad, Z);
|
||||
data.bit(1) = platform->inputPoll(port, ID::Device::FightingPad, Y);
|
||||
data.bit(2) = platform->inputPoll(port, ID::Device::FightingPad, X);
|
||||
data.bit(3) = platform->inputPoll(port, ID::Device::FightingPad, Mode);
|
||||
data.bit(0) = z->value;
|
||||
data.bit(1) = y->value;
|
||||
data.bit(2) = x->value;
|
||||
data.bit(3) = mode->value;
|
||||
data.bits(4,5) = 0;
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,32 @@
|
||||
struct FightingPad : Controller {
|
||||
enum : uint {
|
||||
Up, Down, Left, Right, A, B, C, X, Y, Z, Mode, Start,
|
||||
};
|
||||
Node::Button up;
|
||||
Node::Button down;
|
||||
Node::Button left;
|
||||
Node::Button right;
|
||||
Node::Button a;
|
||||
Node::Button b;
|
||||
Node::Button c;
|
||||
Node::Button x;
|
||||
Node::Button y;
|
||||
Node::Button z;
|
||||
Node::Button mode;
|
||||
Node::Button start;
|
||||
|
||||
FightingPad(uint port);
|
||||
FightingPad(Node::Port, Node::Peripheral);
|
||||
auto main() -> void override;
|
||||
|
||||
auto readData() -> uint8 override;
|
||||
auto writeData(uint8 data) -> void override;
|
||||
|
||||
private:
|
||||
uint1 select = 1;
|
||||
uint1 latch;
|
||||
uint3 counter;
|
||||
uint32 timeout;
|
||||
|
||||
bool yHold = 0;
|
||||
bool upLatch = 0;
|
||||
bool downLatch = 0;
|
||||
bool xHold = 0;
|
||||
bool leftLatch = 0;
|
||||
bool rightLatch = 0;
|
||||
};
|
||||
|
37
higan/md/controller/port.cpp
Normal file
37
higan/md/controller/port.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
ControllerPort controllerPort1{"Controller Port 1"};
|
||||
ControllerPort controllerPort2{"Controller Port 2"};
|
||||
ControllerPort extensionPort{"Extension Port"};
|
||||
|
||||
ControllerPort::ControllerPort(string_view name) : name(name) {
|
||||
}
|
||||
|
||||
auto ControllerPort::load(Node::Object parent, Node::Object from) -> void {
|
||||
port = Node::Port::create(name, "Controller");
|
||||
port->hotSwappable = true;
|
||||
port->attach = [&](auto node) { connect(node); };
|
||||
port->detach = [&](auto node) { disconnect(); };
|
||||
if(from = Node::load(port, from)) {
|
||||
if(auto node = from->find<Node::Peripheral>(0)) port->connect(node);
|
||||
}
|
||||
parent->append(port);
|
||||
}
|
||||
|
||||
auto ControllerPort::connect(Node::Peripheral node) -> void {
|
||||
disconnect();
|
||||
if(node) {
|
||||
if(node->name == "Control Pad") device = new ControlPad(port, node);
|
||||
if(node->name == "Fighting Pad") device = new FightingPad(port, node);
|
||||
}
|
||||
}
|
||||
|
||||
auto ControllerPort::disconnect() -> void {
|
||||
device = {};
|
||||
}
|
||||
|
||||
auto ControllerPort::power() -> void {
|
||||
control = 0x00;
|
||||
}
|
||||
|
||||
auto ControllerPort::serialize(serializer& s) -> void {
|
||||
s.integer(control);
|
||||
}
|
26
higan/md/controller/port.hpp
Normal file
26
higan/md/controller/port.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
struct ControllerPort {
|
||||
Node::Port port;
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
|
||||
ControllerPort(string_view name);
|
||||
auto connect(Node::Peripheral) -> void;
|
||||
auto disconnect() -> void;
|
||||
|
||||
auto readControl() -> uint8 { return control; }
|
||||
auto writeControl(uint8 data) -> void { control = data; }
|
||||
|
||||
auto readData() -> uint8 { if(device) return device->readData(); return 0xff; }
|
||||
auto writeData(uint8 data) -> void { if(device) return device->writeData(data); }
|
||||
|
||||
auto power() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
const string name;
|
||||
uint8 control;
|
||||
unique_pointer<Controller> device;
|
||||
friend class Controller;
|
||||
};
|
||||
|
||||
extern ControllerPort controllerPort1;
|
||||
extern ControllerPort controllerPort2;
|
||||
extern ControllerPort extensionPort;
|
@ -73,9 +73,9 @@ auto CPU::readIO(uint24 addr) -> uint16 {
|
||||
| io.version << 0 //0 = Model 1; 1 = Model 2+
|
||||
);
|
||||
|
||||
case 0xa10002: return controllerPort1.device->readData();
|
||||
case 0xa10004: return controllerPort2.device->readData();
|
||||
case 0xa10006: return extensionPort.device->readData();
|
||||
case 0xa10002: return controllerPort1.readData();
|
||||
case 0xa10004: return controllerPort2.readData();
|
||||
case 0xa10006: return extensionPort.readData();
|
||||
|
||||
case 0xa10008: return controllerPort1.readControl();
|
||||
case 0xa1000a: return controllerPort2.readControl();
|
||||
@ -89,9 +89,9 @@ auto CPU::readIO(uint24 addr) -> uint16 {
|
||||
|
||||
auto CPU::writeIO(uint24 addr, uint16 data) -> void {
|
||||
switch(addr & ~1) {
|
||||
case 0xa10002: return controllerPort1.device->writeData(data);
|
||||
case 0xa10004: return controllerPort2.device->writeData(data);
|
||||
case 0xa10006: return extensionPort.device->writeData(data);
|
||||
case 0xa10002: return controllerPort1.writeData(data);
|
||||
case 0xa10004: return controllerPort2.writeData(data);
|
||||
case 0xa10006: return extensionPort.writeData(data);
|
||||
|
||||
case 0xa10008: return controllerPort1.writeControl(data);
|
||||
case 0xa1000a: return controllerPort2.writeControl(data);
|
||||
|
@ -6,10 +6,6 @@ CPU cpu;
|
||||
#include "bus.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto CPU::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), cpu.main();
|
||||
}
|
||||
|
||||
auto CPU::main() -> void {
|
||||
if(state.interruptPending) {
|
||||
if(state.interruptPending.bit((uint)Interrupt::Reset)) {
|
||||
@ -66,24 +62,20 @@ auto CPU::lower(Interrupt interrupt) -> void {
|
||||
state.interruptPending.bit((uint)interrupt) = 0;
|
||||
}
|
||||
|
||||
auto CPU::load(Markup::Node node) -> bool {
|
||||
tmssEnable = false;
|
||||
if(property.cpu.version() == 1) {
|
||||
if(auto memory = node["memory(type=ROM,content=TMSS)"]) {
|
||||
if(auto fp = platform->open(ID::System, "tmss.rom", File::Read, File::Required)) {
|
||||
fp->read(tmss, 2 * 1024);
|
||||
tmssEnable = true;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CPU::power(bool reset) -> void {
|
||||
M68K::bus = this;
|
||||
M68K::power();
|
||||
create(CPU::Enter, system.frequency() / 7.0);
|
||||
Thread::create(system.frequency() / 7.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
|
||||
tmssEnable = false;
|
||||
if(system.tmss->value()) {
|
||||
if(auto fp = platform->open(system.node, "tmss.rom", File::Read, File::Required)) {
|
||||
fp->read(tmss, 2 * 1024);
|
||||
tmssEnable = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!reset) memory::fill(ram, sizeof(ram));
|
||||
|
||||
|
@ -10,7 +10,6 @@ struct CPU : M68K, M68K::Bus, Thread {
|
||||
using Thread::synchronize;
|
||||
|
||||
//cpu.cpp
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void override;
|
||||
auto synchronize() -> void;
|
||||
@ -18,7 +17,6 @@ struct CPU : M68K, M68K::Bus, Thread {
|
||||
auto raise(Interrupt) -> void;
|
||||
auto lower(Interrupt) -> void;
|
||||
|
||||
auto load(Markup::Node) -> bool;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//bus.cpp
|
||||
|
@ -2,158 +2,27 @@
|
||||
|
||||
namespace higan::MegaDrive {
|
||||
|
||||
Options option;
|
||||
Properties property;
|
||||
Interface* interface = nullptr;
|
||||
|
||||
auto MegaDriveInterface::information() -> Information {
|
||||
Information information;
|
||||
information.manufacturer = "Sega";
|
||||
information.name = "Mega Drive";
|
||||
information.extension = "md";
|
||||
information.resettable = true;
|
||||
return information;
|
||||
auto MegaDriveInterface::root() -> Node::Object {
|
||||
return system.node;
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::display() -> Display {
|
||||
Display display;
|
||||
display.type = Display::Type::CRT;
|
||||
display.colors = 3 * (1 << 9);
|
||||
display.width = 320;
|
||||
display.height = 240;
|
||||
display.internalWidth = 1280;
|
||||
display.internalHeight = 480;
|
||||
display.aspectCorrection = 1.0;
|
||||
return display;
|
||||
auto MegaDriveInterface::load(string tree) -> void {
|
||||
interface = this;
|
||||
system.load(Node::unserialize(tree));
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::color(uint32 color) -> uint64 {
|
||||
uint R = color.bits(0, 2);
|
||||
uint G = color.bits(3, 5);
|
||||
uint B = color.bits(6, 8);
|
||||
uint M = color.bits(9,10);
|
||||
|
||||
uint lookup[3][8] = {
|
||||
{ 0, 29, 52, 70, 87, 101, 116, 130}, //shadow
|
||||
{ 0, 52, 87, 116, 144, 172, 206, 255}, //normal
|
||||
{130, 144, 158, 172, 187, 206, 228, 255}, //highlight
|
||||
};
|
||||
|
||||
uint64 r = image::normalize(lookup[M][R], 8, 16);
|
||||
uint64 g = image::normalize(lookup[M][G], 8, 16);
|
||||
uint64 b = image::normalize(lookup[M][B], 8, 16);
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::loaded() -> bool {
|
||||
return system.loaded();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::hashes() -> vector<string> {
|
||||
return cartridge.hashes();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::manifests() -> vector<string> {
|
||||
return cartridge.manifests();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::titles() -> vector<string> {
|
||||
return cartridge.titles();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::load() -> bool {
|
||||
return system.load(this);
|
||||
auto MegaDriveInterface::unload() -> void {
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::save() -> void {
|
||||
system.save();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::unload() -> void {
|
||||
save();
|
||||
system.unload();
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::ports() -> vector<Port> { return {
|
||||
{ID::Port::Controller1, "Controller Port 1"},
|
||||
{ID::Port::Controller2, "Controller Port 2"},
|
||||
{ID::Port::Extension, "Extension Port" }};
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::devices(uint port) -> vector<Device> {
|
||||
if(port == ID::Port::Controller1) return {
|
||||
{ID::Device::None, "None" },
|
||||
{ID::Device::ControlPad, "Control Pad" },
|
||||
{ID::Device::FightingPad, "Fighting Pad"}
|
||||
};
|
||||
|
||||
if(port == ID::Port::Controller2) return {
|
||||
{ID::Device::None, "None" },
|
||||
{ID::Device::ControlPad, "Control Pad" },
|
||||
{ID::Device::FightingPad, "Fighting Pad"}
|
||||
};
|
||||
|
||||
if(port == ID::Port::Extension) return {
|
||||
{ID::Device::None, "None"}
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::inputs(uint device) -> vector<Input> {
|
||||
using Type = Input::Type;
|
||||
|
||||
if(device == ID::Device::None) return {
|
||||
};
|
||||
|
||||
if(device == ID::Device::ControlPad) return {
|
||||
{Type::Hat, "Up" },
|
||||
{Type::Hat, "Down" },
|
||||
{Type::Hat, "Left" },
|
||||
{Type::Hat, "Right"},
|
||||
{Type::Button, "A" },
|
||||
{Type::Button, "B" },
|
||||
{Type::Button, "C" },
|
||||
{Type::Control, "Start"}
|
||||
};
|
||||
|
||||
if(device == ID::Device::FightingPad) return {
|
||||
{Type::Hat, "Up" },
|
||||
{Type::Hat, "Down" },
|
||||
{Type::Hat, "Left" },
|
||||
{Type::Hat, "Right"},
|
||||
{Type::Button, "A" },
|
||||
{Type::Button, "B" },
|
||||
{Type::Button, "C" },
|
||||
{Type::Button, "X" },
|
||||
{Type::Button, "Y" },
|
||||
{Type::Button, "Z" },
|
||||
{Type::Control, "Mode" },
|
||||
{Type::Control, "Start"}
|
||||
};
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::connected(uint port) -> uint {
|
||||
if(port == ID::Port::Controller1) return option.port.controller1.device();
|
||||
if(port == ID::Port::Controller2) return option.port.controller2.device();
|
||||
if(port == ID::Port::Extension) return option.port.extension.device();
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::connect(uint port, uint device) -> void {
|
||||
if(port == ID::Port::Controller1) controllerPort1.connect(option.port.controller1.device(device));
|
||||
if(port == ID::Port::Controller2) controllerPort2.connect(option.port.controller2.device(device));
|
||||
if(port == ID::Port::Extension) extensionPort.connect(option.port.extension.device(device));
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::power() -> void {
|
||||
system.power(/* reset = */ false);
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::reset() -> void {
|
||||
system.power(/* reset = */ true);
|
||||
system.power(false);
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::run() -> void {
|
||||
@ -173,12 +42,4 @@ auto MegaDriveInterface::cheats(const vector<string>& list) -> void {
|
||||
cheat.assign(list);
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::options() -> Settings& {
|
||||
return option;
|
||||
}
|
||||
|
||||
auto MegaDriveInterface::properties() -> Settings& {
|
||||
return property;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,61 +2,24 @@
|
||||
|
||||
namespace higan::MegaDrive {
|
||||
|
||||
struct ID {
|
||||
enum : uint {
|
||||
System,
|
||||
MegaDrive,
|
||||
};
|
||||
|
||||
struct Port { enum : uint {
|
||||
Controller1,
|
||||
Controller2,
|
||||
Extension,
|
||||
};};
|
||||
|
||||
struct Device { enum : uint {
|
||||
None,
|
||||
ControlPad,
|
||||
FightingPad,
|
||||
};};
|
||||
};
|
||||
extern Interface* interface;
|
||||
|
||||
struct MegaDriveInterface : Interface {
|
||||
auto information() -> Information override;
|
||||
auto name() -> string override { return "Mega Drive"; }
|
||||
|
||||
auto display() -> Display override;
|
||||
auto color(uint32 color) -> uint64 override;
|
||||
|
||||
auto loaded() -> bool override;
|
||||
auto hashes() -> vector<string> override;
|
||||
auto manifests() -> vector<string> override;
|
||||
auto titles() -> vector<string> override;
|
||||
auto load() -> bool override;
|
||||
auto save() -> void override;
|
||||
auto root() -> Node::Object override;
|
||||
auto load(string tree = {}) -> void override;
|
||||
auto unload() -> void override;
|
||||
|
||||
auto ports() -> vector<Port> override;
|
||||
auto devices(uint port) -> vector<Device> override;
|
||||
auto inputs(uint device) -> vector<Input> override;
|
||||
|
||||
auto connected(uint port) -> uint override;
|
||||
auto connect(uint port, uint device) -> void override;
|
||||
auto save() -> void override;
|
||||
auto power() -> void override;
|
||||
auto reset() -> void override;
|
||||
auto run() -> void override;
|
||||
|
||||
auto serialize() -> serializer override;
|
||||
auto unserialize(serializer&) -> bool override;
|
||||
|
||||
auto cheats(const vector<string>& list) -> void override;
|
||||
|
||||
auto options() -> Settings& override;
|
||||
auto properties() -> Settings& override;
|
||||
};
|
||||
|
||||
#include "options.hpp"
|
||||
#include "properties.hpp"
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -1,20 +0,0 @@
|
||||
struct Options : Setting<> {
|
||||
struct Port : Setting<> { using Setting::Setting;
|
||||
struct Controller1 : Setting<> { using Setting::Setting;
|
||||
Setting<natural> device{this, "device", ID::Device::ControlPad};
|
||||
} controller1{this, "controller1"};
|
||||
|
||||
struct Controller2 : Setting<> { using Setting::Setting;
|
||||
Setting<natural> device{this, "device", ID::Device::ControlPad};
|
||||
} controller2{this, "controller2"};
|
||||
|
||||
struct Extension : Setting<> { using Setting::Setting;
|
||||
Setting<natural> device{this, "device", ID::Device::None};
|
||||
} extension{this, "extension"};
|
||||
} port{this, "port"};
|
||||
|
||||
Options() : Setting{"options"} {
|
||||
}
|
||||
};
|
||||
|
||||
extern Options option;
|
@ -1,16 +0,0 @@
|
||||
struct Properties : Setting<> {
|
||||
struct CPU : Setting<> { using Setting::Setting;
|
||||
Setting<natural> version{this, "version", 0};
|
||||
} cpu{this, "cpu"};
|
||||
|
||||
struct Memory : Setting<> { using Setting::Setting;
|
||||
Setting<string> type{this, "type", "ROM"};
|
||||
Setting<natural> size{this, "size", 2048};
|
||||
Setting<string> content{this, "content", "Boot"};
|
||||
} memory{this, "memory"};
|
||||
|
||||
Properties() : Setting{"system"} {
|
||||
}
|
||||
};
|
||||
|
||||
extern Properties property;
|
@ -25,12 +25,17 @@ namespace higan::MegaDrive {
|
||||
};
|
||||
|
||||
struct Thread : higan::Thread {
|
||||
auto create(auto (*entrypoint)() -> void, double frequency) -> void {
|
||||
higan::Thread::create(entrypoint, frequency);
|
||||
auto create(double frequency, function<void ()> entryPoint) -> void {
|
||||
higan::Thread::create(frequency, entryPoint);
|
||||
scheduler.append(*this);
|
||||
wait = 0;
|
||||
}
|
||||
|
||||
auto destroy() -> void {
|
||||
scheduler.remove(*this);
|
||||
higan::Thread::destroy();
|
||||
}
|
||||
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
|
@ -5,10 +5,6 @@ namespace higan::MegaDrive {
|
||||
PSG psg;
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto PSG::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), psg.main();
|
||||
}
|
||||
|
||||
auto PSG::main() -> void {
|
||||
stream->sample(SN76489::clock()[0]);
|
||||
step(16);
|
||||
@ -22,7 +18,9 @@ auto PSG::step(uint clocks) -> void {
|
||||
|
||||
auto PSG::power(bool reset) -> void {
|
||||
SN76489::power(0x1400);
|
||||
create(PSG::Enter, system.frequency() / 15.0);
|
||||
Thread::create(system.frequency() / 15.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
stream = audio.createStream(1, frequency() / 16.0);
|
||||
stream->addHighPassFilter( 20.0, Filter::Order::First);
|
||||
stream->addLowPassFilter (2840.0, Filter::Order::First);
|
||||
|
@ -1,7 +1,6 @@
|
||||
struct PSG : SN76489, Thread {
|
||||
shared_pointer<Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
||||
|
31
higan/md/system/display.cpp
Normal file
31
higan/md/system/display.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
Display display;
|
||||
|
||||
auto Display::load(Node::Object parent, Node::Object from) -> void {
|
||||
node = Node::Video::create("Display");
|
||||
node->type = "CRT";
|
||||
node->width = 1280;
|
||||
node->height = 480;
|
||||
node->aspect = 8.0 / 7.0;
|
||||
node->colors = 3 * (1 << 9);
|
||||
node->color = [&](auto index) { return color(index); };
|
||||
parent->append(node);
|
||||
}
|
||||
|
||||
auto Display::color(uint32 color) -> uint64 {
|
||||
uint R = color.bits(0, 2);
|
||||
uint G = color.bits(3, 5);
|
||||
uint B = color.bits(6, 8);
|
||||
uint M = color.bits(9,10);
|
||||
|
||||
uint lookup[3][8] = {
|
||||
{ 0, 29, 52, 70, 87, 101, 116, 130}, //shadow
|
||||
{ 0, 52, 87, 116, 144, 172, 206, 255}, //normal
|
||||
{130, 144, 158, 172, 187, 206, 228, 255}, //highlight
|
||||
};
|
||||
|
||||
uint64 r = image::normalize(lookup[M][R], 8, 16);
|
||||
uint64 g = image::normalize(lookup[M][G], 8, 16);
|
||||
uint64 b = image::normalize(lookup[M][B], 8, 16);
|
||||
|
||||
return r << 32 | g << 16 | b << 0;
|
||||
}
|
10
higan/md/system/display.hpp
Normal file
10
higan/md/system/display.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
struct Display {
|
||||
Node::Video node;
|
||||
shared_pointer<Screen> screen;
|
||||
|
||||
//display.cpp
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto color(uint32) -> uint64;
|
||||
};
|
||||
|
||||
extern Display display;
|
@ -6,10 +6,15 @@ System system;
|
||||
Scheduler scheduler;
|
||||
Random random;
|
||||
Cheat cheat;
|
||||
#include "display.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto System::run() -> void {
|
||||
if(scheduler.enter() == Scheduler::Event::Frame) vdp.refresh();
|
||||
|
||||
auto reset = resetButton->value;
|
||||
platform->input(resetButton);
|
||||
if(!reset && resetButton->value) power(true);
|
||||
}
|
||||
|
||||
auto System::runToSave() -> void {
|
||||
@ -20,30 +25,30 @@ auto System::runToSave() -> void {
|
||||
scheduler.synchronize(ym2612);
|
||||
}
|
||||
|
||||
auto System::load(Interface* interface, maybe<Region> region) -> bool {
|
||||
this->interface = interface;
|
||||
information = {};
|
||||
auto System::load(Node::Object from) -> void {
|
||||
if(node) unload();
|
||||
|
||||
auto document = BML::unserialize(interface->properties().serialize());
|
||||
auto system = document["system"];
|
||||
if(!cpu.load(system)) return false;
|
||||
if(!cartridge.load()) return false;
|
||||
node = Node::System::create(interface->name());
|
||||
node->load(from);
|
||||
|
||||
if(cartridge.region() == "NTSC-J") {
|
||||
information.region = Region::NTSCJ;
|
||||
information.frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
}
|
||||
if(cartridge.region() == "NTSC-U") {
|
||||
information.region = Region::NTSCU;
|
||||
information.frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
}
|
||||
if(cartridge.region() == "PAL") {
|
||||
information.region = Region::PAL;
|
||||
information.frequency = Constants::Colorburst::PAL * 12.0;
|
||||
}
|
||||
tmss = Node::Boolean::create("TMSS", false);
|
||||
Node::load(tmss, from);
|
||||
node->append(tmss);
|
||||
|
||||
serializeInit();
|
||||
return information.loaded = true;
|
||||
regionNode = Node::String::create("Region", "NTSC-J");
|
||||
regionNode->allowedValues = {"NTSC-J", "NTSC-U", "PAL"};
|
||||
Node::load(regionNode, from);
|
||||
node->append(regionNode);
|
||||
|
||||
resetButton = Node::Button::create("Reset");
|
||||
Node::load(resetButton, from);
|
||||
node->append(resetButton);
|
||||
|
||||
cartridge.load(node, from);
|
||||
controllerPort1.load(node, from);
|
||||
controllerPort2.load(node, from);
|
||||
extensionPort.load(node, from);
|
||||
display.load(node, from);
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
@ -52,17 +57,37 @@ auto System::save() -> void {
|
||||
|
||||
auto System::unload() -> void {
|
||||
cpu.peripherals.reset();
|
||||
controllerPort1.unload();
|
||||
controllerPort2.unload();
|
||||
extensionPort.unload();
|
||||
cartridge.unload();
|
||||
controllerPort1.disconnect();
|
||||
controllerPort2.disconnect();
|
||||
extensionPort.disconnect();
|
||||
cartridge.disconnect();
|
||||
}
|
||||
|
||||
auto System::power(bool reset) -> void {
|
||||
for(auto& setting : node->find<Node::Setting>()) setting->setLatch();
|
||||
information = {};
|
||||
|
||||
if(regionNode->latch() == "NTSC-J") {
|
||||
information.region = Region::NTSCJ;
|
||||
information.frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
}
|
||||
|
||||
if(regionNode->latch() == "NTSC-U") {
|
||||
information.region = Region::NTSCU;
|
||||
information.frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
}
|
||||
|
||||
if(regionNode->latch() == "PAL") {
|
||||
information.region = Region::PAL;
|
||||
information.frequency = Constants::Colorburst::PAL * 12.0;
|
||||
}
|
||||
|
||||
serializeInit();
|
||||
|
||||
video.reset(interface);
|
||||
video.setPalette();
|
||||
display.screen = video.createScreen(display.node, 1280, 480);
|
||||
audio.reset(interface);
|
||||
random.entropy(Random::Entropy::High);
|
||||
random.entropy(Random::Entropy::Low);
|
||||
|
||||
scheduler.reset();
|
||||
cartridge.power();
|
||||
@ -72,14 +97,6 @@ auto System::power(bool reset) -> void {
|
||||
psg.power(reset);
|
||||
ym2612.power(reset);
|
||||
scheduler.primary(cpu);
|
||||
|
||||
controllerPort1.power(ID::Port::Controller1);
|
||||
controllerPort2.power(ID::Port::Controller2);
|
||||
extensionPort.power(ID::Port::Extension);
|
||||
|
||||
controllerPort1.connect(option.port.controller1.device());
|
||||
controllerPort2.connect(option.port.controller2.device());
|
||||
extensionPort.connect(option.port.extension.device());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
struct System {
|
||||
enum class Region : uint {
|
||||
NTSCJ,
|
||||
NTSCU,
|
||||
PAL,
|
||||
};
|
||||
#include "display.hpp"
|
||||
|
||||
auto loaded() const -> bool { return information.loaded; }
|
||||
auto region() const -> Region { return information.region; }
|
||||
auto frequency() const -> double { return information.frequency; }
|
||||
struct System {
|
||||
Node::Object node;
|
||||
Node::Boolean tmss;
|
||||
Node::String regionNode;
|
||||
Node::Button resetButton;
|
||||
enum class Region : uint { NTSCJ, NTSCU, PAL };
|
||||
|
||||
inline auto region() const -> Region { return information.region; }
|
||||
inline auto frequency() const -> double { return information.frequency; }
|
||||
|
||||
auto run() -> void;
|
||||
auto runToSave() -> void;
|
||||
|
||||
auto load(Interface*, maybe<Region> = nothing) -> bool;
|
||||
auto save() -> void;
|
||||
auto load(Node::Object) -> void;
|
||||
auto unload() -> void;
|
||||
auto save() -> void;
|
||||
auto power(bool reset) -> void;
|
||||
|
||||
//serialization.cpp
|
||||
@ -25,10 +26,7 @@ struct System {
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
Interface* interface = nullptr;
|
||||
|
||||
struct Information {
|
||||
bool loaded = false;
|
||||
Region region = Region::NTSCJ;
|
||||
double frequency = Constants::Colorburst::NTSC * 15.0;
|
||||
uint serializeSize = 0;
|
||||
|
@ -11,10 +11,6 @@ VDP vdp;
|
||||
#include "sprite.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto VDP::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), vdp.main();
|
||||
}
|
||||
|
||||
auto VDP::main() -> void {
|
||||
scanline();
|
||||
|
||||
@ -78,11 +74,13 @@ auto VDP::step(uint clocks) -> void {
|
||||
auto VDP::refresh() -> void {
|
||||
auto data = output;
|
||||
if(!latch.overscan) data -= 16 * 1280;
|
||||
video.refresh(data, 1280 * sizeof(uint32), 1280, 480);
|
||||
display.screen->refresh(data, 1280 * sizeof(uint32), 1280, 480);
|
||||
}
|
||||
|
||||
auto VDP::power(bool reset) -> void {
|
||||
create(VDP::Enter, system.frequency() / 2.0);
|
||||
Thread::create(system.frequency() / 2.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
|
||||
output = buffer + 16 * 1280; //overscan offset
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
//Yamaha YM7101
|
||||
|
||||
struct VDP : Thread {
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
auto refresh() -> void;
|
||||
|
@ -9,10 +9,6 @@ YM2612 ym2612;
|
||||
#include "constants.cpp"
|
||||
#include "serialization.cpp"
|
||||
|
||||
auto YM2612::Enter() -> void {
|
||||
while(true) scheduler.synchronize(), ym2612.main();
|
||||
}
|
||||
|
||||
auto YM2612::main() -> void {
|
||||
sample();
|
||||
|
||||
@ -155,7 +151,9 @@ auto YM2612::step(uint clocks) -> void {
|
||||
}
|
||||
|
||||
auto YM2612::power(bool reset) -> void {
|
||||
create(YM2612::Enter, system.frequency() / 7.0);
|
||||
Thread::create(system.frequency() / 7.0, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
stream = audio.createStream(2, frequency() / 144.0);
|
||||
stream->addHighPassFilter( 20.0, Filter::Order::First);
|
||||
stream->addLowPassFilter (2840.0, Filter::Order::First);
|
||||
|
@ -3,7 +3,6 @@
|
||||
struct YM2612 : Thread {
|
||||
shared_pointer<Stream> stream;
|
||||
|
||||
static auto Enter() -> void;
|
||||
auto main() -> void;
|
||||
auto sample() -> void;
|
||||
auto step(uint clocks) -> void;
|
||||
|
@ -7,14 +7,14 @@ namespace higan::MasterSystem {
|
||||
#include "gamepad/gamepad.cpp"
|
||||
|
||||
Controller::Controller() {
|
||||
if(!handle()) create(1, [&] {
|
||||
if(!handle()) Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.peripherals.append(this);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
cpu.peripherals.removeValue(this);
|
||||
removeWhere(cpu.peripherals) == this;
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
|
@ -61,22 +61,14 @@ auto CPU::inSega(uint8 addr) -> uint8 {
|
||||
}
|
||||
|
||||
if(Model::GameGear()) {
|
||||
platform->input(controls.up);
|
||||
platform->input(controls.down);
|
||||
platform->input(controls.left);
|
||||
platform->input(controls.right);
|
||||
platform->input(controls.one);
|
||||
platform->input(controls.two);
|
||||
bool up = !controls.up->value;
|
||||
bool down = !controls.down->value;
|
||||
bool left = !controls.left->value;
|
||||
bool right = !controls.right->value;
|
||||
bool one = !controls.one->value;
|
||||
bool two = !controls.two->value;
|
||||
//todo: update logic to hold most recent pressed direction
|
||||
if(!up && !down) up = 1, down = 1;
|
||||
if(!left && !right) left = 1, right = 1;
|
||||
if(addr.bit(0) == 0) {
|
||||
controls.poll();
|
||||
bool up = !controls.upLatch;
|
||||
bool down = !controls.downLatch;
|
||||
bool left = !controls.leftLatch;
|
||||
bool right = !controls.rightLatch;
|
||||
bool one = !controls.one->value;
|
||||
bool two = !controls.two->value;
|
||||
return up << 0 | down << 1 | left << 2 | right << 3 | one << 4 | two << 5 | 1 << 6 | 1 << 7;
|
||||
} else {
|
||||
return 0xff;
|
||||
|
@ -5,7 +5,7 @@ namespace higan::MasterSystem {
|
||||
Interface* interface = nullptr;
|
||||
|
||||
auto AbstractInterface::root() -> Node::Object {
|
||||
return system.root;
|
||||
return system.node;
|
||||
}
|
||||
|
||||
auto AbstractInterface::load(string tree) -> void {
|
||||
|
@ -19,3 +19,35 @@ auto Controls::load(Node::Object parent, Node::Object from) -> void {
|
||||
start = Node::append<Node::Button>(parent, from, "Start");
|
||||
}
|
||||
}
|
||||
|
||||
auto Controls::poll() -> void {
|
||||
if(Model::SG1000() || Model::SC3000() || Model::MasterSystem()) {
|
||||
platform->input(pause);
|
||||
}
|
||||
|
||||
if(Model::MasterSystem()) {
|
||||
platform->input(reset);
|
||||
}
|
||||
|
||||
if(Model::GameGear()) {
|
||||
platform->input(up);
|
||||
platform->input(down);
|
||||
platform->input(left);
|
||||
platform->input(right);
|
||||
platform->input(one);
|
||||
platform->input(two);
|
||||
platform->input(start);
|
||||
|
||||
if(!(up->value & down->value)) {
|
||||
yHold = 0, upLatch = up->value, downLatch = down->value;
|
||||
} else if(!yHold) {
|
||||
yHold = 1, swap(upLatch, downLatch);
|
||||
}
|
||||
|
||||
if(!(left->value & right->value)) {
|
||||
xHold = 0, leftLatch = left->value, rightLatch = right->value;
|
||||
} else if(!xHold) {
|
||||
xHold = 1, swap(leftLatch, rightLatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,14 @@ struct Controls {
|
||||
Node::Button start;
|
||||
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
auto poll() -> void;
|
||||
|
||||
bool yHold = 0;
|
||||
bool upLatch = 0;
|
||||
bool downLatch = 0;
|
||||
bool xHold = 0;
|
||||
bool leftLatch = 0;
|
||||
bool rightLatch = 0;
|
||||
};
|
||||
|
||||
extern Controls controls;
|
||||
|
@ -23,50 +23,51 @@ auto System::runToSave() -> void {
|
||||
}
|
||||
|
||||
auto System::load(Node::Object from) -> void {
|
||||
if(root) {
|
||||
save();
|
||||
unload();
|
||||
}
|
||||
if(node) unload();
|
||||
|
||||
root = Node::System::create(interface->name());
|
||||
root->load(from);
|
||||
|
||||
regionNode = Node::String::create("Region", "NTSC");
|
||||
regionNode->allowedValues = {"NTSC", "PAL"};
|
||||
Node::load(regionNode, from);
|
||||
root->append(regionNode);
|
||||
|
||||
controls.load(root, from);
|
||||
display.load(root, from);
|
||||
cartridge.load(root, from);
|
||||
if(!MasterSystem::Model::GameGear()) {
|
||||
controllerPort1.load(root, from);
|
||||
controllerPort2.load(root, from);
|
||||
}
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!MasterSystem::Model::GameGear()) {
|
||||
cpu.peripherals.reset();
|
||||
controllerPort1.disconnect();
|
||||
controllerPort2.disconnect();
|
||||
}
|
||||
cartridge.disconnect();
|
||||
root = {};
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
information = {};
|
||||
if(interface->name() == "ColecoVision" ) information.model = Model::ColecoVision;
|
||||
if(interface->name() == "SG-1000" ) information.model = Model::SG1000;
|
||||
if(interface->name() == "SC-3000" ) information.model = Model::SC3000;
|
||||
if(interface->name() == "Master System") information.model = Model::MasterSystem;
|
||||
if(interface->name() == "Game Gear" ) information.model = Model::GameGear;
|
||||
for(auto& setting : root->find<Node::Setting>()) setting->setLatch();
|
||||
|
||||
node = Node::System::create(interface->name());
|
||||
node->load(from);
|
||||
|
||||
regionNode = Node::String::create("Region", "NTSC");
|
||||
regionNode->allowedValues = {"NTSC", "PAL"};
|
||||
Node::load(regionNode, from);
|
||||
node->append(regionNode);
|
||||
|
||||
controls.load(node, from);
|
||||
display.load(node, from);
|
||||
cartridge.load(node, from);
|
||||
if(!MasterSystem::Model::GameGear()) {
|
||||
controllerPort1.load(node, from);
|
||||
controllerPort2.load(node, from);
|
||||
}
|
||||
}
|
||||
|
||||
auto System::save() -> void {
|
||||
if(!node) return;
|
||||
cartridge.save();
|
||||
}
|
||||
|
||||
auto System::unload() -> void {
|
||||
if(!node) return;
|
||||
save();
|
||||
if(!MasterSystem::Model::GameGear()) {
|
||||
cpu.peripherals.reset();
|
||||
controllerPort1.disconnect();
|
||||
controllerPort2.disconnect();
|
||||
}
|
||||
cartridge.disconnect();
|
||||
node = {};
|
||||
}
|
||||
|
||||
auto System::power() -> void {
|
||||
for(auto& setting : node->find<Node::Setting>()) setting->setLatch();
|
||||
|
||||
if(regionNode->latch() == "NTSC") {
|
||||
information.region = Region::NTSC;
|
||||
@ -79,7 +80,7 @@ auto System::power() -> void {
|
||||
}
|
||||
|
||||
if(MasterSystem::Model::ColecoVision()) {
|
||||
if(auto fp = platform->open(root, "bios.rom", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(node, "bios.rom", File::Read, File::Required)) {
|
||||
fp->read(bios, 0x2000);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "display.hpp"
|
||||
|
||||
struct System {
|
||||
Node::Object root;
|
||||
Node::Object node;
|
||||
Node::String regionNode;
|
||||
|
||||
enum class Model : uint { ColecoVision, SG1000, SC3000, MasterSystem, GameGear };
|
||||
|
@ -34,7 +34,7 @@ auto Cartridge::connect(Node::Peripheral with) -> void {
|
||||
} else return;
|
||||
|
||||
loadCartridge(game.document);
|
||||
if(has.GameBoySlot); //todo
|
||||
if(has.GameBoySlot) icd.load(node, with);
|
||||
if(has.BSMemorySlot) bsmemory.load(node, with);
|
||||
if(has.SufamiTurboSlotA) sufamiturboA.load(node, with);
|
||||
if(has.SufamiTurboSlotB) sufamiturboB.load(node, with);
|
||||
@ -49,7 +49,7 @@ auto Cartridge::connect(Node::Peripheral with) -> void {
|
||||
auto Cartridge::disconnect() -> void {
|
||||
if(!node) return;
|
||||
|
||||
if(has.ICD) icd.unload();
|
||||
if(has.ICD) icd.disconnect();
|
||||
if(has.MCC) mcc.unload();
|
||||
if(has.Event) event.unload();
|
||||
if(has.SA1) sa1.unload();
|
||||
@ -73,18 +73,6 @@ auto Cartridge::disconnect() -> void {
|
||||
node = {};
|
||||
}
|
||||
|
||||
//deprecated
|
||||
auto Cartridge::loadGameBoy() -> bool {
|
||||
#if defined(CORE_GB)
|
||||
//invoked from ICD::load()
|
||||
information.sha256 = GameBoy::cartridge.hash();
|
||||
slotGameBoy.load(GameBoy::cartridge.manifest());
|
||||
loadCartridgeGameBoy(slotGameBoy.document);
|
||||
return true;
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
auto Cartridge::power(bool reset) -> void {
|
||||
if(has.ICD) icd.power();
|
||||
if(has.MCC) mcc.power();
|
||||
|
@ -53,10 +53,6 @@ private:
|
||||
Game slotSufamiTurboB;
|
||||
Markup::Node board;
|
||||
|
||||
//cartridge.cpp
|
||||
auto loadGameBoy() -> bool;
|
||||
auto loadBSMemory() -> bool;
|
||||
|
||||
//load.cpp
|
||||
auto loadBoard(string) -> Markup::Node;
|
||||
auto loadCartridge(Markup::Node) -> void;
|
||||
|
@ -7,7 +7,7 @@ auto Cartridge::loadBoard(string board) -> Markup::Node {
|
||||
if(board.beginsWith("EA-" )) board.replace("EA-", "SHVC-", 1L);
|
||||
if(board.beginsWith("WEI-" )) board.replace("WEI-", "SHVC-", 1L);
|
||||
|
||||
if(auto fp = platform->open(system.root, "boards.bml", File::Read, File::Required)) {
|
||||
if(auto fp = platform->open(system.node, "boards.bml", File::Read, File::Required)) {
|
||||
auto document = BML::unserialize(fp->reads());
|
||||
for(auto leaf : document.find("board")) {
|
||||
auto id = leaf.text();
|
||||
|
@ -13,14 +13,13 @@ namespace higan::SuperFamicom {
|
||||
#include "twin-tap/twin-tap.cpp"
|
||||
|
||||
Controller::Controller() {
|
||||
if(!handle()) create(1, [&] {
|
||||
if(!handle()) Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.peripherals.append(this);
|
||||
}
|
||||
|
||||
Controller::~Controller() {
|
||||
cpu.peripherals.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,6 @@ struct Gamepad : Controller {
|
||||
Node::Button start;
|
||||
|
||||
Gamepad(Node::Port, Node::Peripheral);
|
||||
|
||||
auto data() -> uint2;
|
||||
auto latch(bool data) -> void;
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
struct ControllerPort {
|
||||
Node::Port port;
|
||||
auto load(Node::Object parent, Node::Object from) -> void;
|
||||
auto load(Node::Object, Node::Object) -> void;
|
||||
|
||||
ControllerPort(string_view name);
|
||||
auto connect(Node::Peripheral) -> void;
|
||||
|
@ -73,7 +73,6 @@ auto ArmDSP::write(uint24 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto ArmDSP::unload() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
@ -85,8 +84,7 @@ auto ArmDSP::power() -> void {
|
||||
|
||||
auto ArmDSP::reset() -> void {
|
||||
ARM7TDMI::power();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(Frequency, [&] {
|
||||
Thread::create(Frequency, [&] {
|
||||
boot();
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
|
@ -70,13 +70,11 @@ auto EpsonRTC::initialize() -> void {
|
||||
}
|
||||
|
||||
auto EpsonRTC::unload() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto EpsonRTC::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(32'768 * 64, [&] {
|
||||
Thread::create(32'768 * 64, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -26,13 +26,11 @@ auto Event::unload() -> void {
|
||||
rom[1].reset();
|
||||
rom[2].reset();
|
||||
rom[3].reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto Event::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(1, [&] {
|
||||
Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -16,14 +16,12 @@ auto HitachiDSP::halt() -> void {
|
||||
auto HitachiDSP::unload() -> void {
|
||||
rom.reset();
|
||||
ram.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto HitachiDSP::power() -> void {
|
||||
HG51B::power();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(Frequency, [&] {
|
||||
Thread::create(Frequency, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -10,8 +10,8 @@ ICD icd;
|
||||
auto ICD::main() -> void {
|
||||
if(r6003 & 0x80) {
|
||||
GameBoy::system.run();
|
||||
step(GameBoy::system._clocksExecuted);
|
||||
GameBoy::system._clocksExecuted = 0;
|
||||
step(GameBoy::system.information.clocksExecuted);
|
||||
GameBoy::system.information.clocksExecuted = 0;
|
||||
} else { //DMG halted
|
||||
stream->sample(0.0, 0.0);
|
||||
step(2); //two clocks per audio sample
|
||||
@ -19,23 +19,39 @@ auto ICD::main() -> void {
|
||||
synchronize(cpu);
|
||||
}
|
||||
|
||||
auto ICD::load() -> bool {
|
||||
auto ICD::load(Node::Peripheral parent, Node::Peripheral from) -> void {
|
||||
port = Node::Port::create("Game Boy Slot", "Game Boy");
|
||||
port->attach = [&](auto node) { connect(node); };
|
||||
port->detach = [&](auto node) { disconnect(); };
|
||||
GameBoy::superGameBoy = this;
|
||||
GameBoy::system.load(&gameBoyInterface, GameBoy::System::Model::SuperGameBoy, cartridge.pathID());
|
||||
return cartridge.loadGameBoy();
|
||||
GameBoy::system.node = parent;
|
||||
GameBoy::system.information.model = GameBoy::System::Model::SuperGameBoy;
|
||||
GameBoy::cartridge.port = port;
|
||||
if(from = Node::load(port, from)) {
|
||||
if(auto node = from->find<Node::Peripheral>(0)) port->connect(node);
|
||||
}
|
||||
parent->append(port);
|
||||
}
|
||||
|
||||
auto ICD::unload() -> void {
|
||||
GameBoy::system.save();
|
||||
GameBoy::system.unload();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
auto ICD::connect(Node::Peripheral with) -> void {
|
||||
node = Node::Peripheral::create("Game Boy", port->type);
|
||||
node->load(with);
|
||||
|
||||
GameBoy::cartridge.node = node;
|
||||
GameBoy::cartridge.connect(with);
|
||||
power();
|
||||
}
|
||||
|
||||
auto ICD::disconnect() -> void {
|
||||
GameBoy::cartridge.disconnect();
|
||||
Thread::destroy();
|
||||
node = {};
|
||||
port = {};
|
||||
}
|
||||
|
||||
auto ICD::power() -> void {
|
||||
//SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create((Frequency ? Frequency : system.cpuFrequency()) / 5.0, [&] {
|
||||
Thread::create((Frequency ? Frequency : system.cpuFrequency()) / 5.0, [&] {
|
||||
while(true) {
|
||||
if(scheduler.synchronizing()) GameBoy::system.runToSave();
|
||||
scheduler.synchronize();
|
||||
@ -43,7 +59,7 @@ auto ICD::power() -> void {
|
||||
}
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
stream = audio.createStream(2, frequency() / 2.0);
|
||||
stream = higan::audio.createStream(2, frequency() / 2.0);
|
||||
stream->addHighPassFilter(20.0, Filter::Order::First);
|
||||
stream->addDCRemovalFilter();
|
||||
|
||||
@ -67,12 +83,18 @@ auto ICD::power() -> void {
|
||||
joyp14Lock = 0;
|
||||
pulseLock = true;
|
||||
|
||||
GameBoy::system.init();
|
||||
GameBoy::system.power();
|
||||
}
|
||||
|
||||
auto ICD::reset() -> void {
|
||||
create(ICD::Enter, (Frequency ? Frequency : system.cpuFrequency()) / 5.0);
|
||||
Thread::create(Frequency ? Frequency : system.cpuFrequency() / 5.0, [&] {
|
||||
while(true) {
|
||||
if(scheduler.synchronizing()) GameBoy::system.runToSave();
|
||||
scheduler.synchronize();
|
||||
main();
|
||||
}
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
||||
r6003 = 0x00;
|
||||
r6004 = 0xff;
|
||||
@ -94,7 +116,6 @@ auto ICD::reset() -> void {
|
||||
joyp14Lock = 0;
|
||||
pulseLock = true;
|
||||
|
||||
GameBoy::system.init();
|
||||
GameBoy::system.power();
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,21 @@
|
||||
#if defined(CORE_GB)
|
||||
|
||||
struct ICD : Platform, GameBoy::SuperGameBoyInterface, Thread {
|
||||
Node::Port port;
|
||||
Node::Peripheral node;
|
||||
shared_pointer<Stream> stream;
|
||||
|
||||
auto main() -> void;
|
||||
|
||||
auto load() -> bool;
|
||||
auto unload() -> void;
|
||||
auto load(Node::Peripheral, Node::Peripheral) -> void;
|
||||
auto connect(Node::Peripheral) -> void;
|
||||
auto disconnect() -> void;
|
||||
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;
|
||||
auto audio(const double* samples, uint channels) -> void override;
|
||||
auto input() -> uint8 override;
|
||||
|
||||
//interface.cpp
|
||||
auto lcdScanline() -> void override;
|
||||
@ -68,11 +71,10 @@ private:
|
||||
#else
|
||||
|
||||
struct ICD : Thread {
|
||||
auto init() -> void {}
|
||||
auto load() -> void {}
|
||||
auto unload() -> void {}
|
||||
auto load(Node::Peripheral, Node::Peripheral) -> void {}
|
||||
auto connect(Node::Peripheral) -> void {}
|
||||
auto disconnect() -> void {}
|
||||
auto power() -> void {}
|
||||
auto reset() -> void {}
|
||||
|
||||
auto readIO(uint24, uint8) -> uint8 { return 0; }
|
||||
auto writeIO(uint24, uint8) -> void { return; }
|
||||
|
@ -1,8 +1,8 @@
|
||||
auto ICD::audioSample(const double* samples, uint channels) -> void {
|
||||
auto ICD::audio(const double* samples, uint channels) -> void {
|
||||
stream->write(samples);
|
||||
}
|
||||
|
||||
auto ICD::inputPoll(uint port, uint device, uint id) -> int16 {
|
||||
auto ICD::input() -> uint8 {
|
||||
uint8 data = 0x00;
|
||||
switch(joypID) {
|
||||
case 0: data = ~r6004; break;
|
||||
@ -10,17 +10,5 @@ auto ICD::inputPoll(uint port, uint device, uint id) -> int16 {
|
||||
case 2: data = ~r6006; break;
|
||||
case 3: data = ~r6007; break;
|
||||
}
|
||||
|
||||
switch((GameBoy::Input)id) {
|
||||
case GameBoy::Input::Right: return data.bit(0);
|
||||
case GameBoy::Input::Left: return data.bit(1);
|
||||
case GameBoy::Input::Up: return data.bit(2);
|
||||
case GameBoy::Input::Down: return data.bit(3);
|
||||
case GameBoy::Input::A: return data.bit(4);
|
||||
case GameBoy::Input::B: return data.bit(5);
|
||||
case GameBoy::Input::Select: return data.bit(6);
|
||||
case GameBoy::Input::Start: return data.bit(7);
|
||||
}
|
||||
|
||||
return 0;
|
||||
return data;
|
||||
}
|
||||
|
@ -33,13 +33,11 @@ auto MSU1::main() -> void {
|
||||
auto MSU1::unload() -> void {
|
||||
dataFile.reset();
|
||||
audioFile.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto MSU1::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(44100, [&] {
|
||||
Thread::create(44100, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -36,14 +36,12 @@ auto NECDSP::writeRAM(uint24 addr, uint8 data) -> void {
|
||||
}
|
||||
|
||||
auto NECDSP::unload() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto NECDSP::power() -> void {
|
||||
uPD96050::power();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(Frequency, [&] {
|
||||
Thread::create(Frequency, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -117,14 +117,12 @@ auto SA1::unload() -> void {
|
||||
rom.reset();
|
||||
iram.reset();
|
||||
bwram.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto SA1::power() -> void {
|
||||
WDC65816::power();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(system.cpuFrequency(), [&] {
|
||||
Thread::create(system.cpuFrequency(), [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -27,13 +27,11 @@ auto SharpRTC::initialize() -> void {
|
||||
}
|
||||
|
||||
auto SharpRTC::unload() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto SharpRTC::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(1, [&] {
|
||||
Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -28,12 +28,11 @@ auto SPC7110::unload() -> void {
|
||||
prom.reset();
|
||||
drom.reset();
|
||||
ram.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto SPC7110::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(21'477'272, [&] {
|
||||
Thread::create(21'477'272, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -26,14 +26,12 @@ auto SuperFX::main() -> void {
|
||||
auto SuperFX::unload() -> void {
|
||||
rom.reset();
|
||||
ram.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto SuperFX::power() -> void {
|
||||
GSU::power();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(Frequency, [&] {
|
||||
Thread::create(Frequency, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
@ -7,14 +7,13 @@ namespace higan::SuperFamicom {
|
||||
#include <sfc/expansion/satellaview/satellaview.cpp>
|
||||
|
||||
Expansion::Expansion() {
|
||||
if(!handle()) create(1, [&] {
|
||||
if(!handle()) Thread::create(1, [&] {
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.peripherals.append(this);
|
||||
}
|
||||
|
||||
Expansion::~Expansion() {
|
||||
cpu.peripherals.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,8 @@ namespace higan::SuperFamicom {
|
||||
|
||||
Interface* interface = nullptr;
|
||||
|
||||
auto SuperFamicomInterface::name() -> string {
|
||||
return "Super Famicom";
|
||||
}
|
||||
|
||||
auto SuperFamicomInterface::root() -> Node::Object {
|
||||
return system.root;
|
||||
return system.node;
|
||||
}
|
||||
|
||||
auto SuperFamicomInterface::load(string tree) -> void {
|
||||
|
@ -5,7 +5,7 @@ namespace higan::SuperFamicom {
|
||||
extern Interface* interface;
|
||||
|
||||
struct SuperFamicomInterface : Interface {
|
||||
auto name() -> string override;
|
||||
auto name() -> string override { return "Super Famicom"; }
|
||||
|
||||
auto root() -> Node::Object override;
|
||||
auto load(string tree = {}) -> void override;
|
||||
|
@ -26,16 +26,8 @@ namespace higan::SuperFamicom {
|
||||
extern Cheat cheat;
|
||||
|
||||
struct Thread : higan::Thread {
|
||||
auto create(double frequency, function<void ()> entryPoint) -> void {
|
||||
higan::Thread::create(frequency, entryPoint);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
auto destroy() -> void {
|
||||
scheduler.remove(*this);
|
||||
higan::Thread::destroy();
|
||||
}
|
||||
|
||||
inline auto create(double frequency, function<void ()> entryPoint) -> void;
|
||||
inline auto destroy() -> void;
|
||||
inline auto synchronize(Thread& thread) -> void {
|
||||
if(clock() >= thread.clock()) scheduler.resume(thread);
|
||||
}
|
||||
@ -64,6 +56,20 @@ namespace higan::SuperFamicom {
|
||||
|
||||
#include <sfc/memory/memory-inline.hpp>
|
||||
#include <sfc/ppu/counter/counter-inline.hpp>
|
||||
|
||||
auto Thread::create(double frequency, function<void ()> entryPoint) -> void {
|
||||
if(handle()) destroy();
|
||||
higan::Thread::create(frequency, entryPoint);
|
||||
scheduler.append(*this);
|
||||
}
|
||||
|
||||
auto Thread::destroy() -> void {
|
||||
//Thread may not be a coprocessor or peripheral; in which case this will be a no-op
|
||||
removeWhere(cpu.coprocessors) == this;
|
||||
removeWhere(cpu.peripherals) == this;
|
||||
scheduler.remove(*this);
|
||||
higan::Thread::destroy();
|
||||
}
|
||||
}
|
||||
|
||||
#include <sfc/interface/interface.hpp>
|
||||
|
@ -125,13 +125,11 @@ auto BSMemory::disconnect() -> void {
|
||||
}
|
||||
|
||||
memory.reset();
|
||||
cpu.coprocessors.removeValue(this);
|
||||
Thread::destroy();
|
||||
}
|
||||
|
||||
auto BSMemory::power() -> void {
|
||||
cpu.coprocessors.removeValue(this);
|
||||
create(1'000'000, [&] { //microseconds
|
||||
Thread::create(1'000'000, [&] { //microseconds
|
||||
while(true) scheduler.synchronize(), main();
|
||||
});
|
||||
cpu.coprocessors.append(this);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user