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:
Tim Allen 2019-02-15 19:26:17 +11:00
parent 27ac13ef84
commit a1e9bed75a
136 changed files with 1450 additions and 1672 deletions

View File

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

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

View File

@ -33,7 +33,7 @@ struct Scheduler {
}
auto remove(Thread& thread) -> void {
_threads.removeValue(&thread);
removeWhere(_threads) == &thread;
}
auto enter(Mode mode = Mode::Run) -> Event {

View File

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

View File

@ -1,7 +1,6 @@
struct APU : Thread, MMIO {
shared_pointer<Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto power() -> void;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,5 +5,5 @@ auto Cartridge::serialize(serializer& s) -> void {
s.integer(bootromEnable);
mapper->serialize(s);
if(mapper) mapper->serialize(s);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
struct PPU : Thread, MMIO {
static auto Enter() -> void;
auto main() -> void;
auto stat() -> void;
auto coincidence() -> bool;

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

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

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

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

View File

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

View File

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

View File

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

View File

@ -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)"]}) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
struct PSG : SN76489, Thread {
shared_pointer<Stream> stream;
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;

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

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
//Yamaha YM7101
struct VDP : Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto refresh() -> void;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,6 @@ struct Gamepad : Controller {
Node::Button start;
Gamepad(Node::Port, Node::Peripheral);
auto data() -> uint2;
auto latch(bool data) -> void;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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