Update to v099r08 release.

byuu says:

Changelog:
- nall/vfs work 100% completed; even SGB games load now
- emulation cores now call load() for the base cartridges as well
- updated port/device handling; portmask is gone; device ID bug should
  be resolved now
- SNES controller port 1 multitap option was removed
- added support for 128KiB SNES PPU VRAM (for now, edit sfc/ppu/ppu.hpp
  VRAM::size=0x10000; to enable)

Overall, nall/vfs was a huge success!! We've substantially reduced
the amount of boilerplate code everywhere, while still allowing (even
easier than before) support for RAM-based game loading/saving. All of
nall/stream is dead and buried.

I am considering removing Emulator::Interface::Medium::id and/or
bootable flag. Or at least, doing something different with it. The
values for the non-bootable GB/BS/ST entries duplicate the ID that is
supposed to be unique. They are for GB/GBC and WS/WSC. Maybe I'll use
this as the hardware revision selection ID, and then gut non-bootable
options. There's really no reason for that to be there. I think at one
point I was using it to generate library tabs for non-bootable systems,
but we don't do that anymore anyway.

Emulator::Interface::load() may not need the required flag anymore ... it
doesn't really do anything right now anyway.

I have a few reasons for having the cores load the base cartridge. Most
importantly, it is going to enable a special mode for the WonderSwan /
WonderSwan Color in the future. If we ever get the IPLROMs dumped ... it's
possible to boot these systems with no games inserted to set user profile
information and such. There are also other systems that may accept being
booted without a cartridge. To reach this state, you would load a game and
then cancel the load dialog. Right now, this results in games not loading.

The second reason is this prevents nasty crashes when loading fails. So
if you're missing a required manifest, the emulator won't die a violent
death anymore. It's able to back out at any point.

The third reason is consistency: loading the base cartridge works the
same as the slot cartridges.

The fourth reason is Emulator::Interface::open(uint pathID)
values. Before, the GB, SB, GBC modes were IDs 1,2,3 respectively. This
complicated things because you had to pass the correct ID. But now
instead, Emulator::Interface::load() returns maybe<uint> that is nothing
when no game is selected, and a pathID for a valid game. And now open()
can take this ID to access this game's folder contents.

The downside, which is temporary, is that command-line loading is
currently broken. But I do intend on restoring it. In fact, I want to do
better than before and allow multi-cart booting from the command-line by
specifying the base cartridge and then slot cartridges. The idea should
be pretty simple: keep a queue of pending filenames that we fill from
the command-line and/or drag-and-drop operations on the main window,
and then empty out the queue or prompt for load dialogs from the UI
when booting a system. This also might be a bit more unorthodox compared
to the traditional emulator design of "loadGame(filename)", but ... oh
well. It's easy enough still.

The port/device changes are fun. We simplified things quite a bit. The
portmask stuff is gone entirely. While ports and devices keep IDs,
this is really just sugar-coating so UIs can use for(auto& port :
emulator->ports) and access port.id; rather than having to use for(auto
n : range(emulator->ports)) { auto& port = emulator->ports[n]; ... };
but they should otherwise generally be identical to the order they appear
in their respective ranges. Still, don't rely on that.

Input::id is gone. There was no point since we also got rid of the nasty
Input::order vector. Since I was in here, I went ahead and caved on the
pedantics and renamed Input::guid to Input::userData.

I removed the SNES controller port 1 multitap option. Basically, the only
game that uses this is N-warp Daisakusen and, no offense to d4s, it's
not really a good game anyway. It's just a quick demo to show 8-players
on the SNES. But in the UI, all it does is confuse people into wasting
time mapping a controller they're never going to use, and they're going
to wonder which port to use. If more compelling use cases for 8-players
comes about, we can reconsider this. I left all the code to support this
in place, so all you have to do is uncomment one line to enable it again.

We now have dsnes emulation! :D
If you change PPU::VRAM::size to 0x10000 (words), then you should now
have 128KiB of VRAM. Even better, it serializes the used-VRAM size,
so your save states shouldn't crash on you if you swap between the two
(though if you try this, you're nuts.)

Note that this option does break commercial software. Yoshi's Island in
particular. This game is setting A15 on some PPU register writes, but
not on others. The end result of this is things break horribly in-game.

Also, this option is causing a very tiny speed hit for obvious reasons
with the variable masking value (I'm even using size-1 for now.) Given
how niche this is, I may just leave it a compile-time constant to avoid
the overhead cost. Otherwise, if we keep the option, then it'll go into
Super Famicom.sys/manifest.bml ... I'll flesh that out in the near-future.

----

Finally, some fun for my OCD ... my monitor suddenly cut out on me
in the middle of working on this WIP, about six hours in of non-stop
work. Had to hit a bunch of ctrl+alt+fN commands (among other things)
and trying to log in headless on another TTY to do issue commands,
trying to recover the display. Finally power cycled the monitor and it
came back up. So all my typing ended up going to who knows where.

Usually this sort of thing terrifies me enough that I scrap a WIP and
start over to ensure I didn't screw anything up during the crashed screen
when hitting keys randomly.

Obviously, everything compiles and appears to work fine. And I know
it's extremely paranoid, but OCD isn't logical, so ... I'm going
to go over every line of the 100KiB r07->r08 diff looking for any
corruption/errors/whatever.

----

Review finished.

r08 diff review notes:
- fc/controller/gamepad/gamepad.cpp:
  use uint device = ID::Device::Gamepad; not id = ...;
- gb/cartridge/cartridge.hpp:
  remove redundant uint _pathID; (in Information::pathID already)
- gb/cartridge/cartridge.hpp:
  pull sha256 inside Information
- sfc/cartridge/load/cpp:
  add " - Slot (A,B)" to interface->load("Sufami Turbo"); to be more
  descriptive
- sfc/controller/gamepad/gamepad.cpp:
  use uint device = ID::Device::Gamepad; not id = ...;
- sfc/interface/interface.cpp:
  remove n variable from the Multitap device input generation loop
  (now unused)
- sfc/interface/interface.hpp:
  put struct Port above struct Device like the other classes
- ui-tomoko:
  cheats.bml is reading from/writing to mediumPaths(0) [system folder
  instead of game folder]
- ui-tomoko:
  instead of mediumPaths(1) - call emulator->metadataPathID() or something
  like that
This commit is contained in:
Tim Allen 2016-06-24 22:16:53 +10:00
parent ccd8878d75
commit f48b332c83
77 changed files with 612 additions and 1143 deletions

View File

@ -1,7 +1,6 @@
include ../nall/GNUmakefile
target := tomoko
# target := loki
# console := true
flags += -I. -I.. -O3

View File

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

View File

@ -27,13 +27,11 @@ struct Interface {
struct Device {
uint id;
uint portmask;
string name;
struct Input {
uint id;
uint type; //0 = digital, 1 = analog (relative), 2 = rumble
string name;
uintptr guid; //user data field
uintptr userData;
};
vector<Input> inputs;
};
@ -48,7 +46,7 @@ struct Interface {
struct Bind {
virtual auto path(uint) -> string { return ""; }
virtual auto open(uint, string, vfs::file::mode, bool) -> vfs::shared::file { return {}; }
virtual auto load(uint, string, string, bool) -> void {}
virtual auto load(uint, string, string, bool) -> maybe<uint> { return nothing; }
virtual auto videoRefresh(const uint32*, uint, uint, uint) -> void {}
virtual auto audioSample(const double*, uint) -> void {}
virtual auto inputPoll(uint, uint, uint) -> int16 { return 0; }
@ -61,7 +59,7 @@ struct Interface {
//callback bindings (provided by user interface)
auto path(uint id) -> string { return bind->path(id); }
auto open(uint id, string name, vfs::file::mode mode, bool required = false) -> vfs::shared::file { return bind->open(id, name, mode, required); }
auto load(uint id, string name, string type, bool required = false) -> void { return bind->load(id, name, type, required); }
auto load(uint id, string name, string type, bool required = false) -> maybe<uint> { return bind->load(id, name, type, required); }
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void { return bind->videoRefresh(data, pitch, width, height); }
auto audioSample(const double* samples, uint channels) -> void { return bind->audioSample(samples, channels); }
auto inputPoll(uint port, uint device, uint input) -> int16 { return bind->inputPoll(port, device, input); }
@ -84,7 +82,7 @@ struct Interface {
//media interface
virtual auto loaded() -> bool { return false; }
virtual auto sha256() -> string { return ""; }
virtual auto load(uint id) -> void {}
virtual auto load(uint id) -> bool { return false; }
virtual auto save() -> void {}
virtual auto unload() -> void {}
@ -114,4 +112,12 @@ struct Interface {
auto videoColor(uint16 r, uint16 g, uint16 b) -> uint32;
};
//nall/vfs shorthand constants for open(), load()
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
}

View File

@ -42,22 +42,22 @@ Board::Board(Markup::Node& document) {
if(chrram.size) chrram.data = new uint8_t[chrram.size]();
if(prgrom.name = prom["name"].text()) {
if(auto fp = interface->open(ID::Famicom, prgrom.name, File::Read, File::Required)) {
if(auto fp = interface->open(cartridge.pathID(), prgrom.name, File::Read, File::Required)) {
fp->read(prgrom.data, min(prgrom.size, fp->size()));
}
}
if(prgram.name = pram["name"].text()) {
if(auto fp = interface->open(ID::Famicom, prgram.name, File::Read)) {
if(auto fp = interface->open(cartridge.pathID(), prgram.name, File::Read)) {
fp->read(prgram.data, min(prgram.size, fp->size()));
}
}
if(chrrom.name = crom["name"].text()) {
if(auto fp = interface->open(ID::Famicom, chrrom.name, File::Read, File::Required)) {
if(auto fp = interface->open(cartridge.pathID(), chrrom.name, File::Read, File::Required)) {
fp->read(chrrom.data, min(chrrom.size, fp->size()));
}
}
if(chrram.name = cram["name"].text()) {
if(auto fp = interface->open(ID::Famicom, chrram.name, File::Read)) {
if(auto fp = interface->open(cartridge.pathID(), chrram.name, File::Read)) {
fp->read(chrram.data, min(chrram.size, fp->size()));
}
}
@ -70,13 +70,13 @@ auto Board::save() -> void {
auto document = BML::unserialize(cartridge.manifest());
if(auto name = document["board/prg/ram/name"].text()) {
if(auto fp = interface->open(ID::Famicom, name, File::Write)) {
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
fp->write(prgram.data, prgram.size);
}
}
if(auto name = document["board/chr/ram/name"].text()) {
if(auto fp = interface->open(ID::Famicom, name, File::Write)) {
if(auto fp = interface->open(cartridge.pathID(), name, File::Write)) {
fp->write(chrram.data, chrram.size);
}
}

View File

@ -15,7 +15,11 @@ auto Cartridge::main() -> void {
}
auto Cartridge::load() -> bool {
if(auto fp = interface->open(ID::Famicom, "manifest.bml", File::Read, File::Required)) {
if(auto pathID = interface->load(ID::Famicom, "Famicom", "fc", File::Required)) {
information.pathID = pathID();
}
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else {
return false;

View File

@ -5,6 +5,7 @@ struct Cartridge : Thread {
static auto Enter() -> void;
auto main() -> void;
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
@ -19,6 +20,7 @@ struct Cartridge : Thread {
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;

View File

@ -3,7 +3,7 @@ Gamepad::Gamepad(bool port) : Controller(port) {
auto Gamepad::data() -> uint3 {
if(counter >= 8) return 1;
if(latched == 1) return interface->inputPoll(port, Device::Gamepad, A);
if(latched == 1) return interface->inputPoll(port, ID::Device::Gamepad, A);
switch(counter++) {
case 0: return a;
@ -24,7 +24,7 @@ auto Gamepad::latch(bool data) -> void {
counter = 0;
if(latched == 0) {
auto id = Device::Gamepad;
auto id = ID::Device::Gamepad;
a = interface->inputPoll(port, id, A);
b = interface->inputPoll(port, id, B);
select = interface->inputPoll(port, id, Select);

View File

@ -7,12 +7,7 @@
#include <processor/r6502/r6502.hpp>
namespace Famicom {
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
using File = Emulator::File;
struct Thread {
~Thread() {

View File

@ -21,32 +21,29 @@ Interface::Interface() {
media.append({ID::Famicom, "Famicom", "fc", true});
{ Device device{0, ID::ControllerPort1 | ID::ControllerPort2, "None"};
devices.append(device);
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
{ Device device{ID::Device::None, "None"};
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
{ Device device{1, ID::ControllerPort1 | ID::ControllerPort2, "Gamepad"};
device.inputs.append({0, 0, "Up" });
device.inputs.append({1, 0, "Down" });
device.inputs.append({2, 0, "Left" });
device.inputs.append({3, 0, "Right" });
device.inputs.append({4, 0, "B" });
device.inputs.append({5, 0, "A" });
device.inputs.append({6, 0, "Select"});
device.inputs.append({7, 0, "Start" });
devices.append(device);
{ Device device{ID::Device::Gamepad, "Gamepad"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
ports.append({0, "Port 1"});
ports.append({1, "Port 2"});
for(auto& device : devices) {
for(auto& port : ports) {
if(device.portmask & (1 << port.id)) {
port.devices.append(device);
}
}
}
ports.append(move(controllerPort1));
ports.append(move(controllerPort2));
}
auto Interface::manifest() -> string {
@ -128,8 +125,8 @@ auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::load(uint id) -> void {
system.load();
auto Interface::load(uint id) -> bool {
return system.load();
}
auto Interface::save() -> void {

View File

@ -1,50 +1,54 @@
namespace Famicom {
struct ID {
enum : uint { //paths
enum : uint {
System,
Famicom,
};
enum : uint { //bitmasks
ControllerPort1 = 1,
ControllerPort2 = 2,
ExpansionPort = 4,
};
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
struct Device { enum : uint {
None,
Gamepad,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool;
auto sha256() -> string;
auto load(uint id) -> void;
auto save() -> void;
auto unload() -> void;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto connect(uint port, uint device) -> void;
auto power() -> void;
auto reset() -> void;
auto run() -> void;
auto connect(uint port, uint device) -> void override;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const lstring&) -> void;
auto cheatSet(const lstring&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
private:
vector<Device> devices;
};
struct Settings {

View File

@ -8,34 +8,34 @@ auto Peripherals::unload() -> void {
}
auto Peripherals::reset() -> void {
connect(Port::Controller1, settings.controllerPort1);
connect(Port::Controller2, settings.controllerPort2);
connect(ID::Port::Controller1, settings.controllerPort1);
connect(ID::Port::Controller2, settings.controllerPort2);
}
auto Peripherals::connect(uint port, uint device) -> void {
if(port == Port::Controller1) {
if(port == ID::Port::Controller1) {
settings.controllerPort1 = device;
if(!system.loaded()) return;
delete controllerPort1;
switch(device) { default:
case Device::None: controllerPort1 = new Controller(0); break;
case Device::Gamepad: controllerPort1 = new Gamepad(0); break;
case ID::Device::None: controllerPort1 = new Controller(0); break;
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
}
}
if(port == Port::Controller2) {
if(port == ID::Port::Controller2) {
settings.controllerPort2 = device;
if(!system.loaded()) return;
delete controllerPort2;
switch(device) { default:
case Device::None: controllerPort2 = new Controller(1); break;
case Device::Gamepad: controllerPort2 = new Gamepad(1); break;
case ID::Device::None: controllerPort2 = new Controller(1); break;
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
}
}
if(port == Port::Expansion) {
if(port == ID::Port::Expansion) {
settings.expansionPort = device;
if(!system.loaded()) return;
}

View File

@ -1,18 +1,3 @@
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
struct Device { enum : uint {
None,
//controller port peripherals
Gamepad,
//expansion port peripherals
};};
struct Peripherals {
auto unload() -> void;
auto reset() -> void;

View File

@ -28,7 +28,7 @@ auto System::load() -> bool {
return false;
}
auto document = BML::unserialize(information.manifest);
cartridge.load();
if(!cartridge.load()) return false;
serializeInit();
return _loaded = true;
}

View File

@ -16,11 +16,26 @@ Cartridge cartridge;
auto Cartridge::load(System::Revision revision) -> bool {
information = Information();
if(revision == System::Revision::GameBoy) information.mode = ID::GameBoy;
if(revision == System::Revision::SuperGameBoy) information.mode = ID::SuperGameBoy;
if(revision == System::Revision::GameBoyColor) information.mode = ID::GameBoyColor;
if(auto fp = interface->open(mode(), "manifest.bml", File::Read, File::Required)) {
switch(revision) {
case System::Revision::GameBoy:
if(auto pathID = interface->load(ID::GameBoy, "Game Boy", "gb", true)) {
information.pathID = pathID();
} else return false;
break;
case System::Revision::SuperGameBoy:
if(auto pathID = interface->load(ID::SuperGameBoy, "Game Boy", "gb", true)) {
information.pathID = pathID();
} else return false;
break;
case System::Revision::GameBoyColor:
if(auto pathID = interface->load(ID::GameBoyColor, "Game Boy Color", "gbc", true)) {
information.pathID = pathID();
} else return false;
break;
}
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
@ -51,12 +66,12 @@ auto Cartridge::load(System::Revision revision) -> bool {
ramdata = allocate<uint8>(ramsize, 0xff);
if(auto name = rom["name"].text()) {
if(auto fp = interface->open(mode(), name, File::Read, File::Required)) {
if(auto fp = interface->open(pathID(), name, File::Read, File::Required)) {
fp->read(romdata, min(romsize, fp->size()));
}
}
if(auto name = ram["name"].text()) {
if(auto fp = interface->open(mode(), name, File::Read, File::Optional)) {
if(auto fp = interface->open(pathID(), name, File::Read, File::Optional)) {
fp->read(ramdata, min(ramsize, fp->size()));
}
}
@ -85,7 +100,7 @@ auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto name = document["board/ram/name"].text()) {
if(auto fp = interface->open(mode(), name, File::Write)) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(ramdata, ramsize);
}
}

View File

@ -1,7 +1,7 @@
struct Cartridge : MMIO, property<Cartridge> {
auto pathID() const -> uint { return information.pathID; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
auto mode() const -> uint { return information.mode; }
auto load(System::Revision revision) -> bool;
auto save() -> void;
@ -43,9 +43,9 @@ struct Cartridge : MMIO, property<Cartridge> {
};
struct Information {
uint pathID = 0;
string manifest;
string title;
uint mode = 0;
Mapper mapper = Mapper::Unknown;
boolean ram;
@ -57,6 +57,7 @@ struct Cartridge : MMIO, property<Cartridge> {
uint ramsize = 0;
} information;
uint _pathID = 0;
readonly<string> sha256;
uint8* romdata = nullptr;

View File

@ -7,12 +7,7 @@
#include <processor/lr35902/lr35902.hpp>
namespace GameBoy {
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
using File = Emulator::File;
struct Thread {
~Thread() {

View File

@ -23,20 +23,21 @@ Interface::Interface() {
media.append({ID::GameBoy, "Game Boy", "gb" , true});
media.append({ID::GameBoyColor, "Game Boy Color", "gbc", true});
{
Device device{0, ID::Device, "Controller"};
device.inputs.append({0, 0, "Up" });
device.inputs.append({1, 0, "Down" });
device.inputs.append({2, 0, "Left" });
device.inputs.append({3, 0, "Right" });
device.inputs.append({4, 0, "B" });
device.inputs.append({5, 0, "A" });
device.inputs.append({6, 0, "Select"});
device.inputs.append({7, 0, "Start" });
devices.append(device);
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
hardwarePort.devices.append(device);
}
ports.append({0, "Device", {devices[0]}});
ports.append(move(hardwarePort));
}
auto Interface::manifest() -> string {
@ -124,10 +125,11 @@ auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::load(uint id) -> void {
if(id == ID::GameBoy) system.load(System::Revision::GameBoy);
if(id == ID::SuperGameBoy) system.load(System::Revision::SuperGameBoy);
if(id == ID::GameBoyColor) system.load(System::Revision::GameBoyColor);
auto Interface::load(uint id) -> bool {
if(id == ID::GameBoy) return system.load(System::Revision::GameBoy);
if(id == ID::SuperGameBoy) return system.load(System::Revision::SuperGameBoy);
if(id == ID::GameBoyColor) return system.load(System::Revision::GameBoyColor);
return false;
}
auto Interface::save() -> void {

View File

@ -8,46 +8,41 @@ struct ID {
GameBoyColor,
};
enum : uint {
SystemManifest,
GameBoyBootROM,
SuperGameBoyBootROM,
GameBoyColorBootROM,
struct Port { enum : uint {
Hardware,
};};
Manifest,
ROM,
RAM,
};
enum : uint {
Device = 1,
};
struct Device { enum : uint {
Controls,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool;
auto sha256() -> string;
auto load(uint id) -> void;
auto save() -> void;
auto unload() -> void;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void;
auto reset() -> void;
auto run() -> void;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const lstring&) -> void;
auto cheatSet(const lstring&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
@ -64,9 +59,6 @@ struct Interface : Emulator::Interface {
auto lcdScanline() -> void;
auto lcdOutput(uint2 color) -> void;
auto joypWrite(bool p15, bool p14) -> void;
private:
vector<Device> devices;
};
struct Settings {

View File

@ -39,7 +39,7 @@ auto System::load(Revision revision) -> bool {
}
}
cartridge.load(revision);
if(!cartridge.load(revision)) return false;
serializeInit();
return _loaded = true;
}

View File

@ -26,7 +26,11 @@ Cartridge::~Cartridge() {
auto Cartridge::load() -> bool {
information = Information();
if(auto fp = interface->open(ID::GameBoyAdvance, "manifest.bml", File::Read, File::Required)) {
if(auto pathID = interface->load(ID::GameBoyAdvance, "Game Boy Advance", "gba", File::Required)) {
information.pathID = pathID();
} else return false;
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
@ -39,7 +43,7 @@ auto Cartridge::load() -> bool {
if(auto node = document["board/rom"]) {
mrom.size = min(32 * 1024 * 1024, node["size"].natural());
if(auto fp = interface->open(ID::GameBoyAdvance, node["name"].text(), File::Read, File::Required)) {
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read, File::Required)) {
fp->read(mrom.data, mrom.size);
}
}
@ -51,7 +55,7 @@ auto Cartridge::load() -> bool {
sram.mask = sram.size - 1;
for(auto n : range(sram.size)) sram.data[n] = 0xff;
if(auto fp = interface->open(ID::GameBoyAdvance, node["name"].text(), File::Read)) {
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
fp->read(sram.data, sram.size);
}
}
@ -65,7 +69,7 @@ auto Cartridge::load() -> bool {
eeprom.test = mrom.size > 16 * 1024 * 1024 ? 0x0dffff00 : 0x0d000000;
for(auto n : range(eeprom.size)) eeprom.data[n] = 0xff;
if(auto fp = interface->open(ID::GameBoyAdvance, node["name"].text(), File::Read)) {
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
fp->read(eeprom.data, eeprom.size);
}
}
@ -81,7 +85,7 @@ auto Cartridge::load() -> bool {
if(!flash.id && flash.size == 64 * 1024) flash.id = 0x1cc2;
if(!flash.id && flash.size == 128 * 1024) flash.id = 0x09c2;
if(auto fp = interface->open(ID::GameBoyAdvance, node["name"].text(), File::Read)) {
if(auto fp = interface->open(pathID(), node["name"].text(), File::Read)) {
fp->read(flash.data, flash.size);
}
}
@ -94,7 +98,7 @@ auto Cartridge::load() -> bool {
auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto node = document["board/ram"]) {
if(auto fp = interface->open(ID::GameBoyAdvance, node["name"].text(), File::Write)) {
if(auto fp = interface->open(pathID(), node["name"].text(), File::Write)) {
if(node["type"].text() == "sram") fp->write(sram.data, sram.size);
if(node["type"].text() == "eeprom") fp->write(eeprom.data, eeprom.size);
if(node["type"].text() == "flash") fp->write(flash.data, flash.size);

View File

@ -1,11 +1,13 @@
struct Cartridge {
#include "memory.hpp"
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto manifest() const -> string { return information.manifest; }
auto title() const -> string { return information.title; }
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;

View File

@ -7,6 +7,8 @@
#include <processor/arm/arm.hpp>
namespace GameBoyAdvance {
using File = Emulator::File;
enum : uint { //mode flags for bus read, write:
Nonsequential = 1, //N cycle
Sequential = 2, //S cycle
@ -19,13 +21,6 @@ namespace GameBoyAdvance {
Signed = 256, //sign extended
};
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
struct Thread {
~Thread() {
if(thread) co_delete(thread);

View File

@ -21,22 +21,24 @@ Interface::Interface() {
media.append({ID::GameBoyAdvance, "Game Boy Advance", "gba", true});
{ Device device{0, ID::Device, "Controller"};
device.inputs.append({ 0, 0, "Up" });
device.inputs.append({ 1, 0, "Down" });
device.inputs.append({ 2, 0, "Left" });
device.inputs.append({ 3, 0, "Right" });
device.inputs.append({ 4, 0, "B" });
device.inputs.append({ 5, 0, "A" });
device.inputs.append({ 6, 0, "L" });
device.inputs.append({ 7, 0, "R" });
device.inputs.append({ 8, 0, "Select"});
device.inputs.append({ 9, 0, "Start" });
device.inputs.append({10, 2, "Rumble"});
devices.append(device);
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::Controls, "Controls"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "L" });
device.inputs.append({0, "R" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
device.inputs.append({2, "Rumble"});
hardwarePort.devices.append(device);
}
ports.append({0, "Device", {devices[0]}});
ports.append(move(hardwarePort));
}
auto Interface::manifest() -> string {
@ -85,8 +87,8 @@ auto Interface::loaded() -> bool {
return system.loaded();
}
auto Interface::load(uint id) -> void {
system.load();
auto Interface::load(uint id) -> bool {
return system.load();
}
auto Interface::save() -> void {

View File

@ -6,39 +6,42 @@ struct ID {
GameBoyAdvance,
};
enum : uint {
Device = 1,
};
struct Port { enum : uint {
Hardware,
};};
struct Device { enum : uint {
Controls,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool;
auto load(uint id) -> void;
auto save() -> void;
auto unload() -> void;
auto loaded() -> bool override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto power() -> void;
auto reset() -> void;
auto run() -> void;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
private:
vector<Device> devices;
};
struct Settings {

View File

@ -44,7 +44,7 @@ auto System::load() -> bool {
}
}
cartridge.load();
if(!cartridge.load()) return false;
serializeInit();
return _loaded = true;
}

View File

@ -28,8 +28,10 @@ auto Cartridge::title() const -> string {
auto Cartridge::load() -> bool {
information = Information();
has = Has();
_sha256 = "";
_region = Region::NTSC;
if(auto pathID = interface->load(ID::SuperFamicom, "Super Famicom", "sfc", File::Required)) {
information.pathID = pathID();
} else return false;
if(auto fp = interface->open(ID::SuperFamicom, "manifest.bml", File::Read, File::Required)) {
information.manifest.cartridge = fp->reads();
@ -39,12 +41,12 @@ auto Cartridge::load() -> bool {
//Game Boy
if(cartridge.has.ICD2) {
_sha256 = ""; //Game Boy cartridge not loaded yet: set later via loadGameBoy()
information.sha256 = ""; //Game Boy cartridge not loaded yet: set later via loadGameBoy()
}
//BS Memory
else if(cartridge.has.MCC && cartridge.has.BSMemorySlot) {
_sha256 = Hash::SHA256(bsmemory.memory.data(), bsmemory.memory.size()).digest();
information.sha256 = Hash::SHA256(bsmemory.memory.data(), bsmemory.memory.size()).digest();
}
//Sufami Turbo
@ -52,7 +54,7 @@ auto Cartridge::load() -> bool {
Hash::SHA256 sha;
sha.data(sufamiturboA.rom.data(), sufamiturboA.rom.size());
sha.data(sufamiturboB.rom.data(), sufamiturboB.rom.size());
_sha256 = sha.digest();
information.sha256 = sha.digest();
}
//Super Famicom
@ -76,7 +78,7 @@ auto Cartridge::load() -> bool {
buffer = necdsp.firmware();
sha.data(buffer.data(), buffer.size());
//finalize hash
_sha256 = sha.digest();
information.sha256 = sha.digest();
}
rom.writeProtect(true);
@ -84,35 +86,40 @@ auto Cartridge::load() -> bool {
return true;
}
auto Cartridge::loadGameBoy() -> void {
auto Cartridge::loadGameBoy() -> bool {
#if defined(SFC_SUPERGAMEBOY)
//invoked from ICD2::load()
_sha256 = GameBoy::interface->sha256();
information.sha256 = GameBoy::interface->sha256();
information.manifest.gameBoy = GameBoy::interface->manifest();
information.title.gameBoy = GameBoy::interface->title();
#endif
loadGameBoy(BML::unserialize(information.manifest.gameBoy));
return true;
#endif
return false;
}
auto Cartridge::loadBSMemory() -> void {
auto Cartridge::loadBSMemory() -> bool {
if(auto fp = interface->open(ID::BSMemory, "manifest.bml", File::Read, File::Required)) {
information.manifest.bsMemory = fp->reads();
} else return;
} else return false;
loadBSMemory(BML::unserialize(information.manifest.bsMemory));
return true;
}
auto Cartridge::loadSufamiTurboA() -> void {
auto Cartridge::loadSufamiTurboA() -> bool {
if(auto fp = interface->open(ID::SufamiTurboA, "manifest.bml", File::Read, File::Required)) {
information.manifest.sufamiTurboA = fp->reads();
} else return;
} else return false;
loadSufamiTurboA(BML::unserialize(information.manifest.sufamiTurboA));
return true;
}
auto Cartridge::loadSufamiTurboB() -> void {
auto Cartridge::loadSufamiTurboB() -> bool {
if(auto fp = interface->open(ID::SufamiTurboB, "manifest.bml", File::Read, File::Required)) {
information.manifest.sufamiTurboB = fp->reads();
} else return;
} else return false;
loadSufamiTurboB(BML::unserialize(information.manifest.sufamiTurboB));
return true;
}
auto Cartridge::save() -> void {

View File

@ -1,8 +1,9 @@
struct Cartridge {
enum class Region : uint { NTSC, PAL };
auto sha256() const -> string { return _sha256; }
auto region() const -> Region { return _region; }
auto pathID() const -> uint { return information.pathID; }
auto sha256() const -> string { return information.sha256; }
auto region() const -> Region { return information.region; }
auto manifest() const -> string;
auto title() const -> string;
@ -16,6 +17,10 @@ struct Cartridge {
MappedRAM ram;
struct Information {
uint pathID = 0;
string sha256;
Region region = Region::NTSC;
struct Manifest {
string cartridge;
string gameBoy;
@ -57,10 +62,10 @@ struct Cartridge {
private:
//cartridge.cpp
auto loadGameBoy() -> void;
auto loadBSMemory() -> void;
auto loadSufamiTurboA() -> void;
auto loadSufamiTurboB() -> void;
auto loadGameBoy() -> bool;
auto loadBSMemory() -> bool;
auto loadSufamiTurboA() -> bool;
auto loadSufamiTurboB() -> bool;
//load.cpp
auto loadCartridge(Markup::Node) -> void;
@ -89,7 +94,7 @@ private:
auto loadOBC1(Markup::Node) -> void;
auto loadMSU1(Markup::Node) -> void;
auto loadMemory(MappedRAM&, Markup::Node, bool required, uint id = 1) -> void;
auto loadMemory(MappedRAM&, Markup::Node, bool required, maybe<uint> id = nothing) -> void;
auto loadMap(Markup::Node, SuperFamicom::Memory&) -> void;
auto loadMap(Markup::Node, const function<uint8 (uint24, uint8)>&, const function<void (uint24, uint8)>&) -> void;
@ -114,10 +119,7 @@ private:
auto saveSDD1(Markup::Node) -> void;
auto saveOBC1(Markup::Node) -> void;
auto saveMemory(MappedRAM&, Markup::Node, uint = 1) -> void;
string _sha256;
Region _region = Region::NTSC;
auto saveMemory(MappedRAM&, Markup::Node, maybe<uint> = nothing) -> void;
friend class Interface;
friend class ICD2;

View File

@ -1,10 +1,18 @@
auto Cartridge::loadCartridge(Markup::Node node) -> void {
information.title.cartridge = node["information/title"].text();
auto board = node["board"];
_region = board["region"].text() == "pal" ? Region::PAL : Region::NTSC;
information.region = board["region"].text() == "pal" ? Region::PAL : Region::NTSC;
if(board["mcc"] || board["bsmemory"]) interface->load(ID::BSMemory, "BS Memory", "bs");
if(board["sufamiturbo"]) interface->load(ID::SufamiTurboA, "Sufami Turbo", "st");
if(board["mcc"] || board["bsmemory"]) {
if(auto pathID = interface->load(ID::BSMemory, "BS Memory", "bs")) {
bsmemory.pathID = pathID();
}
}
if(board["sufamiturbo"]) {
if(auto pathID = interface->load(ID::SufamiTurboA, "Sufami Turbo", "st")) {
sufamiturboA.pathID = pathID();
}
}
if(auto node = board["rom"]) loadROM(node);
if(auto node = board["ram"]) loadRAM(node);
@ -35,23 +43,27 @@ auto Cartridge::loadBSMemory(Markup::Node node) -> void {
information.title.bsMemory = node["information/title"].text();
bsmemory.readonly = (node["board/rom/type"].text() == "mrom");
loadMemory(bsmemory.memory, node["board/rom"], File::Required, ID::BSMemory);
loadMemory(bsmemory.memory, node["board/rom"], File::Required, bsmemory.pathID);
}
auto Cartridge::loadSufamiTurboA(Markup::Node node) -> void {
information.title.sufamiTurboA = node["information/title"].text();
loadMemory(sufamiturboA.rom, node["board/rom"], File::Required, ID::SufamiTurboA);
loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, ID::SufamiTurboA);
loadMemory(sufamiturboA.rom, node["board/rom"], File::Required, sufamiturboA.pathID);
loadMemory(sufamiturboA.ram, node["board/ram"], File::Optional, sufamiturboA.pathID);
if(node["board/linkable"]) interface->load(ID::SufamiTurboB, "Sufami Turbo", "st");
if(node["board/linkable"]) {
if(auto pathID = interface->load(ID::SufamiTurboB, "Sufami Turbo", "st")) {
sufamiturboB.pathID = pathID();
}
}
}
auto Cartridge::loadSufamiTurboB(Markup::Node node) -> void {
information.title.sufamiTurboB = node["information/title"].text();
loadMemory(sufamiturboB.rom, node["board/rom"], File::Required, ID::SufamiTurboB);
loadMemory(sufamiturboB.ram, node["board/ram"], File::Optional, ID::SufamiTurboB);
loadMemory(sufamiturboB.rom, node["board/rom"], File::Required, sufamiturboB.pathID);
loadMemory(sufamiturboB.ram, node["board/ram"], File::Optional, sufamiturboB.pathID);
}
//
@ -302,11 +314,12 @@ auto Cartridge::loadMSU1(Markup::Node node) -> void {
//
auto Cartridge::loadMemory(MappedRAM& ram, Markup::Node node, bool required, uint id) -> void {
auto Cartridge::loadMemory(MappedRAM& ram, Markup::Node node, bool required, maybe<uint> id) -> void {
if(!id) id = pathID();
auto name = node["name"].text();
auto size = node["size"].natural();
ram.allocate(size);
if(auto fp = interface->open(id, name, File::Read, required)) {
if(auto fp = interface->open(id(), name, File::Read, required)) {
fp->read(ram.data(), ram.size());
}
}

View File

@ -23,11 +23,11 @@ auto Cartridge::saveBSMemory(Markup::Node node) -> void {
}
auto Cartridge::saveSufamiTurboA(Markup::Node node) -> void {
saveMemory(sufamiturboA.ram, node["board/ram"], ID::SufamiTurboA);
saveMemory(sufamiturboA.ram, node["board/ram"], sufamiturboA.pathID);
}
auto Cartridge::saveSufamiTurboB(Markup::Node node) -> void {
saveMemory(sufamiturboB.ram, node["board/ram"], ID::SufamiTurboB);
saveMemory(sufamiturboB.ram, node["board/ram"], sufamiturboB.pathID);
}
//
@ -113,11 +113,12 @@ auto Cartridge::saveOBC1(Markup::Node node) -> void {
//
auto Cartridge::saveMemory(MappedRAM& memory, Markup::Node node, uint id) -> void {
auto Cartridge::saveMemory(MappedRAM& memory, Markup::Node node, maybe<uint> id) -> void {
if(!id) id = pathID();
if(!node || node["volatile"]) return;
auto name = node["name"].text();
auto size = node["size"].natural();
if(auto fp = interface->open(id, name, File::Write)) {
if(auto fp = interface->open(id(), name, File::Write)) {
fp->write(memory.data(), memory.size());
}
}

View File

@ -9,7 +9,7 @@ Gamepad::Gamepad(bool port) : Controller(port) {
auto Gamepad::data() -> uint2 {
if(counter >= 16) return 1;
if(latched == 1) return interface->inputPoll(port, Device::Gamepad, B);
if(latched == 1) return interface->inputPoll(port, ID::Device::Gamepad, B);
//note: D-pad physically prevents up+down and left+right from being pressed at the same time
switch(counter++) {
@ -36,7 +36,7 @@ auto Gamepad::latch(bool data) -> void {
counter = 0;
if(latched == 0) {
auto id = Device::Gamepad;
auto id = ID::Device::Gamepad;
b = interface->inputPoll(port, id, B);
y = interface->inputPoll(port, id, Y);
select = interface->inputPoll(port, id, Select);

View File

@ -1,7 +1,7 @@
Justifier::Justifier(bool port, bool chained):
Controller(port),
chained(chained),
device(chained == false ? Device::Justifier : Device::Justifiers)
device(!chained ? ID::Device::Justifier : ID::Device::Justifiers)
{
create(Controller::Enter, 21'477'272);
latched = 0;

View File

@ -64,10 +64,10 @@ auto Mouse::latch(bool data) -> void {
latched = data;
counter = 0;
x = interface->inputPoll(port, Device::Mouse, X); //-n = left, 0 = center, +n = right
y = interface->inputPoll(port, Device::Mouse, Y); //-n = up, 0 = center, +n = down
l = interface->inputPoll(port, Device::Mouse, Left);
r = interface->inputPoll(port, Device::Mouse, Right);
x = interface->inputPoll(port, ID::Device::Mouse, X); //-n = left, 0 = center, +n = right
y = interface->inputPoll(port, ID::Device::Mouse, Y); //-n = up, 0 = center, +n = down
l = interface->inputPoll(port, ID::Device::Mouse, Left);
r = interface->inputPoll(port, ID::Device::Mouse, Right);
dx = x < 0; //0 = right, 1 = left
dy = y < 0; //0 = down, 1 = up

View File

@ -24,9 +24,9 @@ auto Multitap::data() -> uint2 {
port2 = 3; //controller 4
}
bool data1 = interface->inputPoll(port, Device::Multitap, port1 * 12 + index);
bool data2 = interface->inputPoll(port, Device::Multitap, port2 * 12 + index);
return (data2 << 1) | (data1 << 0);
bool data1 = interface->inputPoll(port, ID::Device::Multitap, port1 * 12 + index);
bool data2 = interface->inputPoll(port, ID::Device::Multitap, port2 * 12 + index);
return data2 << 1 | data1 << 0;
}
auto Multitap::latch(bool data) -> void {

View File

@ -53,8 +53,8 @@ auto SuperScope::main() -> void {
if(next < prev) {
//Vcounter wrapped back to zero; update cursor coordinates for start of new frame
int nx = interface->inputPoll(port, Device::SuperScope, X);
int ny = interface->inputPoll(port, Device::SuperScope, Y);
int nx = interface->inputPoll(port, ID::Device::SuperScope, X);
int ny = interface->inputPoll(port, ID::Device::SuperScope, Y);
nx += x;
ny += y;
x = max(-16, min(256 + 16, nx));
@ -73,7 +73,7 @@ auto SuperScope::data() -> uint2 {
if(counter == 0) {
//turbo is a switch; toggle is edge sensitive
bool newturbo = interface->inputPoll(port, Device::SuperScope, Turbo);
bool newturbo = interface->inputPoll(port, ID::Device::SuperScope, Turbo);
if(newturbo && !oldturbo) {
turbo = !turbo; //toggle state
sprite->setPixels(turbo ? Resource::Sprite::CrosshairRed : Resource::Sprite::CrosshairGreen);
@ -83,7 +83,7 @@ auto SuperScope::data() -> uint2 {
//trigger is a button
//if turbo is active, trigger is level sensitive; otherwise, it is edge sensitive
trigger = false;
bool newtrigger = interface->inputPoll(port, Device::SuperScope, Trigger);
bool newtrigger = interface->inputPoll(port, ID::Device::SuperScope, Trigger);
if(newtrigger && (turbo || !triggerlock)) {
trigger = true;
triggerlock = true;
@ -92,11 +92,11 @@ auto SuperScope::data() -> uint2 {
}
//cursor is a button; it is always level sensitive
cursor = interface->inputPoll(port, Device::SuperScope, Cursor);
cursor = interface->inputPoll(port, ID::Device::SuperScope, Cursor);
//pause is a button; it is always edge sensitive
pause = false;
bool newpause = interface->inputPoll(port, Device::SuperScope, Pause);
bool newpause = interface->inputPoll(port, ID::Device::SuperScope, Pause);
if(newpause && !pauselock) {
pause = true;
pauselock = true;

View File

@ -33,14 +33,13 @@ auto ICD2::main() -> void {
auto ICD2::init() -> void {
}
auto ICD2::load() -> void {
auto ICD2::load() -> bool {
bind = GameBoy::interface->bind;
hook = GameBoy::interface->hook;
GameBoy::interface->bind = this;
GameBoy::interface->hook = this;
interface->load(ID::GameBoy, "Game Boy", "gb");
GameBoy::interface->load(GameBoy::ID::SuperGameBoy);
cartridge.loadGameBoy();
return cartridge.loadGameBoy();
}
auto ICD2::unload() -> void {

View File

@ -7,7 +7,7 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Cothread {
auto main() -> void;
auto init() -> void;
auto load() -> void;
auto load() -> bool;
auto unload() -> void;
auto power() -> void;
auto reset(bool soft = false) -> void;
@ -17,7 +17,8 @@ struct ICD2 : Emulator::Interface::Bind, GameBoy::Interface::Hook, Cothread {
auto lcdOutput(uint2 color) -> void override;
auto joypWrite(bool p15, bool p14) -> void override;
auto load(uint id, string name, string type, bool required) -> void override;
auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override;
auto load(uint id, string name, string type, bool required) -> maybe<uint> override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;

View File

@ -85,39 +85,17 @@ auto ICD2::joypWrite(bool p15, bool p14) -> void {
packetLock = true;
}
auto ICD2::load(uint id, string name, string type, bool required) -> void {
auto ICD2::open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file {
//redirect system folder to cartridge folder:
//expects "GameBoy.sys"; but this would be "Super Famicom.sys"; redirect to "Super Game Boy.sfc/"
if(id == ID::System) id = cartridge.pathID();
return interface->open(id, name, mode, required);
}
/*
auto ICD2::loadRequest(uint id, string name, bool required) -> void {
if(id == GameBoy::ID::SystemManifest) {
interface->loadRequest(ID::SuperGameBoyManifest, name, required);
}
if(id == GameBoy::ID::SuperGameBoyBootROM) {
interface->loadRequest(ID::SuperGameBoyBootROM, name, required);
}
if(id == GameBoy::ID::Manifest) {
interface->loadRequest(ID::GameBoyManifest, name, required);
}
if(id == GameBoy::ID::ROM) {
interface->loadRequest(ID::GameBoyROM, name, required);
}
if(id == GameBoy::ID::RAM) {
interface->loadRequest(ID::GameBoyRAM, name, required);
}
auto ICD2::load(uint id, string name, string type, bool required) -> maybe<uint> {
return interface->load(id, name, type, required);
}
auto ICD2::saveRequest(uint id, string name) -> void {
if(id == GameBoy::ID::RAM) {
interface->saveRequest(ID::GameBoyRAM, name);
}
}
*/
auto ICD2::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
}

View File

@ -25,105 +25,106 @@ Interface::Interface() {
media.append({ID::SuperFamicom, "BS Memory", "bs", false});
media.append({ID::SuperFamicom, "Sufami Turbo", "st", false});
{ Device device{0, ID::ControllerPort1 | ID::ControllerPort2 | ID::ExpansionPort, "None"};
devices.append(device);
Port controllerPort1{ID::Port::Controller1, "Controller Port 1"};
Port controllerPort2{ID::Port::Controller2, "Controller Port 2"};
Port expansionPort{ID::Port::Expansion, "Expansion Port"};
{ Device device{ID::Device::None, "None"};
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
expansionPort.devices.append(device);
}
{ Device device{1, ID::ControllerPort1 | ID::ControllerPort2, "Gamepad"};
device.inputs.append({ 0, 0, "Up" });
device.inputs.append({ 1, 0, "Down" });
device.inputs.append({ 2, 0, "Left" });
device.inputs.append({ 3, 0, "Right" });
device.inputs.append({ 4, 0, "B" });
device.inputs.append({ 5, 0, "A" });
device.inputs.append({ 6, 0, "Y" });
device.inputs.append({ 7, 0, "X" });
device.inputs.append({ 8, 0, "L" });
device.inputs.append({ 9, 0, "R" });
device.inputs.append({10, 0, "Select"});
device.inputs.append({11, 0, "Start" });
devices.append(device);
{ Device device{ID::Device::Gamepad, "Gamepad"};
device.inputs.append({0, "Up" });
device.inputs.append({0, "Down" });
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
device.inputs.append({0, "B" });
device.inputs.append({0, "A" });
device.inputs.append({0, "Y" });
device.inputs.append({0, "X" });
device.inputs.append({0, "L" });
device.inputs.append({0, "R" });
device.inputs.append({0, "Select"});
device.inputs.append({0, "Start" });
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
{ Device device{2, ID::ControllerPort1 | ID::ControllerPort2, "Multitap"};
{ Device device{ID::Device::Multitap, "Multitap"};
for(uint p = 1, n = 0; p <= 4; p++, n += 12) {
device.inputs.append({n + 0, 0, {"Port ", p, " - ", "Up" }});
device.inputs.append({n + 1, 0, {"Port ", p, " - ", "Down" }});
device.inputs.append({n + 2, 0, {"Port ", p, " - ", "Left" }});
device.inputs.append({n + 3, 0, {"Port ", p, " - ", "Right" }});
device.inputs.append({n + 4, 0, {"Port ", p, " - ", "B" }});
device.inputs.append({n + 5, 0, {"Port ", p, " - ", "A" }});
device.inputs.append({n + 6, 0, {"Port ", p, " - ", "Y" }});
device.inputs.append({n + 7, 0, {"Port ", p, " - ", "X" }});
device.inputs.append({n + 8, 0, {"Port ", p, " - ", "L" }});
device.inputs.append({n + 9, 0, {"Port ", p, " - ", "R" }});
device.inputs.append({n + 10, 0, {"Port ", p, " - ", "Select"}});
device.inputs.append({n + 11, 0, {"Port ", p, " - ", "Start" }});
device.inputs.append({0, {"Port ", p, " - ", "Up" }});
device.inputs.append({0, {"Port ", p, " - ", "Down" }});
device.inputs.append({0, {"Port ", p, " - ", "Left" }});
device.inputs.append({0, {"Port ", p, " - ", "Right" }});
device.inputs.append({0, {"Port ", p, " - ", "B" }});
device.inputs.append({0, {"Port ", p, " - ", "A" }});
device.inputs.append({0, {"Port ", p, " - ", "Y" }});
device.inputs.append({0, {"Port ", p, " - ", "X" }});
device.inputs.append({0, {"Port ", p, " - ", "L" }});
device.inputs.append({0, {"Port ", p, " - ", "R" }});
device.inputs.append({0, {"Port ", p, " - ", "Select"}});
device.inputs.append({0, {"Port ", p, " - ", "Start" }});
}
devices.append(device);
//controllerPort1.devices.append(device); //not used by any commercial software (only homebrew)
controllerPort2.devices.append(device);
}
{ Device device{3, ID::ControllerPort1 | ID::ControllerPort2, "Mouse"};
device.inputs.append({0, 1, "X-axis"});
device.inputs.append({1, 1, "Y-axis"});
device.inputs.append({2, 0, "Left" });
device.inputs.append({3, 0, "Right" });
devices.append(device);
{ Device device{ID::Device::Mouse, "Mouse"};
device.inputs.append({1, "X-axis"});
device.inputs.append({1, "Y-axis"});
device.inputs.append({0, "Left" });
device.inputs.append({0, "Right" });
controllerPort1.devices.append(device);
controllerPort2.devices.append(device);
}
{ Device device{4, ID::ControllerPort2, "Super Scope"};
device.inputs.append({0, 1, "X-axis" });
device.inputs.append({1, 1, "Y-axis" });
device.inputs.append({2, 0, "Trigger"});
device.inputs.append({3, 0, "Cursor" });
device.inputs.append({4, 0, "Turbo" });
device.inputs.append({5, 0, "Pause" });
devices.append(device);
{ Device device{ID::Device::SuperScope, "Super Scope"};
device.inputs.append({1, "X-axis" });
device.inputs.append({1, "Y-axis" });
device.inputs.append({0, "Trigger"});
device.inputs.append({0, "Cursor" });
device.inputs.append({0, "Turbo" });
device.inputs.append({0, "Pause" });
controllerPort2.devices.append(device);
}
{ Device device{5, ID::ControllerPort2, "Justifier"};
device.inputs.append({0, 1, "X-axis" });
device.inputs.append({1, 1, "Y-axis" });
device.inputs.append({2, 0, "Trigger"});
device.inputs.append({3, 0, "Start" });
devices.append(device);
{ Device device{ID::Device::Justifier, "Justifier"};
device.inputs.append({1, "X-axis" });
device.inputs.append({1, "Y-axis" });
device.inputs.append({0, "Trigger"});
device.inputs.append({0, "Start" });
controllerPort2.devices.append(device);
}
{ Device device{6, ID::ControllerPort2, "Justifiers"};
device.inputs.append({0, 1, "Port 1 - X-axis" });
device.inputs.append({1, 1, "Port 1 - Y-axis" });
device.inputs.append({2, 0, "Port 1 - Trigger"});
device.inputs.append({3, 0, "Port 1 - Start" });
device.inputs.append({4, 1, "Port 2 - X-axis" });
device.inputs.append({5, 1, "Port 2 - Y-axis" });
device.inputs.append({6, 0, "Port 2 - Trigger"});
device.inputs.append({7, 0, "Port 2 - Start" });
devices.append(device);
{ Device device{ID::Device::Justifiers, "Justifiers"};
device.inputs.append({1, "Port 1 - X-axis" });
device.inputs.append({1, "Port 1 - Y-axis" });
device.inputs.append({0, "Port 1 - Trigger"});
device.inputs.append({0, "Port 1 - Start" });
device.inputs.append({1, "Port 2 - X-axis" });
device.inputs.append({1, "Port 2 - Y-axis" });
device.inputs.append({0, "Port 2 - Trigger"});
device.inputs.append({0, "Port 2 - Start" });
controllerPort2.devices.append(device);
}
{ Device device{7, ID::ExpansionPort, "Satellaview"};
devices.append(device);
{ Device device{ID::Device::Satellaview, "Satellaview"};
expansionPort.devices.append(device);
}
{ Device device{8, ID::ExpansionPort, "Super Disc"};
devices.append(device);
{ Device device{ID::Device::SuperDisc, "Super Disc"};
expansionPort.devices.append(device);
}
{ Device device{9, ID::ExpansionPort, "21fx"};
devices.append(device);
{ Device device{ID::Device::S21FX, "21fx"};
expansionPort.devices.append(device);
}
ports.append({0, "Controller Port 1"});
ports.append({1, "Controller Port 2"});
ports.append({2, "Expansion Port"});
for(auto& device : devices) {
for(auto& port : ports) {
if(device.portmask & (1 << port.id)) {
port.devices.append(device);
}
}
}
ports.append(move(controllerPort1));
ports.append(move(controllerPort2));
ports.append(move(expansionPort));
}
auto Interface::manifest() -> string {
@ -183,11 +184,12 @@ auto Interface::sha256() -> string {
return cartridge.sha256();
}
auto Interface::load(uint id) -> void {
if(id == ID::SuperFamicom) system.load();
if(id == ID::BSMemory) cartridge.loadBSMemory();
if(id == ID::SufamiTurboA) cartridge.loadSufamiTurboA();
if(id == ID::SufamiTurboB) cartridge.loadSufamiTurboB();
auto Interface::load(uint id) -> bool {
if(id == ID::SuperFamicom) return system.load();
if(id == ID::BSMemory) return cartridge.loadBSMemory();
if(id == ID::SufamiTurboA) return cartridge.loadSufamiTurboA();
if(id == ID::SufamiTurboB) return cartridge.loadSufamiTurboB();
return false;
}
auto Interface::save() -> void {

View File

@ -8,22 +8,27 @@ struct ID {
BSMemory,
SufamiTurboA,
SufamiTurboB,
//deprecated
SuperGameBoyManifest,
SuperGameBoyBootROM,
GameBoyManifest,
GameBoyROM,
GameBoyRAM,
};
enum : uint {
//device ports (bitmask)
ControllerPort1 = 1,
ControllerPort2 = 2,
ExpansionPort = 4,
};
struct Device { enum : uint {
None,
Gamepad,
Multitap,
Mouse,
SuperScope,
Justifier,
Justifiers,
Satellaview,
SuperDisc,
S21FX,
};};
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
};
struct Interface : Emulator::Interface {
@ -31,37 +36,35 @@ struct Interface : Emulator::Interface {
Interface();
auto manifest() -> string;
auto title() -> string;
auto videoFrequency() -> double;
auto videoColors() -> uint32;
auto videoColor(uint32 color) -> uint64;
auto audioFrequency() -> double;
auto manifest() -> string override;
auto title() -> string override;
auto videoFrequency() -> double override;
auto videoColors() -> uint32 override;
auto videoColor(uint32 color) -> uint64 override;
auto audioFrequency() -> double override;
auto loaded() -> bool;
auto sha256() -> string;
auto load(uint id) -> void;
auto save() -> void;
auto unload() -> void;
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
auto connect(uint port, uint device) -> void;
auto power() -> void;
auto reset() -> void;
auto run() -> void;
auto connect(uint port, uint device) -> void override;
auto power() -> void override;
auto reset() -> void override;
auto run() -> void override;
auto rtc() -> bool;
auto rtcsync() -> void;
auto rtc() -> bool override;
auto rtcsync() -> void override;
auto serialize() -> serializer;
auto unserialize(serializer&) -> bool;
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const lstring&) -> void;
auto cheatSet(const lstring&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
vector<Device> devices;
};
struct Settings {

View File

@ -116,11 +116,11 @@ auto PPU::Background::getTile() -> void {
uint tx = hoffset >> tileWidth;
uint ty = voffset >> tileHeight;
uint15 offset = ((ty & 0x1f) << 5) + (tx & 0x1f);
uint16 offset = ((ty & 0x1f) << 5) + (tx & 0x1f);
if(tx & 0x20) offset += screenX;
if(ty & 0x20) offset += screenY;
uint15 address = r.screenAddress + offset;
uint16 address = r.screenAddress + offset;
tile = ppu.vram[address];
bool mirrorY = tile & 0x8000;
bool mirrorX = tile & 0x4000;
@ -270,6 +270,6 @@ auto PPU::Background::getTile(uint x, uint y) -> uint {
if(x & 0x20) offset += screenX;
if(y & 0x20) offset += screenY;
uint15 address = r.screenAddress + offset;
uint16 address = r.screenAddress + offset;
return ppu.vram[address];
}

View File

@ -9,7 +9,7 @@ auto PPU::getVramAddress() -> uint16 {
unreachable;
}
auto PPU::vramRead(bool chip, uint15 addr) -> uint8 {
auto PPU::vramRead(bool chip, uint addr) -> uint8 {
uint8 data = 0x00;
if(r.displayDisable || vcounter() >= vdisp()) {
data = vram[addr].byte(chip);
@ -18,7 +18,7 @@ auto PPU::vramRead(bool chip, uint15 addr) -> uint8 {
return data;
}
auto PPU::vramWrite(bool chip, uint15 addr, uint8 data) -> void {
auto PPU::vramWrite(bool chip, uint addr, uint8 data) -> void {
if(r.displayDisable || vcounter() >= vdisp()) {
vram[addr].byte(chip) = data;
debugger.vramWrite(addr << 1 | chip, data);

View File

@ -162,7 +162,7 @@ auto PPU::write(uint24 addr, uint8 data) -> void {
//OBSEL
case 0x2101: {
obj.r.tiledataAddress = data.bits(0,1) << 13;
obj.r.tiledataAddress = data.bits(0,2) << 13;
obj.r.nameSelect = data.bits(3,4);
obj.r.baseSize = data.bits(5,7);
return;
@ -226,42 +226,42 @@ auto PPU::write(uint24 addr, uint8 data) -> void {
//BG1SC
case 0x2107: {
bg1.r.screenSize = data.bits(0,1);
bg1.r.screenAddress = data.bits(2,6) << 10;
bg1.r.screenAddress = data.bits(2,7) << 10;
return;
}
//BG2SC
case 0x2108: {
bg2.r.screenSize = data.bits(0,1);
bg2.r.screenAddress = data.bits(2,6) << 10;
bg2.r.screenAddress = data.bits(2,7) << 10;
return;
}
//BG3SC
case 0x2109: {
bg3.r.screenSize = data.bits(0,1);
bg3.r.screenAddress = data.bits(2,6) << 10;
bg3.r.screenAddress = data.bits(2,7) << 10;
return;
}
//BG4SC
case 0x210a: {
bg4.r.screenSize = data.bits(0,1);
bg4.r.screenAddress = data.bits(2,6) << 10;
bg4.r.screenAddress = data.bits(2,7) << 10;
return;
}
//BG12NBA
case 0x210b: {
bg1.r.tiledataAddress = data.bits(0,2) << 12;
bg2.r.tiledataAddress = data.bits(4,6) << 12;
bg1.r.tiledataAddress = data.bits(0,3) << 12;
bg2.r.tiledataAddress = data.bits(4,7) << 12;
return;
}
//BG34NBA
case 0x210c: {
bg3.r.tiledataAddress = data.bits(0,2) << 12;
bg4.r.tiledataAddress = data.bits(4,6) << 12;
bg3.r.tiledataAddress = data.bits(0,3) << 12;
bg4.r.tiledataAddress = data.bits(4,7) << 12;
return;
}

View File

@ -88,7 +88,7 @@ auto PPU::addClocks(uint clocks) -> void {
}
auto PPU::power() -> void {
for(auto& n : vram) n = random(0x0000);
for(auto& n : vram.data) n = random(0x0000);
for(auto& n : oam) n = random(0x00);
for(auto& n : cgram) n = random(0x00);
}

View File

@ -18,8 +18,8 @@ struct PPU : Thread, PPUcounter {
//memory.cpp
alwaysinline auto getVramAddress() -> uint16;
alwaysinline auto vramRead(bool chip, uint15 addr) -> uint8;
alwaysinline auto vramWrite(bool chip, uint15 addr, uint8 data) -> void;
alwaysinline auto vramRead(bool chip, uint addr) -> uint8;
alwaysinline auto vramWrite(bool chip, uint addr, uint8 data) -> void;
alwaysinline auto oamRead(uint addr) -> uint8;
alwaysinline auto oamWrite(uint addr, uint8 data) -> void;
alwaysinline auto cgramRead(uint addr) -> uint8;
@ -32,7 +32,11 @@ struct PPU : Thread, PPUcounter {
auto updateVideoMode() -> void;
privileged:
uint16 vram[32 * 1024];
struct VRAM {
auto& operator[](uint offset) { return data[offset & size - 1]; }
uint16 data[64 * 1024];
uint size = 0x8000;
} vram;
uint8 oam[544];
uint8 cgram[512];

View File

@ -14,7 +14,8 @@ auto PPU::serialize(serializer& s) -> void {
Thread::serialize(s);
PPUcounter::serialize(s);
s.array(vram);
s.integer(vram.size);
s.array(vram.data, vram.size);
s.array(oam);
s.array(cgram);

View File

@ -16,12 +16,7 @@
#endif
namespace SuperFamicom {
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
using File = Emulator::File;
struct Thread {
virtual ~Thread() {

View File

@ -9,6 +9,7 @@ struct BSMemory : Memory {
auto read(uint24 addr, uint8) -> uint8;
auto write(uint24 addr, uint8 data) -> void;
uint pathID = 0;
MappedRAM memory;
bool readonly;

View File

@ -3,6 +3,7 @@ struct SufamiTurboCartridge {
auto unload() -> void;
auto serialize(serializer&) -> void;
uint pathID = 0;
MappedRAM rom;
MappedRAM ram;
};

View File

@ -10,51 +10,51 @@ auto Peripherals::unload() -> void {
}
auto Peripherals::reset() -> void {
connect(0, settings.controllerPort1);
connect(1, settings.controllerPort2);
connect(2, settings.expansionPort);
connect(ID::Port::Controller1, settings.controllerPort1);
connect(ID::Port::Controller2, settings.controllerPort2);
connect(ID::Port::Expansion, settings.expansionPort);
}
auto Peripherals::connect(uint port, uint id) -> void {
if(port == Port::Controller1) {
settings.controllerPort1 = id;
auto Peripherals::connect(uint port, uint device) -> void {
if(port == ID::Port::Controller1) {
settings.controllerPort1 = device;
if(!system.loaded()) return;
delete controllerPort1;
switch(id) { default:
case Device::None: controllerPort1 = new Controller(0); break;
case Device::Gamepad: controllerPort1 = new Gamepad(0); break;
case Device::Multitap: controllerPort1 = new Multitap(0); break;
case Device::Mouse: controllerPort1 = new Mouse(0); break;
switch(device) { default:
case ID::Device::None: controllerPort1 = new Controller(0); break;
case ID::Device::Gamepad: controllerPort1 = new Gamepad(0); break;
case ID::Device::Multitap: controllerPort1 = new Multitap(0); break;
case ID::Device::Mouse: controllerPort1 = new Mouse(0); break;
}
}
if(port == Port::Controller2) {
settings.controllerPort2 = id;
if(port == ID::Port::Controller2) {
settings.controllerPort2 = device;
if(!system.loaded()) return;
delete controllerPort2;
switch(id) { default:
case Device::None: controllerPort2 = new Controller(1); break;
case Device::Gamepad: controllerPort2 = new Gamepad(1); break;
case Device::Multitap: controllerPort2 = new Multitap(1); break;
case Device::Mouse: controllerPort2 = new Mouse(1); break;
case Device::SuperScope: controllerPort2 = new SuperScope(1); break;
case Device::Justifier: controllerPort2 = new Justifier(1, false); break;
case Device::Justifiers: controllerPort2 = new Justifier(1, true); break;
switch(device) { default:
case ID::Device::None: controllerPort2 = new Controller(1); break;
case ID::Device::Gamepad: controllerPort2 = new Gamepad(1); break;
case ID::Device::Multitap: controllerPort2 = new Multitap(1); break;
case ID::Device::Mouse: controllerPort2 = new Mouse(1); break;
case ID::Device::SuperScope: controllerPort2 = new SuperScope(1); break;
case ID::Device::Justifier: controllerPort2 = new Justifier(1, false); break;
case ID::Device::Justifiers: controllerPort2 = new Justifier(1, true); break;
}
}
if(port == Port::Expansion) {
settings.expansionPort = id;
if(port == ID::Port::Expansion) {
settings.expansionPort = device;
if(!system.loaded()) return;
delete expansionPort;
switch(id) { default:
case Device::None: expansionPort = new Expansion; break;
case Device::Satellaview: expansionPort = new Satellaview; break;
case Device::SuperDisc: expansionPort = new SuperDisc; break;
case Device::S21FX: expansionPort = new S21FX; break;
switch(device) { default:
case ID::Device::None: expansionPort = new Expansion; break;
case ID::Device::Satellaview: expansionPort = new Satellaview; break;
case ID::Device::SuperDisc: expansionPort = new SuperDisc; break;
case ID::Device::S21FX: expansionPort = new S21FX; break;
}
}

View File

@ -1,30 +1,7 @@
struct Port { enum : uint {
Controller1,
Controller2,
Expansion,
};};
struct Device { enum : uint {
None,
//controller port peripherals
Gamepad,
Multitap,
Mouse,
SuperScope,
Justifier,
Justifiers,
//expansion port peripherals
Satellaview,
SuperDisc,
S21FX,
};};
struct Peripherals {
auto unload() -> void;
auto reset() -> void;
auto connect(uint port, uint id) -> void;
auto connect(uint port, uint device) -> void;
Controller* controllerPort1 = nullptr;
Controller* controllerPort2 = nullptr;

View File

@ -66,7 +66,7 @@ auto System::load() -> bool {
} else return false;
}
cartridge.load();
if(!cartridge.load()) return false;
_region = cartridge.region() == Cartridge::Region::NTSC ? Region::NTSC : Region::PAL;
_cpuFrequency = region() == Region::NTSC ? 21'477'272 : 21'281'370;
_apuFrequency = 24'606'720;

View File

@ -1,71 +0,0 @@
name := loki
flags += -DDEBUGGER
include sfc/GNUmakefile
include processor/GNUmakefile
ui_objects := ui-loki ui-program
ui_objects += ui-terminal ui-presentation
ui_objects += ruby hiro
ui_objects += $(if $(call streq,$(platform),windows),ui-resource)
# platform
ifeq ($(platform),windows)
ruby += video.gdi audio.directsound input.windows
else ifeq ($(platform),macosx)
ruby += video.cgl audio.openal input.quartz
else ifeq ($(platform),linux)
ruby += video.xshm audio.openal input.sdl
else ifeq ($(platform),bsd)
ruby += video.xshm audio.oss input.sdl
endif
# ruby
include ../ruby/GNUmakefile
link += $(rubylink)
# hiro
include ../hiro/GNUmakefile
link += $(hirolink)
# rules
objects := $(ui_objects) $(objects)
objects := $(patsubst %,obj/%.o,$(objects))
obj/ruby.o: ../ruby/ruby.cpp $(call rwildcard,../ruby/)
$(compiler) $(rubyflags) -c $< -o $@
obj/hiro.o: ../hiro/hiro.cpp $(call rwildcard,../hiro/)
$(compiler) $(hiroflags) -c $< -o $@
obj/ui-loki.o: $(ui)/loki.cpp $(call rwildcard,$(ui)/)
obj/ui-program.o: $(ui)/program/program.cpp $(call rwildcard,$(ui)/)
obj/ui-terminal.o: $(ui)/terminal/terminal.cpp $(call rwildcard,$(ui)/)
obj/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/)
obj/ui-resource.o:
windres data/resource.rc obj/ui-resource.o
# targets
build: $(objects)
$(call unique,$(compiler) -o out/$(name) $(objects) $(link))
install:
ifeq ($(shell id -un),root)
$(error "make install should not be run as root")
else ifneq ($(filter $(platform),linux bsd),)
mkdir -p $(prefix)/bin/
mkdir -p $(prefix)/share/icons/
mkdir -p $(prefix)/share/$(name)/
cp out/$(name) $(prefix)/bin/$(name)
cp data/higan.png $(prefix)/share/icons/$(name).png
cp -R profile/* $(prefix)/share/$(name)/
endif
uninstall:
ifeq ($(shell id -un),root)
$(error "make uninstall should not be run as root")
else ifneq ($(filter $(platform),linux bsd),)
if [ -f $(prefix)/bin/$(name) ]; then rm $(prefix)/bin/$(name); fi
if [ -f $(prefix)/share/icons/$(name).png ]; then rm $(prefix)/share/icons/$(name).png; fi
endif

View File

@ -1,23 +0,0 @@
#include "loki.hpp"
unique_pointer<Video> video;
unique_pointer<Audio> audio;
unique_pointer<Input> input;
Emulator::Interface* emulator = nullptr;
auto locate(string name) -> string {
string location = {Path::program(), name};
if(inode::exists(location)) return location;
location = {Path::config(), "loki/", name};
if(inode::exists(location)) return location;
directory::create({Path::local(), "loki/"});
return {Path::local(), "loki/", name};
}
#include <nall/main.hpp>
auto nall::main(lstring args) -> void {
Application::setName("loki");
new Program(args);
Application::run();
}

View File

@ -1,21 +0,0 @@
#include <nall/nall.hpp>
#include <ruby/ruby.hpp>
#include <hiro/hiro.hpp>
using namespace nall;
using namespace ruby;
using namespace hiro;
extern unique_pointer<Video> video;
extern unique_pointer<Audio> audio;
extern unique_pointer<Input> input;
#include <emulator/emulator.hpp>
extern Emulator::Interface* emulator;
#include <sfc/sfc.hpp>
namespace SFC = SuperFamicom;
auto locate(string name) -> string;
#include "program/program.hpp"
#include "terminal/terminal.hpp"
#include "presentation/presentation.hpp"

View File

@ -1,28 +0,0 @@
#include "../loki.hpp"
unique_pointer<Presentation> presentation;
Presentation::Presentation() {
presentation = this;
onClose([&] { program->quit(); });
setTitle({"loki v", Emulator::Version});
setResizable(false);
setBackgroundColor({0, 0, 0});
setSize({512, 480});
setCentered();
setVisible();
}
auto Presentation::drawSplashScreen() -> void {
uint32_t* output;
uint length;
if(video->lock(output, length, 512, 480)) {
for(auto y : range(480)) {
auto dp = output + y * (length >> 2);
for(auto x : range(512)) *dp++ = 0xff'00'00'00;
}
video->unlock();
video->refresh();
}
}

View File

@ -1,9 +0,0 @@
struct Presentation : Window {
Presentation();
auto drawSplashScreen() -> void;
VerticalLayout layout{this};
Viewport viewport{&layout, Size{~0, ~0}};
};
extern unique_pointer<Presentation> presentation;

View File

@ -1,120 +0,0 @@
auto Program::loadRequest(uint id, string name, string type, bool required) -> void {
string location = BrowserDialog()
.setTitle({"Load ", name})
.setPath({Path::user(), "Emulation/", name})
.setFilters({string{name, "|*.", type}})
.openFolder();
if(!directory::exists(location)) return;
mediumPaths(id) = location;
folderPaths.append(location);
emulator->load(id);
}
auto Program::loadRequest(uint id, string filename, bool required) -> void {
string pathname = mediumPaths(emulator->group(id));
string location = {pathname, filename};
if(filename == "manifest.bml" && pathname && !pathname.endsWith("sys/")) {
if(!file::exists(location)) {
if(auto manifest = execute("icarus", "--manifest", pathname)) {
memorystream stream{manifest.output.data<uint8_t>(), manifest.output.size()};
return emulator->load(id, stream);
}
}
}
if(file::exists(location)) {
mmapstream stream{location};
return emulator->load(id, stream);
}
if(required) MessageDialog().setTitle("loki").setText({
"Missing required file: ", nall::filename(location), "\n\n",
"From location:\n", nall::pathname(location)
}).error();
}
auto Program::saveRequest(uint id, string filename) -> void {
string pathname = mediumPaths(emulator->group(id));
string location = {pathname, filename};
//filestream stream{location, file::mode::write};
//return emulator->save(id, stream);
}
auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
uint32_t* output;
uint length;
if(video->lock(output, length, width, height)) {
pitch >>= 2, length >>= 2;
for(auto y : range(height)) {
memory::copy(output + y * length, data + y * pitch, width * sizeof(uint32));
}
video->unlock();
video->refresh();
}
}
auto Program::audioSample(int16 left, int16 right) -> void {
audio->sample(left, right);
}
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
if(!presentation->focused()) return 0;
shared_pointer<HID::Keyboard> keyboard;
for(auto& device : devices) {
if(device->isKeyboard()) {
keyboard = device;
break;
}
}
if(port == (uint)SFC::Port::Controller1) {
if(device == (uint)SFC::Device::Gamepad) {
#define map(id, name) \
case id: \
if(auto code = keyboard->buttons().find(name)) { \
return keyboard->buttons().input(*code).value(); \
} \
break; \
switch(input) {
map(SFC::Gamepad::Up, "Up");
map(SFC::Gamepad::Down, "Down");
map(SFC::Gamepad::Left, "Left");
map(SFC::Gamepad::Right, "Right");
map(SFC::Gamepad::B, "Z");
map(SFC::Gamepad::A, "X");
map(SFC::Gamepad::Y, "A");
map(SFC::Gamepad::X, "S");
map(SFC::Gamepad::L, "D");
map(SFC::Gamepad::R, "C");
map(SFC::Gamepad::Select, "Apostrophe");
map(SFC::Gamepad::Start, "Return");
}
#undef map
}
}
return 0;
}
auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void {
}
auto Program::dipSettings(const Markup::Node& node) -> uint {
return 0;
}
auto Program::path(uint group) -> string {
return mediumPaths(group);
}
auto Program::notify(string text) -> void {
}

View File

@ -1,45 +0,0 @@
auto Program::loadMedium(string location) -> void {
location.transform("\\", "/");
if(!location.endsWith("/")) location.append("/");
if(!directory::exists(location)) return;
string type = suffixname(location).trimLeft(".", 1L);
for(auto& medium : emulator->media) {
if(!medium.bootable) continue;
if(medium.type != type) continue;
return loadMedium(medium, location);
}
}
auto Program::loadMedium(Emulator::Interface::Medium& medium, string location) -> void {
unloadMedium();
mediumPaths(0) = locate({medium.name, ".sys/"});
mediumPaths(medium.id) = location;
folderPaths.append(location);
emulator->set("Blur Emulation", false);
emulator->set("Color Emulation", false);
emulator->set("Scanline Emulation", false);
Emulator::audio.reset();
Emulator::audio.setFrequency(audio->get(Audio::Frequency).get<uint>());
emulator->connect((uint)SFC::Port::Controller1, (uint)SFC::Device::Gamepad);
emulator->connect((uint)SFC::Port::Controller2, (uint)SFC::Device::None);
emulator->connect((uint)SFC::Port::Expansion, (uint)SFC::Device::None);
emulator->load(medium.id);
emulator->power();
presentation->setTitle(emulator->title());
}
auto Program::unloadMedium() -> void {
if(!emulator->loaded()) return;
emulator->unload();
mediumPaths.reset();
folderPaths.reset();
presentation->setTitle("");
}

View File

@ -1,60 +0,0 @@
#include "../loki.hpp"
#include "interface.cpp"
#include "medium.cpp"
unique_pointer<Program> program;
Program::Program(lstring args) {
program = this;
Application::onMain({&Program::main, this});
emulator = new SuperFamicom::Interface;
emulator->bind = this;
new Terminal;
new Presentation;
terminal->setFocused();
video = Video::create();
video->set(Video::Handle, presentation->viewport.handle());
video->set(Video::Synchronize, false);
if(!video->init()) video = Video::create("None");
audio = Audio::create();
audio->set(Audio::Handle, presentation->viewport.handle());
audio->set(Audio::Synchronize, true);
audio->set(Audio::Frequency, 96000u);
audio->set(Audio::Latency, 80u);
if(!audio->init()) audio = Audio::create("None");
input = Input::create();
input->set(Input::Handle, presentation->viewport.handle());
if(!input->init()) input = Input::create("None");
presentation->drawSplashScreen();
string location = args(1, "");
if(!directory::exists(location)) location = { //quick testing hack
Path::user(), "Emulation/Super Famicom/",
"Legend of Zelda - A Link to the Past, The (USA) (1.0).sfc/"
};
if(directory::exists(location)) loadMedium(location);
}
auto Program::main() -> void {
if(!emulator->loaded()) {
usleep(20 * 1000);
return;
}
devices = input->poll();
emulator->run();
}
auto Program::quit() -> void {
unloadMedium();
video.reset();
audio.reset();
input.reset();
Application::quit();
}

View File

@ -1,29 +0,0 @@
struct Program : Emulator::Interface::Bind {
//program.cpp
Program(lstring args);
auto main() -> void;
auto quit() -> void;
//media.cpp
auto loadMedium(string location) -> void;
auto loadMedium(Emulator::Interface::Medium& medium, string location) -> void;
auto unloadMedium() -> void;
//interface.cpp
auto loadRequest(uint id, string name, string type, bool required) -> void override;
auto loadRequest(uint id, string path, bool required) -> void override;
auto saveRequest(uint id, string path) -> void override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(int16 left, int16 right) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;
auto inputRumble(uint port, uint device, uint input, bool enable) -> void override;
auto dipSettings(const Markup::Node& node) -> uint override;
auto path(uint group) -> string override;
auto notify(string text) -> void override;
vector<shared_pointer<HID::Device>> devices;
vector<string> mediumPaths;
vector<string> folderPaths;
};
extern unique_pointer<Program> program;

View File

@ -1,18 +0,0 @@
unique_pointer<AboutWindow> aboutWindow;
AboutWindow::AboutWindow() {
aboutWindow = this;
layout.setMargin(5);
canvas.setIcon({locate("loki.png")});
information.setFont(Font().setFamily(Font::Sans).setBold()).setAlignment(0.5).setText({
Emulator::Name, "/loki v", Emulator::Version, "\n\n"
"Author: ", Emulator::Author, "\n",
"License: ", Emulator::License, "\n",
"Website: ", Emulator::Website
});
setTitle("About loki ...");
setSize(layout.minimumSize());
setCentered();
}

View File

@ -1,26 +0,0 @@
#include "../loki.hpp"
#include "about-window.cpp"
unique_pointer<Terminal> terminal;
Terminal::Terminal() {
terminal = this;
new AboutWindow;
onClose([&] { program->quit(); });
helpMenu.setText("Help");
aboutAction.setText("About ...").onActivate([&] { aboutWindow->setFocused(); });
console.setBackgroundColor({ 56, 56, 56});
console.setForegroundColor({255, 255, 255});
console.setFont(Font().setFamily(Font::Mono).setSize(8));
console.setPrompt("$ ");
setTitle({"loki v", Emulator::Version});
setSize({800, 480});
setAlignment({0.0, 1.0});
setVisible();
}
auto Terminal::showAboutWindow() -> void {
}

View File

@ -1,22 +0,0 @@
struct Terminal : Window {
Terminal();
auto showAboutWindow() -> void;
MenuBar menuBar{this};
Menu helpMenu{&menuBar};
MenuItem aboutAction{&helpMenu};
VerticalLayout layout{this};
Console console{&layout, Size{~0, ~0}};
};
struct AboutWindow : Window {
AboutWindow();
VerticalLayout layout{this};
Canvas canvas{&layout, Size{288, 360}, 15};
Label information{&layout, Size{~0, 0}};
};
extern unique_pointer<Terminal> terminal;
extern unique_pointer<AboutWindow> aboutWindow;

View File

@ -163,7 +163,7 @@ InputManager::InputManager() {
auto& inputMapping = inputDevice.mappings.right();
inputMapping->name = input.name;
inputMapping->link = &input;
input.guid = (uintptr)inputMapping;
input.userData = (uintptr)inputMapping;
inputMapping->path = string{inputEmulator.name, "/", inputPort.name, "/", inputDevice.name, "/", inputMapping->name}.replace(" ", "");
inputMapping->assignment = settings(inputMapping->path).text();

View File

@ -6,19 +6,11 @@ Presentation::Presentation() {
libraryMenu.setText("Library");
for(auto& emulator : program->emulators) {
for(auto& media : emulator->media) {
if(!media.bootable) continue;
for(auto& medium : emulator->media) {
if(!medium.bootable) continue;
auto item = new MenuItem{&libraryMenu};
item->setText({media.name, " ..."}).onActivate([=] {
directory::create({settings["Library/Location"].text(), media.name});
auto location = BrowserDialog()
.setTitle({"Load ", media.name})
.setPath({settings["Library/Location"].text(), media.name})
.setFilters(string{media.name, "|*.", media.type})
.openFolder();
if(directory::exists(location)) {
program->loadMedium(location);
}
item->setText({medium.name, " ..."}).onActivate([=] {
program->loadMedium(*emulator, medium);
});
loadBootableMedia.append(item);
}
@ -26,10 +18,9 @@ Presentation::Presentation() {
//add icarus menu options -- but only if icarus binary is present
if(execute("icarus", "--name").output.strip() == "icarus") {
libraryMenu.append(MenuSeparator());
libraryMenu.append(MenuItem().setText("Load ROM File ...").onActivate([&] {
libraryMenu.append(MenuItem().setText("Import ROM File ...").onActivate([&] {
audio->clear();
if(auto location = execute("icarus", "--import")) {
program->loadMedium(location.output.strip());
}
}));
libraryMenu.append(MenuItem().setText("Import ROM Files ...").onActivate([&] {
@ -40,7 +31,7 @@ Presentation::Presentation() {
systemMenu.setText("System").setVisible(false);
powerSystem.setText("Power").onActivate([&] { program->powerCycle(); });
resetSystem.setText("Reset").onActivate([&] { program->softReset(); });
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedium(); drawSplashScreen(); });
unloadSystem.setText("Unload").onActivate([&] { program->unloadMedium(); });
settingsMenu.setText("Settings");
videoScaleMenu.setText("Video Scale");
@ -141,8 +132,6 @@ Presentation::Presentation() {
statusBar.setFont(Font().setBold());
statusBar.setVisible(settings["UserInterface/ShowStatusBar"].boolean());
viewport.setDroppable().onDrop([&](auto locations) { program->load(locations(0)); });
onClose([&] { program->quit(); });
setTitle({"higan v", Emulator::Version});

View File

@ -12,17 +12,17 @@ auto Program::open(uint id, string name, vfs::file::mode mode, bool required) ->
return {};
}
auto Program::load(uint id, string name, string type, bool required) -> void {
auto Program::load(uint id, string name, string type, bool required) -> maybe<uint> {
string location = BrowserDialog()
.setTitle({"Load ", name})
.setPath({settings["Library/Location"].text(), name})
.setFilters({string{name, "|*.", type}, "All|*.*"})
.openFolder();
if(!directory::exists(location)) return;
if(!directory::exists(location)) return nothing;
mediumPaths(id) = location;
folderPaths.append(location);
emulator->load(id);
uint pathID = mediumPaths.size();
mediumPaths.append(location);
return pathID;
}
auto Program::videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void {
@ -75,8 +75,8 @@ auto Program::audioSample(const double* samples, uint channels) -> void {
auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean()) {
auto guid = emulator->ports[port].devices[device].inputs[input].guid;
auto mapping = (InputMapping*)guid;
auto userData = emulator->ports[port].devices[device].inputs[input].userData;
auto mapping = (InputMapping*)userData;
if(mapping) return mapping->poll();
}
return 0;
@ -84,8 +84,8 @@ auto Program::inputPoll(uint port, uint device, uint input) -> int16 {
auto Program::inputRumble(uint port, uint device, uint input, bool enable) -> void {
if(presentation->focused() || settings["Input/FocusLoss/AllowInput"].boolean() || !enable) {
auto guid = emulator->ports[port].devices[device].inputs[input].guid;
auto mapping = (InputMapping*)guid;
auto userData = emulator->ports[port].devices[device].inputs[input].userData;
auto mapping = (InputMapping*)userData;
if(mapping) return mapping->rumble(enable);
}
}

View File

@ -1,30 +1,16 @@
auto Program::loadMedium(string location) -> void {
location.transform("\\", "/");
if(!location.endsWith("/")) location.append("/");
if(!directory::exists(location)) return;
string type = suffixname(location).trimLeft(".", 1L);
for(auto& emulator : emulators) {
for(auto& medium : emulator->media) {
if(!medium.bootable) continue;
if(medium.type != type) continue;
return loadMedium(*emulator, medium, location);
}
}
}
auto Program::loadMedium(Emulator::Interface& interface, Emulator::Interface::Medium& medium, string location) -> void {
auto Program::loadMedium(Emulator::Interface& interface, const Emulator::Interface::Medium& medium) -> void {
unloadMedium();
mediumPaths(0) = locate({medium.name, ".sys/"});
mediumPaths(medium.id) = location;
folderPaths.append(location);
mediumPaths.append(locate({medium.name, ".sys/"}));
//note: the order of operations in this block of code is critical
Emulator::audio.reset(2, audio->get(Audio::Frequency).get<uint>(44100));
emulator = &interface;
connectDevices();
emulator->load(medium.id);
if(!emulator->load(medium.id)) {
emulator = nullptr;
mediumPaths.reset();
return;
}
updateAudioDriver();
updateAudioEffects();
emulator->power();
@ -45,10 +31,9 @@ auto Program::unloadMedium() -> void {
toolsManager->cheatEditor.saveCheats();
emulator->unload();
emulator = nullptr;
mediumPaths.reset();
folderPaths.reset();
presentation->drawSplashScreen();
presentation->setTitle({"higan v", Emulator::Version});
presentation->systemMenu.setVisible(false);
presentation->toolsMenu.setVisible(false);

View File

@ -56,28 +56,6 @@ Program::Program(lstring args) {
for(auto& argument : args) {
if(argument == "--fullscreen") {
presentation->toggleFullScreen();
} else {
load(argument);
}
}
}
auto Program::load(string location) -> void {
if(directory::exists(location)) {
loadMedium(location);
} else if(file::exists(location)) {
//special handling to allow importing the Game Boy Advance BIOS
if(file::size(location) == 16384 && file::sha256(location).beginsWith("fd2547724b505f48")) {
auto target = locate("Game Boy Advance.sys/");
if(file::copy(location, {target, "bios.rom"})) {
MessageDialog().setTitle(Emulator::Name).setText("Game Boy Advance BIOS imported successfully!").information();
}
return;
}
//ask icarus to import the game; and play it upon success
if(auto result = execute("icarus", "--import", location)) {
loadMedium(result.output.strip());
}
}
}

View File

@ -1,14 +1,13 @@
struct Program : Emulator::Interface::Bind {
//program.cpp
Program(lstring args);
auto load(string) -> void;
auto main() -> void;
auto quit() -> void;
//interface.cpp
auto path(uint id) -> string override;
auto open(uint id, string name, vfs::file::mode mode, bool required) -> vfs::shared::file override;
auto load(uint id, string name, string type, bool required) -> void override;
auto load(uint id, string name, string type, bool required) -> maybe<uint> override;
auto videoRefresh(const uint32* data, uint pitch, uint width, uint height) -> void override;
auto audioSample(const double* samples, uint channels) -> void override;
auto inputPoll(uint port, uint device, uint input) -> int16 override;
@ -17,8 +16,7 @@ struct Program : Emulator::Interface::Bind {
auto notify(string text) -> void override;
//medium.cpp
auto loadMedium(string location) -> void;
auto loadMedium(Emulator::Interface& interface, Emulator::Interface::Medium& medium, string location) -> void;
auto loadMedium(Emulator::Interface& interface, const Emulator::Interface::Medium& medium) -> void;
auto unloadMedium() -> void;
//state.cpp
@ -42,7 +40,6 @@ struct Program : Emulator::Interface::Bind {
vector<Emulator::Interface*> emulators;
vector<string> mediumPaths;
vector<string> folderPaths;
string statusText;
string statusMessage;

View File

@ -1,6 +1,6 @@
auto Program::stateName(uint slot, bool manager) -> string {
return {
folderPaths[0], "higan/states/",
mediumPaths(1), "higan/states/",
manager ? "managed/" : "quick/",
"slot-", natural(slot, 2L), ".bst"
};

View File

@ -128,7 +128,7 @@ auto CheatEditor::addCode(const string& code, const string& description, bool en
auto CheatEditor::loadCheats() -> void {
doReset(true);
auto contents = string::read({program->folderPaths[0], "cheats.bml"});
auto contents = string::read({program->mediumPaths(1), "cheats.bml"});
auto document = BML::unserialize(contents);
for(auto cheat : document["cartridge"].find("cheat")) {
if(!addCode(cheat["code"].text(), cheat["description"].text(), (bool)cheat["enabled"])) break;
@ -149,9 +149,9 @@ auto CheatEditor::saveCheats() -> void {
count++;
}
if(count) {
file::write({program->folderPaths[0], "cheats.bml"}, document);
file::write({program->mediumPaths(0), "cheats.bml"}, document);
} else {
file::remove({program->folderPaths[0], "cheats.bml"});
file::remove({program->mediumPaths(0), "cheats.bml"});
}
doReset(true);
}

View File

@ -41,14 +41,24 @@ auto Cartridge::power() -> void {
r.sramBank = 0xff;
}
auto Cartridge::mode() const -> uint {
return system.model() == Model::WonderSwan ? ID::WonderSwan : ID::WonderSwanColor;
}
auto Cartridge::load() -> bool {
information = Information();
if(auto fp = interface->open(mode(), "manifest.bml", File::Read, File::Required)) {
switch(system.model()) {
case Model::WonderSwan:
if(auto pathID = interface->load(ID::WonderSwan, "WonderSwan", "ws", File::Required)) {
information.pathID = pathID();
} else return false;
break;
case Model::WonderSwanColor:
case Model::SwanCrystal:
if(auto pathID = interface->load(ID::WonderSwanColor, "WonderSwan Color", "wsc", File::Required)) {
information.pathID = pathID();
} else return false;
break;
}
if(auto fp = interface->open(pathID(), "manifest.bml", File::Read, File::Required)) {
information.manifest = fp->reads();
} else return false;
@ -61,7 +71,7 @@ auto Cartridge::load() -> bool {
if(rom.size) {
rom.data = new uint8[rom.mask + 1];
memory::fill(rom.data, rom.mask + 1, 0xff);
if(rom.name) if(auto fp = interface->open(mode(), rom.name, File::Read, File::Required)) {
if(rom.name) if(auto fp = interface->open(pathID(), rom.name, File::Read, File::Required)) {
fp->read(rom.data, rom.size);
}
}
@ -75,7 +85,7 @@ auto Cartridge::load() -> bool {
if(ram.size) {
ram.data = new uint8[ram.mask + 1];
memory::fill(ram.data, ram.mask + 1, 0xff);
if(ram.name) if(auto fp = interface->open(mode(), ram.name, File::Read)) {
if(ram.name) if(auto fp = interface->open(pathID(), ram.name, File::Read)) {
fp->read(ram.data, ram.size);
}
}
@ -86,7 +96,7 @@ auto Cartridge::load() -> bool {
eeprom.setSize(node["size"].natural() / sizeof(uint16));
if(eeprom.size()) {
eeprom.erase();
if(eeprom.name()) if(auto fp = interface->open(mode(), eeprom.name(), File::Read)) {
if(eeprom.name()) if(auto fp = interface->open(pathID(), eeprom.name(), File::Read)) {
fp->read(eeprom.data(), eeprom.size());
}
}
@ -100,7 +110,7 @@ auto Cartridge::load() -> bool {
if(rtc.size) {
rtc.data = new uint8[rtc.mask + 1];
memory::fill(rtc.data, rtc.mask + 1, 0x00);
if(rtc.name) if(auto fp = interface->open(mode(), rtc.name, File::Read)) {
if(rtc.name) if(auto fp = interface->open(pathID(), rtc.name, File::Read)) {
fp->read(rtc.data, rtc.size);
}
}
@ -116,19 +126,19 @@ auto Cartridge::save() -> void {
auto document = BML::unserialize(information.manifest);
if(auto name = document["board/ram/name"].text()) {
if(auto fp = interface->open(mode(), name, File::Write)) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(ram.data, ram.size);
}
}
if(auto name = document["board/eeprom/name"].text()) {
if(auto fp = interface->open(mode(), name, File::Write)) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(eeprom.data(), eeprom.size());
}
}
if(auto name = document["board/rtc/name"].text()) {
if(auto fp = interface->open(mode(), name, File::Write)) {
if(auto fp = interface->open(pathID(), name, File::Write)) {
fp->write(rtc.data, rtc.size);
}
}

View File

@ -1,10 +1,11 @@
struct Cartridge : Thread, IO {
auto pathID() const -> uint { return information.pathID; }
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto power() -> void;
auto mode() const -> uint;
auto load() -> bool;
auto save() -> void;
auto unload() -> void;
@ -34,10 +35,11 @@ struct Cartridge : Thread, IO {
auto serialize(serializer&) -> void;
struct Information {
uint pathID = 0;
string sha256;
string manifest;
string title;
bool orientation; //0 = horizontal; 1 = vertical
boolean orientation; //0 = horizontal; 1 = vertical
} information;
struct Registers {

View File

@ -19,43 +19,44 @@ Interface::Interface() {
information.capability.states = true;
information.capability.cheats = true;
media.append({ID::WonderSwan, "WonderSwan", "ws", true});
media.append({ID::WonderSwan, "WonderSwan", "ws", true});
media.append({ID::WonderSwanColor, "WonderSwan Color", "wsc", true});
{ Device device{0, ID::DeviceHorizontal, "Controller"};
device.inputs.append({ 0, 0, "Y1"});
device.inputs.append({ 1, 0, "Y2"});
device.inputs.append({ 2, 0, "Y3"});
device.inputs.append({ 3, 0, "Y4"});
device.inputs.append({ 4, 0, "X1"});
device.inputs.append({ 5, 0, "X2"});
device.inputs.append({ 6, 0, "X3"});
device.inputs.append({ 7, 0, "X4"});
device.inputs.append({ 8, 0, "B"});
device.inputs.append({ 9, 0, "A"});
device.inputs.append({10, 0, "Start"});
device.inputs.append({11, 0, "Rotate"});
devices.append(device);
Port hardwarePort{ID::Port::Hardware, "Hardware"};
{ Device device{ID::Device::HorizontalControls, "Horizontal Controls"};
device.inputs.append({0, "Y1"});
device.inputs.append({0, "Y2"});
device.inputs.append({0, "Y3"});
device.inputs.append({0, "Y4"});
device.inputs.append({0, "X1"});
device.inputs.append({0, "X2"});
device.inputs.append({0, "X3"});
device.inputs.append({0, "X4"});
device.inputs.append({0, "B"});
device.inputs.append({0, "A"});
device.inputs.append({0, "Start"});
device.inputs.append({0, "Rotate"});
hardwarePort.devices.append(device);
}
{ Device device{1, ID::DeviceVertical, "Controller"};
device.inputs.append({ 0, 0, "Y1"});
device.inputs.append({ 1, 0, "Y2"});
device.inputs.append({ 2, 0, "Y3"});
device.inputs.append({ 3, 0, "Y4"});
device.inputs.append({ 4, 0, "X1"});
device.inputs.append({ 5, 0, "X2"});
device.inputs.append({ 6, 0, "X3"});
device.inputs.append({ 7, 0, "X4"});
device.inputs.append({ 8, 0, "B"});
device.inputs.append({ 9, 0, "A"});
device.inputs.append({10, 0, "Start"});
device.inputs.append({11, 0, "Rotate"});
devices.append(device);
{ Device device{ID::Device::VerticalControls, "Vertical Controls"};
device.inputs.append({0, "Y1"});
device.inputs.append({0, "Y2"});
device.inputs.append({0, "Y3"});
device.inputs.append({0, "Y4"});
device.inputs.append({0, "X1"});
device.inputs.append({0, "X2"});
device.inputs.append({0, "X3"});
device.inputs.append({0, "X4"});
device.inputs.append({0, "B"});
device.inputs.append({0, "A"});
device.inputs.append({0, "Start"});
device.inputs.append({0, "Rotate"});
hardwarePort.devices.append(device);
}
ports.append({0, "Horizontal Orientation", {devices[0]}});
ports.append({1, "Vertical Orientation", {devices[1]}});
ports.append(move(hardwarePort));
}
auto Interface::manifest() -> string {
@ -107,9 +108,10 @@ auto Interface::sha256() -> string {
return cartridge.information.sha256;
}
auto Interface::load(uint id) -> void {
if(id == ID::WonderSwan) system.load(Model::WonderSwan);
if(id == ID::WonderSwanColor) system.load(Model::WonderSwanColor);
auto Interface::load(uint id) -> bool {
if(id == ID::WonderSwan) return system.load(Model::WonderSwan);
if(id == ID::WonderSwanColor) return system.load(Model::WonderSwanColor);
return false;
}
auto Interface::save() -> void {
@ -118,6 +120,7 @@ auto Interface::save() -> void {
auto Interface::unload() -> void {
save();
system.unload();
}
auto Interface::power() -> void {

View File

@ -7,25 +7,19 @@ struct ID {
WonderSwanColor,
};
enum : uint {
SystemManifest,
SystemIPLROM,
SystemEEPROM,
struct Device { enum : uint {
HorizontalControls,
VerticalControls,
};};
Manifest,
ROM,
RAM,
EEPROM,
RTC,
};
enum : uint {
DeviceHorizontal = 1,
DeviceVertical = 2,
};
struct Port { enum : uint {
Hardware,
};};
};
struct Interface : Emulator::Interface {
using Emulator::Interface::load;
Interface();
auto manifest() -> string override;
@ -37,7 +31,7 @@ struct Interface : Emulator::Interface {
auto loaded() -> bool override;
auto sha256() -> string override;
auto load(uint id) -> void override;
auto load(uint id) -> bool override;
auto save() -> void override;
auto unload() -> void override;
@ -47,14 +41,11 @@ struct Interface : Emulator::Interface {
auto serialize() -> serializer override;
auto unserialize(serializer&) -> bool override;
auto cheatSet(const lstring&) -> void;
auto cheatSet(const lstring&) -> void override;
auto cap(const string& name) -> bool override;
auto get(const string& name) -> any override;
auto set(const string& name, const any& value) -> bool override;
private:
vector<Device> devices;
};
struct Settings {

View File

@ -36,7 +36,7 @@ auto System::load(Model model) -> bool {
}
}
cartridge.load();
if(!cartridge.load()) return false;
serializeInit();
_orientation = cartridge.information.orientation;
return _loaded = true;
@ -98,19 +98,22 @@ auto System::runToSave() -> void {
}
auto System::pollKeypad() -> void {
uint port = ID::Port::Hardware;
uint device = !_orientation ? ID::Device::HorizontalControls : ID::Device::VerticalControls;
bool rotate = keypad.rotate;
keypad.y1 = interface->inputPoll(_orientation, 0, 0);
keypad.y2 = interface->inputPoll(_orientation, 0, 1);
keypad.y3 = interface->inputPoll(_orientation, 0, 2);
keypad.y4 = interface->inputPoll(_orientation, 0, 3);
keypad.x1 = interface->inputPoll(_orientation, 0, 4);
keypad.x2 = interface->inputPoll(_orientation, 0, 5);
keypad.x3 = interface->inputPoll(_orientation, 0, 6);
keypad.x4 = interface->inputPoll(_orientation, 0, 7);
keypad.b = interface->inputPoll(_orientation, 0, 8);
keypad.a = interface->inputPoll(_orientation, 0, 9);
keypad.start = interface->inputPoll(_orientation, 0, 10);
keypad.rotate = interface->inputPoll(_orientation, 0, 11);
keypad.y1 = interface->inputPoll(port, device, 0);
keypad.y2 = interface->inputPoll(port, device, 1);
keypad.y3 = interface->inputPoll(port, device, 2);
keypad.y4 = interface->inputPoll(port, device, 3);
keypad.x1 = interface->inputPoll(port, device, 4);
keypad.x2 = interface->inputPoll(port, device, 5);
keypad.x3 = interface->inputPoll(port, device, 6);
keypad.x4 = interface->inputPoll(port, device, 7);
keypad.b = interface->inputPoll(port, device, 8);
keypad.a = interface->inputPoll(port, device, 9);
keypad.start = interface->inputPoll(port, device, 10);
keypad.rotate = interface->inputPoll(port, device, 11);
if(keypad.y1 || keypad.y2 || keypad.y3 || keypad.y4
|| keypad.x1 || keypad.x2 || keypad.x3 || keypad.x4

View File

@ -7,6 +7,8 @@
#include <processor/v30mz/v30mz.hpp>
namespace WonderSwan {
using File = Emulator::File;
enum class Model : uint {
WonderSwan, //SW-001 (ASWAN)
WonderSwanColor, //WSC-001 (SPHINX)
@ -15,13 +17,6 @@ namespace WonderSwan {
enum : uint { Byte = 1, Word = 2, Long = 4 };
struct File {
static const auto Read = vfs::file::mode::read;
static const auto Write = vfs::file::mode::write;
static const auto Optional = false;
static const auto Required = true;
};
struct Thread {
~Thread() {
if(thread) co_delete(thread);