mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
ccd8878d75
commit
f48b332c83
|
@ -1,7 +1,6 @@
|
|||
include ../nall/GNUmakefile
|
||||
|
||||
target := tomoko
|
||||
# target := loki
|
||||
# console := true
|
||||
|
||||
flags += -I. -I.. -O3
|
||||
|
|
|
@ -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/";
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -39,7 +39,7 @@ auto System::load(Revision revision) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
cartridge.load(revision);
|
||||
if(!cartridge.load(revision)) return false;
|
||||
serializeInit();
|
||||
return _loaded = true;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -44,7 +44,7 @@ auto System::load() -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
cartridge.load();
|
||||
if(!cartridge.load()) return false;
|
||||
serializeInit();
|
||||
return _loaded = true;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ struct SufamiTurboCartridge {
|
|||
auto unload() -> void;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
uint pathID = 0;
|
||||
MappedRAM rom;
|
||||
MappedRAM ram;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
|
@ -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();
|
||||
}
|
|
@ -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"
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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 {
|
||||
}
|
|
@ -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("");
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
|
@ -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();
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue