mirror of https://github.com/bsnes-emu/bsnes.git
Update to v088r13 release.
byuu says: Changelog: - fixed Super Game Boy input - Sufami Turbo prompts to load second slot now (you can cancel to leave it empty) - NEC/Hitachi/ARM DSP firmware is loaded; NEC RAM is saved - folders are grouped properly: Sufami Turbo save RAM saves to its slot folder, etc. - title shows properly (SGB shows GB game name only, BS-X slotted shows game name and optional slot name, etc.) - above extends to saving cheats and such in their correct folders as well - added cheat editor and cheat database - and hooked up the requisite SGB mode loads and can use GB cheats, because that's kinda cool - added state manager - input settings, cheat editor and state manager all have erase (one) and reset (all) buttons now - lots of cleanup and restructuring on Emulator::Interface; *almost* finished with it now Remaining: - BS-X BIOS won't show the data pack - need XML mapping information window - need NSS DIP switch settings window - need video shaders - need driver selection - need to hide controllers that have no inputs from the input mapping list (tempted to just remove "None" as a controller option ...) ethos is currently 88KB of code, ui is 167KB. We're missing about 5-10KB of code in ethos to complete it, so the rewrite nearly cut the GUI code size in half, while support all of the same functionality and allowing the easy addition and removal of entire systems.
This commit is contained in:
parent
5d273c5265
commit
3cb04b101b
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "bsnes";
|
||||
static const char Version[] = "088.12";
|
||||
static const char Version[] = "088.13";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
}
|
||||
|
|
|
@ -12,29 +12,18 @@ struct Interface {
|
|||
double aspectRatio;
|
||||
unsigned frequency;
|
||||
bool resettable;
|
||||
|
||||
struct Media {
|
||||
string name;
|
||||
string extension;
|
||||
};
|
||||
vector<Media> media;
|
||||
} information;
|
||||
|
||||
struct Media {
|
||||
unsigned id;
|
||||
string name;
|
||||
string extension;
|
||||
string type;
|
||||
string path;
|
||||
string extension;
|
||||
};
|
||||
vector<Media> firmware;
|
||||
|
||||
struct Schema : Media {
|
||||
vector<Media> slot;
|
||||
Schema(const Media &media) {
|
||||
id = media.id, name = media.name, extension = media.extension, path = media.path;
|
||||
}
|
||||
};
|
||||
vector<Schema> schema;
|
||||
vector<Media> firmware;
|
||||
vector<Media> media;
|
||||
|
||||
struct Memory {
|
||||
unsigned id;
|
||||
|
@ -42,11 +31,9 @@ struct Interface {
|
|||
};
|
||||
vector<Memory> memory;
|
||||
|
||||
struct Port {
|
||||
unsigned id;
|
||||
string name;
|
||||
struct Device {
|
||||
unsigned id;
|
||||
unsigned portmask;
|
||||
string name;
|
||||
struct Input {
|
||||
unsigned id;
|
||||
|
@ -58,6 +45,11 @@ struct Interface {
|
|||
vector<unsigned> order;
|
||||
};
|
||||
vector<Device> device;
|
||||
|
||||
struct Port {
|
||||
unsigned id;
|
||||
string name;
|
||||
vector<Device> device;
|
||||
};
|
||||
vector<Port> port;
|
||||
|
||||
|
@ -94,6 +86,8 @@ struct Interface {
|
|||
|
||||
//media interface
|
||||
virtual bool loaded() { return false; }
|
||||
virtual string sha256() { return ""; }
|
||||
virtual unsigned group(unsigned id) { return 0u; }
|
||||
virtual void load(unsigned id, const stream &memory, const string &markup = "") {}
|
||||
virtual void save(unsigned id, const stream &memory) {}
|
||||
virtual void unload() {}
|
||||
|
@ -108,6 +102,9 @@ struct Interface {
|
|||
virtual serializer serialize() = 0;
|
||||
virtual bool unserialize(serializer&) = 0;
|
||||
|
||||
//cheat functions
|
||||
virtual void cheatSet(const lstring& = lstring{}) {}
|
||||
|
||||
//utility functions
|
||||
virtual void updatePalette() {}
|
||||
};
|
||||
|
|
|
@ -8,6 +8,10 @@ bool Interface::loaded() {
|
|||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
string Interface::sha256() {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||
if(id == ID::ROM) {
|
||||
cartridge.load(markup, stream);
|
||||
|
@ -52,6 +56,18 @@ bool Interface::unserialize(serializer &s) {
|
|||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::cheatSet(const lstring &list) {
|
||||
cheat.reset();
|
||||
for(auto &code : list) {
|
||||
lstring codelist = code.split("+");
|
||||
for(auto &part : codelist) {
|
||||
unsigned addr, data, comp;
|
||||
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
|
||||
}
|
||||
}
|
||||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -67,25 +83,10 @@ Interface::Interface() {
|
|||
information.frequency = 1789772;
|
||||
information.resettable = true;
|
||||
|
||||
information.media.append({"Famicom", "fc"});
|
||||
|
||||
schema.append(Media{ID::ROM, "Famicom", "fc", "program.rom"});
|
||||
media.append({ID::ROM, "Famicom", "sys", "program.rom", "fc"});
|
||||
|
||||
{
|
||||
Port port{0, "Port 1"};
|
||||
port.device.append(controller());
|
||||
this->port.append(port);
|
||||
}
|
||||
|
||||
{
|
||||
Port port{1, "Port 2"};
|
||||
port.device.append(controller());
|
||||
this->port.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::controller() {
|
||||
Port::Device device{0, "Controller"};
|
||||
Device device{0, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
|
@ -95,7 +96,19 @@ Emulator::Interface::Port::Device Interface::controller() {
|
|||
device.input.append({6, 0, "Left" });
|
||||
device.input.append({7, 0, "Right" });
|
||||
device.order = {4, 5, 6, 7, 1, 0, 2, 3};
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({ID::Port1, "Port 1"});
|
||||
port.append({ID::Port2, "Port 2"});
|
||||
|
||||
for(auto &device : this->device) {
|
||||
for(auto &port : this->port) {
|
||||
if(device.portmask & port.id) {
|
||||
port.device.append(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,10 +7,16 @@ struct ID {
|
|||
ROM,
|
||||
RAM,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
Port1 = 1,
|
||||
Port2 = 2,
|
||||
};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
bool loaded();
|
||||
string sha256();
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
void save(unsigned id, const stream &stream);
|
||||
void unload();
|
||||
|
@ -22,12 +28,11 @@ struct Interface : Emulator::Interface {
|
|||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
||||
private:
|
||||
Port::Device controller();
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -8,6 +8,10 @@ bool Interface::loaded() {
|
|||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
string Interface::sha256() {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||
if(id == ID::GameBoyBootROM) {
|
||||
stream.read(system.bootROM.dmg, min( 256u, stream.size()));
|
||||
|
@ -67,6 +71,18 @@ bool Interface::unserialize(serializer &s) {
|
|||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::cheatSet(const lstring &list) {
|
||||
cheat.reset();
|
||||
for(auto &code : list) {
|
||||
lstring codelist = code.split("+");
|
||||
for(auto &part : codelist) {
|
||||
unsigned addr, data, comp;
|
||||
if(Cheat::decode(part, addr, data, comp)) cheat.append({addr, data, comp});
|
||||
}
|
||||
}
|
||||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -82,20 +98,15 @@ Interface::Interface() {
|
|||
information.frequency = 4194304;
|
||||
information.resettable = false;
|
||||
|
||||
information.media.append({"Game Boy", "gb" });
|
||||
information.media.append({"Game Boy Color", "gbc"});
|
||||
|
||||
firmware.append({ID::GameBoyBootROM, "Game Boy", "sys", "boot.rom"});
|
||||
firmware.append({ID::SuperGameBoyBootROM, "Super Game Boy", "sfc", "boot.rom"});
|
||||
firmware.append({ID::GameBoyColorBootROM, "Game Boy Color", "sys", "boot.rom"});
|
||||
|
||||
schema.append(Media{ID::GameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
schema.append(Media{ID::GameBoyColorROM, "Game Boy Color", "gbc", "program.rom"});
|
||||
media.append({ID::GameBoyROM, "Game Boy", "sys", "program.rom", "gb" });
|
||||
media.append({ID::GameBoyColorROM, "Game Boy Color", "sys", "program.rom", "gbc"});
|
||||
|
||||
{
|
||||
Port port{0, "Device"};
|
||||
{
|
||||
Port::Device device{0, "Controller"};
|
||||
Device device{0, ID::Device, "Controller"};
|
||||
device.input.append({0, 0, "Up" });
|
||||
device.input.append({1, 0, "Down" });
|
||||
device.input.append({2, 0, "Left" });
|
||||
|
@ -105,10 +116,10 @@ Interface::Interface() {
|
|||
device.input.append({6, 0, "Select"});
|
||||
device.input.append({7, 0, "Start" });
|
||||
device.order = {0, 1, 2, 3, 4, 5, 6, 7};
|
||||
port.device.append(device);
|
||||
}
|
||||
this->port.append(port);
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({ID::Device, "Device", {device[0]}});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@ struct ID {
|
|||
GameBoyColorROM,
|
||||
RAM,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
Device = 1,
|
||||
};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
|
@ -19,6 +23,7 @@ struct Interface : Emulator::Interface {
|
|||
virtual void joypWrite(bool p15, bool p14) {}
|
||||
|
||||
bool loaded();
|
||||
string sha256();
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
void save(unsigned id, const stream &stream);
|
||||
void unload();
|
||||
|
@ -30,6 +35,8 @@ struct Interface : Emulator::Interface {
|
|||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
|
|
@ -86,16 +86,12 @@ Interface::Interface() {
|
|||
information.frequency = 32768;
|
||||
information.resettable = false;
|
||||
|
||||
information.media.append({"Game Boy Advance", "gba"});
|
||||
|
||||
firmware.append({ID::BIOS, "Game Boy Advance", "sys", "bios.rom"});
|
||||
|
||||
schema.append(Media{ID::ROM, "Game Boy Advance", "gba", "program.rom"});
|
||||
media.append({ID::ROM, "Game Boy Advance", "sys", "program.rom", "gba"});
|
||||
|
||||
{
|
||||
Port port{0, "Device"};
|
||||
{
|
||||
Port::Device device{0, "Controller"};
|
||||
Device device{0, ID::Device, "Controller"};
|
||||
device.input.append({0, 0, "A" });
|
||||
device.input.append({1, 0, "B" });
|
||||
device.input.append({2, 0, "Select"});
|
||||
|
@ -107,10 +103,10 @@ Interface::Interface() {
|
|||
device.input.append({8, 0, "R" });
|
||||
device.input.append({9, 0, "L" });
|
||||
device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3};
|
||||
port.device.append(device);
|
||||
}
|
||||
this->port.append(port);
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({ID::Device, "Device", {device[0]}});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ struct ID {
|
|||
EEPROM,
|
||||
FlashROM,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
Device = 1,
|
||||
};
|
||||
};
|
||||
|
||||
struct Interface : Emulator::Interface {
|
||||
|
|
|
@ -11,7 +11,6 @@ Cartridge cartridge;
|
|||
void Cartridge::load(const string &markup, const stream &stream) {
|
||||
information.markup = markup;
|
||||
rom.copy(stream);
|
||||
sha256 = nall::sha256(rom.data(), rom.size()); //TODO: special case SGB, BSX, ST mode SHA256 sums
|
||||
|
||||
region = Region::NTSC;
|
||||
ram_size = 0;
|
||||
|
@ -19,7 +18,7 @@ void Cartridge::load(const string &markup, const stream &stream) {
|
|||
has_gb_slot = false;
|
||||
has_bs_cart = false;
|
||||
has_bs_slot = false;
|
||||
has_st_slot = false;
|
||||
has_st_slots = false;
|
||||
has_nss_dip = false;
|
||||
has_superfx = false;
|
||||
has_sa1 = false;
|
||||
|
@ -39,6 +38,26 @@ void Cartridge::load(const string &markup, const stream &stream) {
|
|||
parse_markup(markup);
|
||||
//print(markup, "\n\n");
|
||||
|
||||
//Super Game Boy
|
||||
if(cartridge.has_gb_slot()) {
|
||||
sha256 = nall::sha256(GameBoy::cartridge.romdata, GameBoy::cartridge.romsize);
|
||||
}
|
||||
|
||||
//Broadcast Satellaview
|
||||
else if(cartridge.has_bs_cart() && cartridge.has_bs_slot()) {
|
||||
sha256 = nall::sha256(bsxflash.memory.data(), bsxflash.memory.size());
|
||||
}
|
||||
|
||||
//Sufami Turbo
|
||||
else if(cartridge.has_st_slots()) {
|
||||
sha256 = nall::sha256(sufamiturbo.slotA.rom.data(), sufamiturbo.slotA.rom.size());
|
||||
}
|
||||
|
||||
//Super Famicom
|
||||
else {
|
||||
sha256 = nall::sha256(rom.data(), rom.size());
|
||||
}
|
||||
|
||||
if(ram_size > 0) {
|
||||
ram.map(allocate<uint8>(ram_size, 0xff), ram_size);
|
||||
interface->memory.append({ID::RAM, "save.ram"});
|
||||
|
|
|
@ -25,7 +25,7 @@ struct Cartridge : property<Cartridge> {
|
|||
readonly<bool> has_gb_slot;
|
||||
readonly<bool> has_bs_cart;
|
||||
readonly<bool> has_bs_slot;
|
||||
readonly<bool> has_st_slot;
|
||||
readonly<bool> has_st_slots;
|
||||
readonly<bool> has_nss_dip;
|
||||
readonly<bool> has_superfx;
|
||||
readonly<bool> has_sa1;
|
||||
|
|
|
@ -110,7 +110,7 @@ void Cartridge::parse_markup_icd2(XML::Node &root) {
|
|||
if(root.exists() == false) return;
|
||||
has_gb_slot = true;
|
||||
|
||||
interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
interface->mediaRequest({ID::SuperGameBoyROM, "Game Boy", "", "program.rom", "gb"});
|
||||
|
||||
icd2.revision = max(1, numeral(root["revision"].data));
|
||||
|
||||
|
@ -225,33 +225,12 @@ void Cartridge::parse_markup_necdsp(XML::Node &root) {
|
|||
string firmware = root["firmware"].data;
|
||||
string sha256 = root["sha256"].data;
|
||||
|
||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
||||
unsigned promsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 2048 : 16384);
|
||||
unsigned dromsize = (necdsp.revision == NECDSP::Revision::uPD7725 ? 1024 : 2048);
|
||||
unsigned filesize = promsize * 3 + dromsize * 2;
|
||||
|
||||
file fp;
|
||||
if(fp.open(path, file::mode::read) == false) {
|
||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " is missing." });
|
||||
} else if(fp.size() != filesize) {
|
||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " is of the wrong file size." });
|
||||
fp.close();
|
||||
} else {
|
||||
for(unsigned n = 0; n < promsize; n++) necdsp.programROM[n] = fp.readl(3);
|
||||
for(unsigned n = 0; n < dromsize; n++) necdsp.dataROM[n] = fp.readl(2);
|
||||
|
||||
if(!sha256.empty()) {
|
||||
//XML file specified SHA256 sum for program. Verify file matches the hash.
|
||||
fp.seek(0);
|
||||
uint8_t data[filesize];
|
||||
fp.read(data, filesize);
|
||||
|
||||
if(sha256 != nall::sha256(data, filesize)) {
|
||||
interface->message({ "Warning: NEC DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
||||
}
|
||||
if(necdsp.revision == NECDSP::Revision::uPD7725) {
|
||||
interface->mediaRequest({ID::Nec7725DSP, "", "", firmware});
|
||||
}
|
||||
|
||||
fp.close();
|
||||
if(necdsp.revision == NECDSP::Revision::uPD96050) {
|
||||
interface->mediaRequest({ID::Nec96050DSP, "", "", firmware});
|
||||
}
|
||||
|
||||
for(auto &node : root) {
|
||||
|
@ -290,29 +269,7 @@ void Cartridge::parse_markup_hitachidsp(XML::Node &root) {
|
|||
string firmware = root["firmware"].data;
|
||||
string sha256 = root["sha256"].data;
|
||||
|
||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
||||
file fp;
|
||||
if(fp.open(path, file::mode::read) == false) {
|
||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is missing." });
|
||||
} else if(fp.size() != 1024 * 3) {
|
||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " is of the wrong file size." });
|
||||
fp.close();
|
||||
} else {
|
||||
for(unsigned n = 0; n < 1024; n++) hitachidsp.dataROM[n] = fp.readl(3);
|
||||
|
||||
if(!sha256.empty()) {
|
||||
//XML file specified SHA256 sum for program. Verify file matches the hash.
|
||||
fp.seek(0);
|
||||
uint8 data[3072];
|
||||
fp.read(data, 3072);
|
||||
|
||||
if(sha256 != nall::sha256(data, 3072)) {
|
||||
interface->message({ "Warning: Hitachi DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
||||
}
|
||||
}
|
||||
|
||||
fp.close();
|
||||
}
|
||||
interface->mediaRequest({ID::HitachiDSP, "", "", firmware});
|
||||
|
||||
for(auto &node : root) {
|
||||
if(node.name == "rom") {
|
||||
|
@ -340,24 +297,7 @@ void Cartridge::parse_markup_armdsp(XML::Node &root) {
|
|||
string firmware = root["firmware"].data;
|
||||
string sha256 = root["sha256"].data;
|
||||
|
||||
string path = interface->path((unsigned)Slot::Base, firmware);
|
||||
file fp;
|
||||
if(fp.open(path, file::mode::read) == false) {
|
||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " is missing." });
|
||||
} else if(fp.size() != 160 * 1024) {
|
||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " is of the wrong file size." });
|
||||
fp.close();
|
||||
} else {
|
||||
fp.read(armdsp.firmware, fp.size());
|
||||
|
||||
if(!sha256.empty()) {
|
||||
if(sha256 != nall::sha256(armdsp.firmware, fp.size())) {
|
||||
interface->message({ "Warning: ARM DSP firmware ", firmware, " SHA256 sum is incorrect." });
|
||||
}
|
||||
}
|
||||
|
||||
fp.close();
|
||||
}
|
||||
interface->mediaRequest({ID::ArmDSP, "", "", firmware});
|
||||
|
||||
for(auto &node : root) {
|
||||
if(node.name != "map") continue;
|
||||
|
@ -372,7 +312,7 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
|
|||
has_bs_cart = root["mmio"].exists();
|
||||
has_bs_slot = true;
|
||||
|
||||
interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
||||
interface->mediaRequest({ID::BsxFlashROM, "BS-X Satellaview", "", "program.rom", "bs"});
|
||||
|
||||
for(auto &node : root["slot"]) {
|
||||
if(node.name != "map") continue;
|
||||
|
@ -398,9 +338,10 @@ void Cartridge::parse_markup_bsx(XML::Node &root) {
|
|||
|
||||
void Cartridge::parse_markup_sufamiturbo(XML::Node &root) {
|
||||
if(root.exists() == false) return;
|
||||
has_st_slot = true;
|
||||
has_st_slots = true;
|
||||
|
||||
interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo", "st", "program.rom"});
|
||||
interface->mediaRequest({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "", "program.rom", "st"});
|
||||
interface->mediaRequest({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "", "program.rom", "st"});
|
||||
|
||||
for(auto &slot : root) {
|
||||
if(slot.name != "slot") continue;
|
||||
|
|
|
@ -3,7 +3,7 @@ struct CheatCode {
|
|||
unsigned data;
|
||||
};
|
||||
|
||||
struct Cheat : public linear_vector<CheatCode> {
|
||||
struct Cheat : public vector<CheatCode> {
|
||||
uint8 *override;
|
||||
|
||||
bool enabled() const;
|
||||
|
|
|
@ -103,14 +103,14 @@ int16_t ICD2::inputPoll(unsigned port, unsigned device, unsigned id) {
|
|||
}
|
||||
|
||||
switch((GameBoy::Input)id) {
|
||||
case GameBoy::Input::Start: return data & 0x80;
|
||||
case GameBoy::Input::Select: return data & 0x40;
|
||||
case GameBoy::Input::B: return data & 0x20;
|
||||
case GameBoy::Input::A: return data & 0x10;
|
||||
case GameBoy::Input::Down: return data & 0x08;
|
||||
case GameBoy::Input::Up: return data & 0x04;
|
||||
case GameBoy::Input::Left: return data & 0x02;
|
||||
case GameBoy::Input::Right: return data & 0x01;
|
||||
case GameBoy::Input::Start: return (bool)(data & 0x80);
|
||||
case GameBoy::Input::Select: return (bool)(data & 0x40);
|
||||
case GameBoy::Input::B: return (bool)(data & 0x20);
|
||||
case GameBoy::Input::A: return (bool)(data & 0x10);
|
||||
case GameBoy::Input::Down: return (bool)(data & 0x08);
|
||||
case GameBoy::Input::Up: return (bool)(data & 0x04);
|
||||
case GameBoy::Input::Left: return (bool)(data & 0x02);
|
||||
case GameBoy::Input::Right: return (bool)(data & 0x01);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -34,7 +34,7 @@ void NECDSP::init() {
|
|||
|
||||
void NECDSP::load() {
|
||||
if(revision == Revision::uPD96050) {
|
||||
cartridge.nvram.append({ "upd96050.ram", (uint8_t*)dataRAM, 4096 });
|
||||
interface->memory.append({ID::NecDSPRAM, "upd96050.ram"});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,13 +11,13 @@ void SufamiTurbo::load() {
|
|||
slotB.ram.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||
|
||||
if(slotA.rom.data()) {
|
||||
cartridge.nvram.append({ "save.ram", slotA.ram.data(), slotA.ram.size(), Cartridge::Slot::SufamiTurboA });
|
||||
interface->memory.append({ID::SufamiTurboSlotARAM, "save.ram"});
|
||||
} else {
|
||||
slotA.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||
}
|
||||
|
||||
if(slotB.rom.data()) {
|
||||
cartridge.nvram.append({ "save.ram", slotB.ram.data(), slotB.ram.size(), Cartridge::Slot::SufamiTurboB });
|
||||
interface->memory.append({ID::SufamiTurboSlotBRAM, "save.ram"});
|
||||
} else {
|
||||
slotB.rom.map(allocate<uint8>(128 * 1024, 0xff), 128 * 1024);
|
||||
}
|
||||
|
|
|
@ -8,11 +8,63 @@ bool Interface::loaded() {
|
|||
return cartridge.loaded();
|
||||
}
|
||||
|
||||
string Interface::sha256() {
|
||||
return cartridge.sha256();
|
||||
}
|
||||
|
||||
unsigned Interface::group(unsigned id) {
|
||||
switch(id) {
|
||||
case ID::Nec7725DSP:
|
||||
case ID::Nec96050DSP:
|
||||
case ID::HitachiDSP:
|
||||
case ID::ArmDSP:
|
||||
case ID::ROM:
|
||||
case ID::RAM:
|
||||
case ID::NecDSPRAM:
|
||||
case ID::RTC:
|
||||
case ID::SPC7110RTC:
|
||||
case ID::BsxRAM:
|
||||
case ID::BsxPSRAM:
|
||||
return 0;
|
||||
case ID::SuperGameBoyROM:
|
||||
case ID::SuperGameBoyRAM:
|
||||
case ID::SuperGameBoyRTC:
|
||||
return 1;
|
||||
case ID::BsxFlashROM:
|
||||
return 2;
|
||||
case ID::SufamiTurboSlotAROM:
|
||||
case ID::SufamiTurboSlotARAM:
|
||||
return 3;
|
||||
case ID::SufamiTurboSlotBROM:
|
||||
case ID::SufamiTurboSlotBRAM:
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
||||
if(id == ID::IPLROM) {
|
||||
stream.read(smp.iplrom, min(64u, stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::Nec7725DSP) {
|
||||
for(unsigned n = 0; n < 2048; n++) necdsp.programROM[n] = stream.readl(3);
|
||||
for(unsigned n = 0; n < 1024; n++) necdsp.dataROM[n] = stream.readl(2);
|
||||
}
|
||||
|
||||
if(id == ID::Nec96050DSP) {
|
||||
for(unsigned n = 0; n < 16384; n++) necdsp.programROM[n] = stream.readl(3);
|
||||
for(unsigned n = 0; n < 2048; n++) necdsp.dataROM[n] = stream.readl(2);
|
||||
}
|
||||
|
||||
if(id == ID::HitachiDSP) {
|
||||
for(unsigned n = 0; n < 1024; n++) hitachidsp.dataROM[n] = stream.readl(3);
|
||||
}
|
||||
|
||||
if(id == ID::ArmDSP) {
|
||||
stream.read(armdsp.firmware, stream.size());
|
||||
}
|
||||
|
||||
if(id == ID::ROM) {
|
||||
cartridge.load(markup, stream);
|
||||
system.power();
|
||||
|
@ -40,6 +92,10 @@ void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
|||
stream.read(cartridge.ram.data(), min(cartridge.ram.size(), stream.size()));
|
||||
}
|
||||
|
||||
if(id == ID::NecDSPRAM) {
|
||||
for(unsigned n = 0; n < 2048; n++) necdsp.dataRAM[n] = stream.readl(2);
|
||||
}
|
||||
|
||||
if(id == ID::RTC) {
|
||||
stream.read(srtc.rtc, min(stream.size(), sizeof srtc.rtc));
|
||||
}
|
||||
|
@ -55,6 +111,14 @@ void Interface::load(unsigned id, const stream &stream, const string &markup) {
|
|||
if(id == ID::BsxPSRAM) {
|
||||
stream.read(bsxcartridge.psram.data(), min(stream.size(), bsxcartridge.psram.size()));
|
||||
}
|
||||
|
||||
if(id == ID::SufamiTurboSlotARAM) {
|
||||
sufamiturbo.slotA.ram.copy(stream);
|
||||
}
|
||||
|
||||
if(id == ID::SufamiTurboSlotBRAM) {
|
||||
sufamiturbo.slotB.ram.copy(stream);
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::save(unsigned id, const stream &stream) {
|
||||
|
@ -62,6 +126,10 @@ void Interface::save(unsigned id, const stream &stream) {
|
|||
stream.write(cartridge.ram.data(), cartridge.ram.size());
|
||||
}
|
||||
|
||||
if(id == ID::NecDSPRAM) {
|
||||
for(unsigned n = 0; n < 2048; n++) stream.writel(necdsp.dataRAM[n], 2);
|
||||
}
|
||||
|
||||
if(id == ID::RTC) {
|
||||
stream.write(srtc.rtc, sizeof srtc.rtc);
|
||||
}
|
||||
|
@ -77,6 +145,14 @@ void Interface::save(unsigned id, const stream &stream) {
|
|||
if(id == ID::BsxPSRAM) {
|
||||
stream.write(bsxcartridge.psram.data(), bsxcartridge.psram.size());
|
||||
}
|
||||
|
||||
if(id == ID::SufamiTurboSlotARAM) {
|
||||
stream.write(sufamiturbo.slotA.ram.data(), sufamiturbo.slotA.ram.size());
|
||||
}
|
||||
|
||||
if(id == ID::SufamiTurboSlotBRAM) {
|
||||
stream.write(sufamiturbo.slotB.ram.data(), sufamiturbo.slotB.ram.size());
|
||||
}
|
||||
}
|
||||
|
||||
void Interface::unload() {
|
||||
|
@ -108,6 +184,35 @@ bool Interface::unserialize(serializer &s) {
|
|||
return system.unserialize(s);
|
||||
}
|
||||
|
||||
void Interface::cheatSet(const lstring &list) {
|
||||
//Super Game Boy
|
||||
if(cartridge.has_gb_slot()) {
|
||||
GameBoy::cheat.reset();
|
||||
for(auto &code : list) {
|
||||
lstring codelist = code.split("+");
|
||||
for(auto &part : codelist) {
|
||||
unsigned addr, data, comp;
|
||||
part.trim();
|
||||
if(GameBoy::Cheat::decode(part, addr, data, comp)) GameBoy::cheat.append({addr, data, comp});
|
||||
}
|
||||
}
|
||||
GameBoy::cheat.synchronize();
|
||||
return;
|
||||
}
|
||||
|
||||
//Super Famicom, Broadcast Satellaview, Sufami Turbo
|
||||
cheat.reset();
|
||||
for(auto &code : list) {
|
||||
lstring codelist = code.split("+");
|
||||
for(auto &part : codelist) {
|
||||
unsigned addr, data;
|
||||
part.trim();
|
||||
if(Cheat::decode(part, addr, data)) cheat.append({addr, data});
|
||||
}
|
||||
}
|
||||
cheat.synchronize();
|
||||
}
|
||||
|
||||
void Interface::updatePalette() {
|
||||
video.generate_palette();
|
||||
}
|
||||
|
@ -123,67 +228,20 @@ Interface::Interface() {
|
|||
information.frequency = 32040;
|
||||
information.resettable = true;
|
||||
|
||||
information.media.append({"Super Famicom", "sfc"});
|
||||
information.media.append({"BS-X Satellaview", "bs" });
|
||||
information.media.append({"Sufami Turbo", "st" });
|
||||
information.media.append({"Super Game Boy", "gb" });
|
||||
|
||||
firmware.append({ID::IPLROM, "Super Famicom", "sys", "spc700.rom"});
|
||||
|
||||
media.append({ID::ROM, "Super Famicom", "sys", "program.rom", "sfc"});
|
||||
media.append({ID::ROM, "Super Game Boy", "sfc", "program.rom", "gb" });
|
||||
media.append({ID::ROM, "BS-X Satellaview", "sfc", "program.rom", "bs" });
|
||||
media.append({ID::ROM, "Sufami Turbo", "sfc", "program.rom", "st" });
|
||||
|
||||
{
|
||||
Schema schema(Media{ID::ROM, "Super Famicom", "sfc", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
Device device{0, ID::Port1 | ID::Port2, "None"};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema(Media{ID::ROM, "Super Game Boy", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::SuperGameBoyROM, "Game Boy", "gb", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema(Media{ID::ROM, "BS-X Satellaview", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::BsxFlashROM, "BS-X Satellaview", "bs", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Schema schema(Media{ID::ROM, "Sufami Turbo", "sfc", "program.rom"});
|
||||
schema.slot.append({ID::SufamiTurboSlotAROM, "Sufami Turbo - Slot A", "st", "program.rom"});
|
||||
schema.slot.append({ID::SufamiTurboSlotBROM, "Sufami Turbo - Slot B", "st", "program.rom"});
|
||||
this->schema.append(schema);
|
||||
}
|
||||
|
||||
{
|
||||
Port port{0, "Port 1"};
|
||||
port.device.append(none());
|
||||
port.device.append(controller());
|
||||
port.device.append(multitap());
|
||||
port.device.append(mouse());
|
||||
port.device.append(usart());
|
||||
this->port.append(port);
|
||||
}
|
||||
|
||||
{
|
||||
Port port{1, "Port 2"};
|
||||
port.device.append(none());
|
||||
port.device.append(controller());
|
||||
port.device.append(multitap());
|
||||
port.device.append(mouse());
|
||||
port.device.append(superScope());
|
||||
port.device.append(justifier());
|
||||
port.device.append(justifiers());
|
||||
this->port.append(port);
|
||||
}
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::none() {
|
||||
Port::Device device{0, "None"};
|
||||
return device;
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::controller() {
|
||||
Port::Device device{1, "Controller"};
|
||||
Device device{1, ID::Port1 | ID::Port2, "Controller"};
|
||||
device.input.append({ 0, 0, "B" });
|
||||
device.input.append({ 1, 0, "Y" });
|
||||
device.input.append({ 2, 0, "Select"});
|
||||
|
@ -197,11 +255,11 @@ Emulator::Interface::Port::Device Interface::controller() {
|
|||
device.input.append({10, 0, "L" });
|
||||
device.input.append({11, 0, "R" });
|
||||
device.order = {4, 5, 6, 7, 0, 8, 1, 9, 10, 11, 2, 3};
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::multitap() {
|
||||
Port::Device device{2, "Multitap"};
|
||||
{
|
||||
Device device{2, ID::Port1 | ID::Port2, "Multitap"};
|
||||
for(unsigned p = 1, n = 0; p <= 4; p++, n += 12) {
|
||||
device.input.append({n + 0, 0, {"Port ", p, " - ", "B" }});
|
||||
device.input.append({n + 1, 0, {"Port ", p, " - ", "Y" }});
|
||||
|
@ -218,21 +276,21 @@ Emulator::Interface::Port::Device Interface::multitap() {
|
|||
device.order.append(n + 4, n + 5, n + 6, n + 7, n + 0, n + 8);
|
||||
device.order.append(n + 1, n + 9, n + 10, n + 11, n + 2, n + 3);
|
||||
}
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::mouse() {
|
||||
Port::Device device{3, "Mouse"};
|
||||
{
|
||||
Device device{3, ID::Port1 | ID::Port2, "Mouse"};
|
||||
device.input.append({0, 1, "X-axis"});
|
||||
device.input.append({1, 1, "Y-axis"});
|
||||
device.input.append({2, 0, "Left" });
|
||||
device.input.append({3, 0, "Right" });
|
||||
device.order = {0, 1, 2, 3};
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::superScope() {
|
||||
Port::Device device{4, "Super Scope"};
|
||||
{
|
||||
Device device{4, ID::Port2, "Super Scope"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
|
@ -240,21 +298,21 @@ Emulator::Interface::Port::Device Interface::superScope() {
|
|||
device.input.append({4, 0, "Turbo" });
|
||||
device.input.append({5, 0, "Pause" });
|
||||
device.order = {0, 1, 2, 3, 4, 5};
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::justifier() {
|
||||
Port::Device device{5, "Justifier"};
|
||||
{
|
||||
Device device{5, ID::Port2, "Justifier"};
|
||||
device.input.append({0, 1, "X-axis" });
|
||||
device.input.append({1, 1, "Y-axis" });
|
||||
device.input.append({2, 0, "Trigger"});
|
||||
device.input.append({3, 0, "Start" });
|
||||
device.order = {0, 1, 2, 3};
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::justifiers() {
|
||||
Port::Device device{6, "Justifiers"};
|
||||
{
|
||||
Device device{6, ID::Port2, "Justifiers"};
|
||||
device.input.append({0, 1, "Port 1 - X-axis" });
|
||||
device.input.append({1, 1, "Port 1 - Y-axis" });
|
||||
device.input.append({2, 0, "Port 1 - Trigger"});
|
||||
|
@ -265,12 +323,24 @@ Emulator::Interface::Port::Device Interface::justifiers() {
|
|||
device.input.append({6, 0, "Port 2 - Trigger"});
|
||||
device.input.append({7, 0, "Port 2 - Start" });
|
||||
device.order.append(4, 5, 6, 7);
|
||||
return device;
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device Interface::usart() {
|
||||
Port::Device device{7, "Serial USART"};
|
||||
return device;
|
||||
{
|
||||
Device device{7, ID::Port1, "Serial USART"};
|
||||
this->device.append(device);
|
||||
}
|
||||
|
||||
port.append({ID::Port1, "Port 1"});
|
||||
port.append({ID::Port2, "Port 2"});
|
||||
|
||||
for(auto &device : this->device) {
|
||||
for(auto &port : this->port) {
|
||||
if(device.portmask & port.id) {
|
||||
port.device.append(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,16 +5,30 @@ namespace SuperFamicom {
|
|||
struct ID {
|
||||
enum : unsigned {
|
||||
IPLROM,
|
||||
Nec7725DSP,
|
||||
Nec96050DSP,
|
||||
HitachiDSP,
|
||||
ArmDSP,
|
||||
ROM,
|
||||
SuperGameBoyROM,
|
||||
BsxFlashROM,
|
||||
SufamiTurboSlotAROM,
|
||||
SufamiTurboSlotBROM,
|
||||
RAM,
|
||||
NecDSPRAM,
|
||||
RTC,
|
||||
SPC7110RTC,
|
||||
BsxRAM,
|
||||
BsxPSRAM,
|
||||
SuperGameBoyRAM,
|
||||
SuperGameBoyRTC,
|
||||
SufamiTurboSlotARAM,
|
||||
SufamiTurboSlotBRAM,
|
||||
};
|
||||
|
||||
enum : unsigned {
|
||||
Port1 = 1,
|
||||
Port2 = 2,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -23,6 +37,8 @@ struct Interface : Emulator::Interface {
|
|||
virtual void message(const string &text) {}
|
||||
|
||||
bool loaded();
|
||||
string sha256();
|
||||
unsigned group(unsigned id);
|
||||
void load(unsigned id, const stream &stream, const string &markup = "");
|
||||
void save(unsigned id, const stream &stream);
|
||||
void unload();
|
||||
|
@ -35,19 +51,11 @@ struct Interface : Emulator::Interface {
|
|||
serializer serialize();
|
||||
bool unserialize(serializer&);
|
||||
|
||||
void cheatSet(const lstring&);
|
||||
|
||||
void updatePalette();
|
||||
|
||||
Interface();
|
||||
|
||||
private:
|
||||
Port::Device none();
|
||||
Port::Device controller();
|
||||
Port::Device multitap();
|
||||
Port::Device mouse();
|
||||
Port::Device superScope();
|
||||
Port::Device justifier();
|
||||
Port::Device justifiers();
|
||||
Port::Device usart();
|
||||
};
|
||||
|
||||
extern Interface *interface;
|
||||
|
|
|
@ -60,7 +60,7 @@ void System::serialize_all(serializer &s) {
|
|||
#if defined(GAMEBOY)
|
||||
if(cartridge.has_gb_slot()) icd2.serialize(s);
|
||||
#endif
|
||||
if(cartridge.has_st_slot()) sufamiturbo.serialize(s);
|
||||
if(cartridge.has_st_slots()) sufamiturbo.serialize(s);
|
||||
if(cartridge.has_superfx()) superfx.serialize(s);
|
||||
if(cartridge.has_sa1()) sa1.serialize(s);
|
||||
if(cartridge.has_necdsp()) necdsp.serialize(s);
|
||||
|
|
|
@ -108,7 +108,7 @@ void System::load() {
|
|||
#endif
|
||||
if(cartridge.has_bs_cart()) bsxcartridge.load();
|
||||
if(cartridge.has_bs_slot()) bsxflash.load();
|
||||
if(cartridge.has_st_slot()) sufamiturbo.load();
|
||||
if(cartridge.has_st_slots()) sufamiturbo.load();
|
||||
if(cartridge.has_nss_dip()) nss.load();
|
||||
if(cartridge.has_superfx()) superfx.load();
|
||||
if(cartridge.has_sa1()) sa1.load();
|
||||
|
@ -133,7 +133,7 @@ void System::unload() {
|
|||
#endif
|
||||
if(cartridge.has_bs_cart()) bsxcartridge.unload();
|
||||
if(cartridge.has_bs_slot()) bsxflash.unload();
|
||||
if(cartridge.has_st_slot()) sufamiturbo.unload();
|
||||
if(cartridge.has_st_slots()) sufamiturbo.unload();
|
||||
if(cartridge.has_nss_dip()) nss.unload();
|
||||
if(cartridge.has_superfx()) superfx.unload();
|
||||
if(cartridge.has_sa1()) sa1.unload();
|
||||
|
|
|
@ -7,7 +7,8 @@ include gb/Makefile
|
|||
include gba/Makefile
|
||||
name := ethos
|
||||
|
||||
ui_objects := ui-ethos ui-configuration ui-interface ui-utility ui-input ui-window ui-general ui-settings
|
||||
ui_objects := ui-ethos ui-configuration ui-interface ui-utility
|
||||
ui_objects += ui-input ui-window ui-general ui-settings ui-tools
|
||||
ui_objects += phoenix ruby
|
||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
||||
|
||||
|
@ -46,6 +47,7 @@ obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/)
|
|||
obj/ui-window.o: $(ui)/window/window.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
|
||||
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
|
||||
|
||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||
$(call compile,$(rubyflags))
|
||||
|
|
|
@ -19,7 +19,7 @@ void Application::bootstrap() {
|
|||
system->callback.mediaRequest = {&Interface::mediaRequest, interface};
|
||||
|
||||
for(auto &firmware : system->firmware) {
|
||||
filestream fs{application->path({firmware.name, ".", firmware.extension, "/", firmware.path})};
|
||||
filestream fs{application->path({firmware.name, ".", firmware.type, "/", firmware.path})};
|
||||
system->load(firmware.id, fs);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,6 +79,9 @@ Application::Application(int argc, char **argv) {
|
|||
inputSettings = new InputSettings;
|
||||
hotkeySettings = new HotkeySettings;
|
||||
settings = new Settings;
|
||||
cheatDatabase = new CheatDatabase;
|
||||
cheatEditor = new CheatEditor;
|
||||
stateManager = new StateManager;
|
||||
windowManager->loadGeometry();
|
||||
presentation->setVisible();
|
||||
|
||||
|
@ -105,7 +108,7 @@ Application::Application(int argc, char **argv) {
|
|||
run();
|
||||
}
|
||||
|
||||
if(active && system().loaded()) utility->unload();
|
||||
utility->unload();
|
||||
config->save();
|
||||
browser->saveConfiguration();
|
||||
inputManager->saveConfiguration();
|
||||
|
|
|
@ -24,6 +24,7 @@ using namespace ruby;
|
|||
#include "window/window.hpp"
|
||||
#include "general/general.hpp"
|
||||
#include "settings/settings.hpp"
|
||||
#include "tools/tools.hpp"
|
||||
|
||||
Emulator::Interface& system();
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ void Browser::saveConfiguration() {
|
|||
|
||||
void Browser::bootstrap() {
|
||||
for(auto &emulator : application->emulator) {
|
||||
for(auto &media : emulator->information.media) {
|
||||
for(auto &media : emulator->media) {
|
||||
bool found = false;
|
||||
for(auto &folder : folderList) {
|
||||
if(folder.extension == media.extension) {
|
||||
|
|
|
@ -56,12 +56,14 @@ Presentation::Presentation() : active(nullptr) {
|
|||
loadStateMenu.setText("Load State");
|
||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].setText({"Slot ", 1 + n});
|
||||
resizeWindow.setText("Resize Window");
|
||||
cheatEditor.setText("Cheat Editor");
|
||||
stateManager.setText("State Manager");
|
||||
|
||||
append(loadMenu);
|
||||
for(auto &item : loadListDirect) loadMenu.append(*item);
|
||||
if(loadListSlotted.size() > 0) {
|
||||
for(auto &item : loadListSystem) loadMenu.append(*item);
|
||||
if(loadListSubsystem.size() > 0) {
|
||||
loadMenu.append(*new Separator);
|
||||
for(auto &item : loadListSlotted) loadMenu.append(*item);
|
||||
for(auto &item : loadListSubsystem) loadMenu.append(*item);
|
||||
}
|
||||
for(auto &system : emulatorList) append(system->menu);
|
||||
append(settingsMenu);
|
||||
|
@ -77,7 +79,7 @@ Presentation::Presentation() : active(nullptr) {
|
|||
toolsMenu.append(loadStateMenu);
|
||||
for(unsigned n = 0; n < 5; n++) loadStateMenu.append(loadStateItem[n]);
|
||||
toolsMenu.append(stateMenuSeparator);
|
||||
toolsMenu.append(resizeWindow);
|
||||
toolsMenu.append(resizeWindow, cheatEditor, stateManager);
|
||||
|
||||
append(layout);
|
||||
layout.append(viewport, {0, 0, 720, 480});
|
||||
|
@ -97,6 +99,8 @@ Presentation::Presentation() : active(nullptr) {
|
|||
for(unsigned n = 0; n < 5; n++) saveStateItem[n].onActivate = [=] { utility->saveState(1 + n); };
|
||||
for(unsigned n = 0; n < 5; n++) loadStateItem[n].onActivate = [=] { utility->loadState(1 + n); };
|
||||
resizeWindow.onActivate = [&] { utility->resize(true); };
|
||||
cheatEditor.onActivate = [&] { ::cheatEditor->setVisible(); };
|
||||
stateManager.onActivate = [&] { ::stateManager->setVisible(); };
|
||||
|
||||
synchronize();
|
||||
}
|
||||
|
@ -106,14 +110,14 @@ void Presentation::bootstrap() {
|
|||
auto iEmulator = new Emulator;
|
||||
iEmulator->interface = emulator;
|
||||
|
||||
for(auto &schema : emulator->schema) {
|
||||
for(auto &media : emulator->media) {
|
||||
Item *item = new Item;
|
||||
item->setText({schema.name, " ..."});
|
||||
item->onActivate = [=, &schema] {
|
||||
utility->loadSchema(iEmulator->interface, schema);
|
||||
item->setText({media.name, " ..."});
|
||||
item->onActivate = [=, &media] {
|
||||
utility->loadMedia(iEmulator->interface, media);
|
||||
};
|
||||
if(schema.slot.size() == 0) loadListDirect.append(item);
|
||||
if(schema.slot.size() >= 1) loadListSlotted.append(item);
|
||||
if(media.type == "sys") loadListSystem.append(item);
|
||||
if(media.type != "sys") loadListSubsystem.append(item);
|
||||
}
|
||||
|
||||
iEmulator->menu.setText(emulator->information.name);
|
||||
|
|
|
@ -21,8 +21,8 @@ struct Presentation : Window {
|
|||
vector<Emulator*> emulatorList;
|
||||
|
||||
Menu loadMenu;
|
||||
vector<Item*> loadListDirect;
|
||||
vector<Item*> loadListSlotted;
|
||||
vector<Item*> loadListSystem;
|
||||
vector<Item*> loadListSubsystem;
|
||||
Menu settingsMenu;
|
||||
Menu videoMenu;
|
||||
RadioItem centerVideo;
|
||||
|
@ -41,6 +41,8 @@ struct Presentation : Window {
|
|||
Item loadStateItem[5];
|
||||
Separator stateMenuSeparator;
|
||||
Item resizeWindow;
|
||||
Item cheatEditor;
|
||||
Item stateManager;
|
||||
|
||||
void synchronize();
|
||||
void setSystemName(const string &name);
|
||||
|
|
|
@ -97,11 +97,5 @@ int16_t Interface::inputPoll(unsigned port, unsigned device, unsigned input) {
|
|||
}
|
||||
|
||||
void Interface::mediaRequest(Emulator::Interface::Media media) {
|
||||
string pathname = browser->select({"Load ", media.name}, media.extension);
|
||||
if(pathname.empty()) return;
|
||||
|
||||
string markup;
|
||||
markup.readfile({pathname, "manifest.xml"});
|
||||
mmapstream stream({pathname, media.path});
|
||||
system().load(media.id, stream, markup);
|
||||
utility->loadMedia(media);
|
||||
}
|
||||
|
|
|
@ -6,23 +6,23 @@ HotkeySettings::HotkeySettings() : activeInput(nullptr) {
|
|||
|
||||
inputList.setHeaderText("Name", "Mapping");
|
||||
inputList.setHeaderVisible();
|
||||
clearButton.setText("Clear");
|
||||
eraseButton.setText("Erase");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(inputList, {~0, ~0}, 5);
|
||||
append(controlLayout, {~0, 0});
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(clearButton, {80, 0});
|
||||
controlLayout.append(eraseButton, {80, 0});
|
||||
|
||||
inputList.onChange = {&HotkeySettings::synchronize, this};
|
||||
inputList.onActivate = {&HotkeySettings::assignInput, this};
|
||||
clearButton.onActivate = {&HotkeySettings::clearInput, this};
|
||||
eraseButton.onActivate = {&HotkeySettings::eraseInput, this};
|
||||
|
||||
refresh();
|
||||
}
|
||||
|
||||
void HotkeySettings::synchronize() {
|
||||
clearButton.setEnabled(inputList.selected());
|
||||
eraseButton.setEnabled(inputList.selected());
|
||||
}
|
||||
|
||||
void HotkeySettings::refresh() {
|
||||
|
@ -37,7 +37,7 @@ void HotkeySettings::refresh() {
|
|||
synchronize();
|
||||
}
|
||||
|
||||
void HotkeySettings::clearInput() {
|
||||
void HotkeySettings::eraseInput() {
|
||||
activeInput = inputManager->hotkeyMap[inputList.selection()];
|
||||
inputEvent(Scancode::None, 1);
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ struct HotkeySettings : SettingsLayout {
|
|||
ListView inputList;
|
||||
HorizontalLayout controlLayout;
|
||||
Widget spacer;
|
||||
Button clearButton;
|
||||
Button eraseButton;
|
||||
|
||||
void synchronize();
|
||||
void refresh();
|
||||
void clearInput();
|
||||
void eraseInput();
|
||||
void assignInput();
|
||||
void inputEvent(unsigned scancode, int16_t value);
|
||||
HotkeySettings();
|
||||
|
|
|
@ -8,7 +8,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
|||
focusAllow.setText("Allow Input");
|
||||
inputList.setHeaderText("Name", "Mapping");
|
||||
inputList.setHeaderVisible();
|
||||
clearButton.setText("Clear");
|
||||
resetButton.setText("Reset");
|
||||
eraseButton.setText("Erase");
|
||||
|
||||
append(title, {~0, 0}, 5);
|
||||
append(focusLayout, {~0, 0}, 5);
|
||||
|
@ -25,7 +26,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
|||
controlLayout.append(assign[1], {100, 0}, 5);
|
||||
controlLayout.append(assign[2], {100, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(clearButton, {80, 0});
|
||||
controlLayout.append(resetButton, {80, 0}, 5);
|
||||
controlLayout.append(eraseButton, {80, 0});
|
||||
|
||||
for(auto &emulator : application->emulator) {
|
||||
systemList.append(emulator->information.name);
|
||||
|
@ -45,7 +47,8 @@ InputSettings::InputSettings() : activeInput(nullptr) {
|
|||
assign[0].onActivate = [&] { assignMouseInput(0); };
|
||||
assign[1].onActivate = [&] { assignMouseInput(1); };
|
||||
assign[2].onActivate = [&] { assignMouseInput(2); };
|
||||
clearButton.onActivate = {&InputSettings::clearInput, this};
|
||||
resetButton.onActivate = {&InputSettings::resetInput, this};
|
||||
eraseButton.onActivate = {&InputSettings::eraseInput, this};
|
||||
|
||||
systemChanged();
|
||||
}
|
||||
|
@ -78,7 +81,7 @@ void InputSettings::synchronize() {
|
|||
}
|
||||
}
|
||||
|
||||
clearButton.setEnabled(inputList.selected());
|
||||
eraseButton.setEnabled(inputList.selected());
|
||||
}
|
||||
|
||||
Emulator::Interface& InputSettings::activeSystem() {
|
||||
|
@ -89,7 +92,7 @@ Emulator::Interface::Port& InputSettings::activePort() {
|
|||
return activeSystem().port[portList.selection()];
|
||||
}
|
||||
|
||||
Emulator::Interface::Port::Device& InputSettings::activeDevice() {
|
||||
Emulator::Interface::Device& InputSettings::activeDevice() {
|
||||
return activePort().device[deviceList.selection()];
|
||||
}
|
||||
|
||||
|
@ -123,7 +126,19 @@ void InputSettings::deviceChanged() {
|
|||
synchronize();
|
||||
}
|
||||
|
||||
void InputSettings::clearInput() {
|
||||
void InputSettings::resetInput() {
|
||||
if(MessageWindow::question(*settings, "All inputs will be erased. Are you sure you want to do this?")
|
||||
== MessageWindow::Response::No) return;
|
||||
|
||||
auto &device = activeDevice();
|
||||
unsigned length = device.input.size();
|
||||
for(unsigned n = 0; n < length; n++) {
|
||||
activeInput = inputManager->inputMap[device.input[n].guid];
|
||||
inputEvent(Scancode::None, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void InputSettings::eraseInput() {
|
||||
unsigned number = activeDevice().order[inputList.selection()];
|
||||
auto &input = activeDevice().input[number];
|
||||
activeInput = inputManager->inputMap[input.guid];
|
||||
|
|
|
@ -12,18 +12,20 @@ struct InputSettings : SettingsLayout {
|
|||
HorizontalLayout controlLayout;
|
||||
Button assign[3];
|
||||
Widget spacer;
|
||||
Button clearButton;
|
||||
Button resetButton;
|
||||
Button eraseButton;
|
||||
|
||||
void synchronize();
|
||||
|
||||
Emulator::Interface& activeSystem();
|
||||
Emulator::Interface::Port& activePort();
|
||||
Emulator::Interface::Port::Device& activeDevice();
|
||||
Emulator::Interface::Device& activeDevice();
|
||||
|
||||
void systemChanged();
|
||||
void portChanged();
|
||||
void deviceChanged();
|
||||
void clearInput();
|
||||
void resetInput();
|
||||
void eraseInput();
|
||||
void assignInput();
|
||||
void assignMouseInput(unsigned n);
|
||||
void inputEvent(unsigned scancode, int16_t value, bool allowMouseInput = false);
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
CheatDatabase *cheatDatabase = nullptr;
|
||||
|
||||
CheatDatabase::CheatDatabase() {
|
||||
setGeometry({256, 256, 600, 360});
|
||||
windowManager->append(this, "CheatDatabase");
|
||||
|
||||
layout.setMargin(5);
|
||||
cheatList.setCheckable();
|
||||
selectAllButton.setText("Select All");
|
||||
unselectAllButton.setText("Unselect All");
|
||||
acceptButton.setText("Add Codes");
|
||||
|
||||
append(layout);
|
||||
layout.append(cheatList, {~0, ~0}, 5);
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(selectAllButton, {100, 0}, 5);
|
||||
controlLayout.append(unselectAllButton, {100, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(acceptButton, {80, 0});
|
||||
|
||||
selectAllButton.onActivate = [&] {
|
||||
for(unsigned n = 0; n < cheat.size(); n++) cheatList.setChecked(n, true);
|
||||
};
|
||||
|
||||
unselectAllButton.onActivate = [&] {
|
||||
for(unsigned n = 0; n < cheat.size(); n++) cheatList.setChecked(n, false);
|
||||
};
|
||||
|
||||
acceptButton.onActivate = {&CheatDatabase::addCodes, this};
|
||||
}
|
||||
|
||||
void CheatDatabase::findCodes() {
|
||||
const string sha256 = system().sha256();
|
||||
cheatList.reset();
|
||||
cheat.reset();
|
||||
|
||||
string data;
|
||||
data.readfile(application->path("cheats.xml"));
|
||||
XML::Document document(data);
|
||||
for(auto &node : document["database"]) {
|
||||
if(node.name != "cartridge") continue;
|
||||
if(node["sha256"].data != sha256) continue;
|
||||
|
||||
setTitle(node["name"].data);
|
||||
for(auto &cheat : node) {
|
||||
if(cheat.name != "cheat") continue;
|
||||
cheatList.append(cheat["description"].data);
|
||||
|
||||
string codeList;
|
||||
for(auto &code : cheat) {
|
||||
if(code.name != "code") continue;
|
||||
codeList.append(code.data, "+");
|
||||
}
|
||||
codeList.rtrim<1>("+");
|
||||
|
||||
this->cheat.append({codeList, cheat["description"].data});
|
||||
}
|
||||
|
||||
setVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
MessageWindow::information(*cheatEditor, "Sorry, no cheat codes were found.");
|
||||
}
|
||||
|
||||
void CheatDatabase::addCodes() {
|
||||
for(unsigned n = 0; n < cheat.size(); n++) {
|
||||
if(cheatList.checked(n) == false) continue;
|
||||
if(cheatEditor->import(cheat[n].code, cheat[n].desc) == false) {
|
||||
MessageWindow::warning(*this, "Ran out of empty slots for cheat codes.\nNot all cheat codes were added.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setVisible(false);
|
||||
cheatEditor->synchronize();
|
||||
cheatEditor->refresh();
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
struct CheatDatabase : Window {
|
||||
VerticalLayout layout;
|
||||
ListView cheatList;
|
||||
HorizontalLayout controlLayout;
|
||||
Button selectAllButton;
|
||||
Button unselectAllButton;
|
||||
Widget spacer;
|
||||
Button acceptButton;
|
||||
|
||||
void findCodes();
|
||||
void addCodes();
|
||||
CheatDatabase();
|
||||
|
||||
private:
|
||||
struct Cheat {
|
||||
string code;
|
||||
string desc;
|
||||
};
|
||||
vector<Cheat> cheat;
|
||||
};
|
||||
|
||||
extern CheatDatabase *cheatDatabase;
|
|
@ -0,0 +1,187 @@
|
|||
CheatEditor *cheatEditor = nullptr;
|
||||
|
||||
CheatEditor::CheatEditor() {
|
||||
setGeometry({128, 128, 600, 360});
|
||||
windowManager->append(this, "CheatEditor");
|
||||
|
||||
setTitle("Cheat Editor");
|
||||
layout.setMargin(5);
|
||||
cheatList.setHeaderText("Slot", "Code", "Description");
|
||||
cheatList.setHeaderVisible();
|
||||
cheatList.setCheckable();
|
||||
for(unsigned n = 0; n < Codes; n++) cheatList.append("", "", "");
|
||||
codeLabel.setText("Code(s):");
|
||||
descLabel.setText("Description:");
|
||||
findButton.setText("Find Codes ...");
|
||||
resetButton.setText("Reset");
|
||||
eraseButton.setText("Erase");
|
||||
unsigned width = max(
|
||||
Font(application->normalFont).geometry("Codes(s)" ).width,
|
||||
Font(application->normalFont).geometry("Description:").width
|
||||
);
|
||||
|
||||
append(layout);
|
||||
layout.append(cheatList, {~0, ~0}, 5);
|
||||
layout.append(codeLayout, {~0, 0}, 5);
|
||||
codeLayout.append(codeLabel, {width, 0}, 5);
|
||||
codeLayout.append(codeEdit, {~0, 0});
|
||||
layout.append(descLayout, {~0, 0}, 5);
|
||||
descLayout.append(descLabel, {width, 0}, 5);
|
||||
descLayout.append(descEdit, {~0, 0});
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(findButton, {0, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(resetButton, {80, 0}, 5);
|
||||
controlLayout.append(eraseButton, {80, 0});
|
||||
|
||||
cheatList.onChange = {&CheatEditor::synchronize, this};
|
||||
cheatList.onToggle = [&](unsigned) { update(); };
|
||||
codeEdit.onChange = {&CheatEditor::updateCode, this};
|
||||
descEdit.onChange = {&CheatEditor::updateDesc, this};
|
||||
findButton.onActivate = {&CheatDatabase::findCodes, cheatDatabase};
|
||||
resetButton.onActivate = [&] {
|
||||
if(MessageWindow::question(*this, "All codes will be erased. Are you sure you want to do this?")
|
||||
== MessageWindow::Response::Yes) reset();
|
||||
};
|
||||
eraseButton.onActivate = {&CheatEditor::erase, this};
|
||||
|
||||
cheatList.setSelection(0);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void CheatEditor::synchronize() {
|
||||
layout.setEnabled(application->active);
|
||||
|
||||
if(cheatList.selected()) {
|
||||
unsigned n = cheatList.selection();
|
||||
codeEdit.setText(cheat[n].code);
|
||||
descEdit.setText(cheat[n].desc);
|
||||
codeEdit.setEnabled(true);
|
||||
descEdit.setEnabled(true);
|
||||
eraseButton.setEnabled(true);
|
||||
} else {
|
||||
codeEdit.setText("");
|
||||
descEdit.setText("");
|
||||
codeEdit.setEnabled(false);
|
||||
descEdit.setEnabled(false);
|
||||
eraseButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CheatEditor::refresh() {
|
||||
for(unsigned n = 0; n < Codes; n++) {
|
||||
string code = cheat[n].code;
|
||||
string desc = cheat[n].code.empty() && cheat[n].desc.empty() ? "(empty)" : cheat[n].desc;
|
||||
lstring codes = code.split("+");
|
||||
if(codes.size() > 1) code = {codes[0], "+..."};
|
||||
cheatList.modify(n, decimal<3>(1 + n), code, desc);
|
||||
}
|
||||
cheatList.autoSizeColumns();
|
||||
}
|
||||
|
||||
void CheatEditor::update() {
|
||||
lstring codes;
|
||||
for(unsigned n = 0; n < Codes; n++) {
|
||||
string code = cheat[n].code;
|
||||
if(cheatList.checked(n) && !code.empty()) codes.append(code);
|
||||
}
|
||||
system().cheatSet(codes);
|
||||
}
|
||||
|
||||
void CheatEditor::reset() {
|
||||
for(unsigned n = 0; n < Codes; n++) {
|
||||
cheatList.setChecked(n, false);
|
||||
cheat[n].code = "";
|
||||
cheat[n].desc = "";
|
||||
}
|
||||
codeEdit.setText("");
|
||||
descEdit.setText("");
|
||||
refresh();
|
||||
update();
|
||||
}
|
||||
|
||||
void CheatEditor::erase() {
|
||||
unsigned n = cheatList.selection();
|
||||
cheatList.setChecked(n, false);
|
||||
cheat[n].code = "";
|
||||
cheat[n].desc = "";
|
||||
codeEdit.setText("");
|
||||
descEdit.setText("");
|
||||
refresh();
|
||||
update();
|
||||
}
|
||||
|
||||
void CheatEditor::updateCode() {
|
||||
unsigned n = cheatList.selection();
|
||||
cheat[n].code = codeEdit.text();
|
||||
refresh();
|
||||
update();
|
||||
}
|
||||
|
||||
void CheatEditor::updateDesc() {
|
||||
unsigned n = cheatList.selection();
|
||||
cheat[n].desc = descEdit.text();
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool CheatEditor::load(const string &filename) {
|
||||
string data;
|
||||
if(data.readfile(filename) == false) return false;
|
||||
|
||||
unsigned n = 0;
|
||||
XML::Document document(data);
|
||||
for(auto &node : document["cartridge"]) {
|
||||
if(node.name != "cheat") continue;
|
||||
cheatList.setChecked(n, node["enable"].data == "true");
|
||||
cheat[n].code = node["code"].data;
|
||||
cheat[n].desc = node["description"].data;
|
||||
if(++n >= Codes) break;
|
||||
}
|
||||
|
||||
refresh();
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheatEditor::save(const string &filename) {
|
||||
signed lastSave = -1;
|
||||
for(signed n = 127; n >= 0; n--) {
|
||||
if(!cheat[n].code.empty() || !cheat[n].desc.empty()) {
|
||||
lastSave = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(lastSave == -1) {
|
||||
unlink(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::write) == false) return false;
|
||||
|
||||
fp.print("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
fp.print("<cartridge sha256=\"", system().sha256(), "\">\n");
|
||||
for(unsigned n = 0; n <= lastSave; n++) {
|
||||
fp.print(" <cheat enable=\"", cheatList.checked(n) ? "true" : "false", "\">\n");
|
||||
fp.print(" <description><![CDATA[", cheat[n].desc, "]]></description>\n");
|
||||
fp.print(" <code><![CDATA[", cheat[n].code, "]]></code>\n");
|
||||
fp.print(" </cheat>\n");
|
||||
}
|
||||
fp.print("</cartridge>\n");
|
||||
fp.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheatEditor::import(const string &code, const string &desc) {
|
||||
for(unsigned n = 0; n < Codes; n++) {
|
||||
if(cheat[n].code.empty() && cheat[n].desc.empty()) {
|
||||
cheatList.setChecked(n, false);
|
||||
cheat[n].code = code;
|
||||
cheat[n].desc = desc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
struct CheatEditor : Window {
|
||||
VerticalLayout layout;
|
||||
ListView cheatList;
|
||||
HorizontalLayout codeLayout;
|
||||
Label codeLabel;
|
||||
LineEdit codeEdit;
|
||||
HorizontalLayout descLayout;
|
||||
Label descLabel;
|
||||
LineEdit descEdit;
|
||||
HorizontalLayout controlLayout;
|
||||
Button findButton;
|
||||
Widget spacer;
|
||||
Button resetButton;
|
||||
Button eraseButton;
|
||||
|
||||
void reset();
|
||||
void erase();
|
||||
void updateCode();
|
||||
void updateDesc();
|
||||
|
||||
bool load(const string &filename);
|
||||
bool save(const string &filename);
|
||||
bool import(const string &code, const string &desc);
|
||||
|
||||
void update();
|
||||
void refresh();
|
||||
void synchronize();
|
||||
CheatEditor();
|
||||
|
||||
private:
|
||||
enum : unsigned { Codes = 128 };
|
||||
struct Cheat {
|
||||
string code;
|
||||
string desc;
|
||||
};
|
||||
Cheat cheat[Codes];
|
||||
};
|
||||
|
||||
extern CheatEditor *cheatEditor;
|
|
@ -0,0 +1,155 @@
|
|||
StateManager *stateManager = nullptr;
|
||||
|
||||
StateManager::StateManager() {
|
||||
setGeometry({128, 128, 600, 360});
|
||||
windowManager->append(this, "StateManager");
|
||||
|
||||
setTitle("State Manager");
|
||||
layout.setMargin(5);
|
||||
stateList.setHeaderText("Slot", "Description");
|
||||
stateList.setHeaderVisible();
|
||||
for(unsigned n = 0; n < Slots; n++) stateList.append(decimal<2>(1 + n), "(empty)");
|
||||
stateList.autoSizeColumns();
|
||||
descLabel.setText("Description:");
|
||||
saveButton.setText("Save");
|
||||
loadButton.setText("Load");
|
||||
resetButton.setText("Reset");
|
||||
eraseButton.setText("Erase");
|
||||
|
||||
append(layout);
|
||||
layout.append(stateList, {~0, ~0}, 5);
|
||||
layout.append(descLayout, {~0, 0}, 5);
|
||||
descLayout.append(descLabel, {0, 0}, 5);
|
||||
descLayout.append(descEdit, {~0, 0});
|
||||
layout.append(controlLayout, {~0, 0});
|
||||
controlLayout.append(saveButton, {80, 0}, 5);
|
||||
controlLayout.append(loadButton, {80, 0}, 5);
|
||||
controlLayout.append(spacer, {~0, 0});
|
||||
controlLayout.append(resetButton, {80, 0}, 5);
|
||||
controlLayout.append(eraseButton, {80, 0});
|
||||
|
||||
stateList.onChange = {&StateManager::synchronize, this};
|
||||
stateList.onActivate = {&StateManager::slotLoad, this};
|
||||
descEdit.onChange = {&StateManager::slotSaveDescription, this};
|
||||
saveButton.onActivate = {&StateManager::slotSave, this};
|
||||
loadButton.onActivate = {&StateManager::slotLoad, this};
|
||||
resetButton.onActivate = [&] {
|
||||
if(MessageWindow::question(*this, "All states will be erased. Are you sure you want to do this?")
|
||||
== MessageWindow::Response::Yes) reset();
|
||||
};
|
||||
eraseButton.onActivate = {&StateManager::slotErase, this};
|
||||
|
||||
stateList.setSelection(0);
|
||||
synchronize();
|
||||
}
|
||||
|
||||
void StateManager::synchronize() {
|
||||
layout.setEnabled(application->active);
|
||||
|
||||
descEdit.setText("");
|
||||
descEdit.setEnabled(false);
|
||||
controlLayout.setEnabled(stateList.selected());
|
||||
if(stateList.selected() == false) return;
|
||||
|
||||
if(slot[stateList.selection()].capacity() > 0) {
|
||||
descEdit.setText(slotLoadDescription(stateList.selection()));
|
||||
descEdit.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::refresh() {
|
||||
for(unsigned n = 0; n < Slots; n++) {
|
||||
stateList.modify(n, decimal<2>(1 + n), slotLoadDescription(n));
|
||||
}
|
||||
stateList.autoSizeColumns();
|
||||
}
|
||||
|
||||
void StateManager::reset() {
|
||||
for(auto &slot : this->slot) slot = serializer();
|
||||
synchronize();
|
||||
refresh();
|
||||
}
|
||||
|
||||
bool StateManager::load(const string &filename, unsigned revision) {
|
||||
for(auto &slot : this->slot) slot = serializer();
|
||||
synchronize();
|
||||
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::read) == false) return false;
|
||||
|
||||
if(fp.readl(4) == 0x31415342 /* 'BSA1' */ && fp.readl(4) == revision) {
|
||||
for(auto &slot : this->slot) {
|
||||
if(fp.read() == false) continue; //slot is empty
|
||||
unsigned size = fp.readl(4);
|
||||
uint8_t *data = new uint8_t[size];
|
||||
fp.read(data, size);
|
||||
slot = serializer(data, size);
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
|
||||
refresh();
|
||||
synchronize();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool StateManager::save(const string &filename, unsigned revision) {
|
||||
bool hasSave = false;
|
||||
for(auto &slot : this->slot) hasSave |= slot.capacity() > 0;
|
||||
if(hasSave == false) {
|
||||
unlink(filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
file fp;
|
||||
if(fp.open(filename, file::mode::write) == false) return false;
|
||||
|
||||
fp.writel(0x31415342, 4); //'BSA1'
|
||||
fp.writel(revision, 4);
|
||||
for(auto &slot : this->slot) {
|
||||
fp.write(slot.capacity() > 0);
|
||||
if(slot.capacity()) {
|
||||
fp.writel(slot.capacity(), 4);
|
||||
fp.write(slot.data(), slot.capacity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StateManager::slotLoad() {
|
||||
if(stateList.selected() == false) return;
|
||||
serializer s(slot[stateList.selection()].data(), slot[stateList.selection()].capacity());
|
||||
system().unserialize(s);
|
||||
}
|
||||
|
||||
void StateManager::slotSave() {
|
||||
if(stateList.selected()) {
|
||||
slot[stateList.selection()] = system().serialize();
|
||||
}
|
||||
refresh();
|
||||
synchronize();
|
||||
descEdit.setFocused();
|
||||
}
|
||||
|
||||
void StateManager::slotErase() {
|
||||
if(stateList.selected()) {
|
||||
slot[stateList.selection()] = serializer();
|
||||
}
|
||||
refresh();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
string StateManager::slotLoadDescription(unsigned id) {
|
||||
if(slot[id].capacity() == 0) return "(empty)";
|
||||
char text[DescriptionLength];
|
||||
strmcpy(text, (const char*)slot[id].data() + HeaderLength, DescriptionLength);
|
||||
return text;
|
||||
}
|
||||
|
||||
void StateManager::slotSaveDescription() {
|
||||
if(stateList.selected() == false) return;
|
||||
string text = descEdit.text();
|
||||
if(slot[stateList.selection()].capacity() > 0) {
|
||||
strmcpy((char*)slot[stateList.selection()].data() + HeaderLength, (const char*)text, DescriptionLength);
|
||||
}
|
||||
refresh();
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
struct StateManager : Window {
|
||||
VerticalLayout layout;
|
||||
ListView stateList;
|
||||
HorizontalLayout descLayout;
|
||||
Label descLabel;
|
||||
LineEdit descEdit;
|
||||
HorizontalLayout controlLayout;
|
||||
Button saveButton;
|
||||
Button loadButton;
|
||||
Widget spacer;
|
||||
Button resetButton;
|
||||
Button eraseButton;
|
||||
|
||||
void reset();
|
||||
bool load(const string &filename, unsigned revision);
|
||||
bool save(const string &filename, unsigned revision);
|
||||
|
||||
void slotLoad();
|
||||
void slotSave();
|
||||
void slotErase();
|
||||
string slotLoadDescription(unsigned id);
|
||||
void slotSaveDescription();
|
||||
|
||||
void refresh();
|
||||
void synchronize();
|
||||
StateManager();
|
||||
|
||||
private:
|
||||
enum : unsigned {
|
||||
Slots = 32,
|
||||
HeaderLength = 72,
|
||||
DescriptionLength = 512,
|
||||
};
|
||||
serializer slot[Slots];
|
||||
};
|
||||
|
||||
extern StateManager *stateManager;
|
|
@ -0,0 +1,4 @@
|
|||
#include "../ethos.hpp"
|
||||
#include "cheat-database.cpp"
|
||||
#include "cheat-editor.cpp"
|
||||
#include "state-manager.cpp"
|
|
@ -0,0 +1,3 @@
|
|||
#include "cheat-database.hpp"
|
||||
#include "cheat-editor.hpp"
|
||||
#include "state-manager.hpp"
|
|
@ -7,44 +7,69 @@ void Utility::setInterface(Emulator::Interface *emulator) {
|
|||
presentation->synchronize();
|
||||
}
|
||||
|
||||
void Utility::loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema) {
|
||||
string pathname = application->path({schema.name, ".", schema.extension, "/"});
|
||||
if(!directory::exists(pathname)) pathname = browser->select({"Load ", schema.name}, schema.extension);
|
||||
if(!directory::exists(pathname)) return;
|
||||
|
||||
loadMedia(emulator, schema, pathname);
|
||||
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media) {
|
||||
string pathname = application->path({media.name, ".", media.type, "/"});
|
||||
if(!file::exists({pathname, media.path})) pathname = browser->select({"Load ", media.name}, media.extension);
|
||||
if(!file::exists({pathname, media.path})) return;
|
||||
loadMedia(emulator, media, pathname);
|
||||
}
|
||||
|
||||
void Utility::loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname) {
|
||||
unload();
|
||||
setInterface(emulator);
|
||||
this->pathname = pathname;
|
||||
path(system().group(media.id)) = pathname;
|
||||
if(media.type == "sys") this->pathname.append(pathname);
|
||||
|
||||
string manifest;
|
||||
manifest.readfile({pathname, "manifest.xml"});
|
||||
auto memory = file::read({pathname, media.path});
|
||||
system().load(media.id, vectorstream{memory}, manifest);
|
||||
|
||||
if(this->pathname.size() == 0) this->pathname.append(pathname);
|
||||
presentation->setSystemName(media.name);
|
||||
load();
|
||||
}
|
||||
|
||||
void Utility::loadMedia(Emulator::Interface::Media &media) {
|
||||
string pathname = {path(system().group(media.id)), media.path};
|
||||
if(file::exists(pathname)) {
|
||||
mmapstream stream(pathname);
|
||||
return system().load(media.id, stream);
|
||||
}
|
||||
if(media.name.empty()) return;
|
||||
|
||||
pathname = browser->select({"Load ", media.name}, media.extension);
|
||||
if(pathname.empty()) return;
|
||||
path(system().group(media.id)) = pathname;
|
||||
this->pathname.append(pathname);
|
||||
|
||||
string markup;
|
||||
markup.readfile({pathname, "manifest.xml"});
|
||||
mmapstream stream({pathname, media.path});
|
||||
system().load(media.id, stream, markup);
|
||||
}
|
||||
|
||||
void Utility::loadMemory() {
|
||||
for(auto &memory : system().memory) {
|
||||
string pathname = path(system().group(memory.id));
|
||||
filestream fs({pathname, memory.name});
|
||||
system().load(memory.id, fs);
|
||||
}
|
||||
|
||||
system().updatePalette();
|
||||
dspaudio.setFrequency(emulator->information.frequency);
|
||||
|
||||
string displayname = pathname;
|
||||
displayname.rtrim<1>("/");
|
||||
presentation->setTitle(notdir(nall::basename(displayname)));
|
||||
presentation->setSystemName(media.name);
|
||||
resize();
|
||||
cheatEditor->load({pathname[0], "cheats.xml"});
|
||||
stateManager->load({pathname[0], "bsnes/states.bsa"}, 1);
|
||||
}
|
||||
|
||||
void Utility::saveMedia() {
|
||||
void Utility::saveMemory() {
|
||||
for(auto &memory : system().memory) {
|
||||
string pathname = path(system().group(memory.id));
|
||||
filestream fs({pathname, memory.name}, file::mode::write);
|
||||
system().save(memory.id, fs);
|
||||
}
|
||||
|
||||
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||
cheatEditor->save({pathname[0], "cheats.xml"});
|
||||
stateManager->save({pathname[0], "bsnes/states.bsa"}, 1);
|
||||
}
|
||||
|
||||
void Utility::connect(unsigned port, unsigned device) {
|
||||
|
@ -62,12 +87,37 @@ void Utility::reset() {
|
|||
system().reset();
|
||||
}
|
||||
|
||||
void Utility::unload() {
|
||||
if(application->active) {
|
||||
saveMedia();
|
||||
system().unload();
|
||||
setInterface(nullptr);
|
||||
void Utility::load() {
|
||||
string title;
|
||||
for(auto &path : pathname) {
|
||||
string name = path;
|
||||
name.rtrim<1>("/");
|
||||
title.append(notdir(nall::basename(name)), " + ");
|
||||
}
|
||||
title.rtrim<1>(" + ");
|
||||
presentation->setTitle(title);
|
||||
|
||||
loadMemory();
|
||||
|
||||
system().updatePalette();
|
||||
dspaudio.setFrequency(system().information.frequency);
|
||||
|
||||
resize();
|
||||
cheatEditor->synchronize();
|
||||
cheatEditor->refresh();
|
||||
}
|
||||
|
||||
void Utility::unload() {
|
||||
if(application->active == nullptr) return;
|
||||
|
||||
saveMemory();
|
||||
system().unload();
|
||||
path.reset();
|
||||
pathname.reset();
|
||||
cheatEditor->reset();
|
||||
stateManager->reset();
|
||||
setInterface(nullptr);
|
||||
|
||||
presentation->setTitle({Emulator::Name, " v", Emulator::Version});
|
||||
video.clear();
|
||||
}
|
||||
|
@ -76,14 +126,14 @@ void Utility::saveState(unsigned slot) {
|
|||
if(application->active == nullptr) return;
|
||||
serializer s = system().serialize();
|
||||
if(s.size() == 0) return;
|
||||
mkdir(string{pathname, "bsnes/"}, 0755);
|
||||
if(file::write({pathname, "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
||||
mkdir(string{pathname[0], "bsnes/"}, 0755);
|
||||
if(file::write({pathname[0], "bsnes/state-", slot, ".bsa"}, s.data(), s.size()) == false);
|
||||
showMessage({"Save to slot ", slot});
|
||||
}
|
||||
|
||||
void Utility::loadState(unsigned slot) {
|
||||
if(application->active == nullptr) return;
|
||||
auto memory = file::read({pathname, "bsnes/state-", slot, ".bsa"});
|
||||
auto memory = file::read({pathname[0], "bsnes/state-", slot, ".bsa"});
|
||||
if(memory.size() == 0) return;
|
||||
serializer s(memory.data(), memory.size());
|
||||
if(system().unserialize(s) == false) return;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
struct Utility {
|
||||
string pathname;
|
||||
|
||||
void setInterface(Emulator::Interface *emulator);
|
||||
void loadSchema(Emulator::Interface *emulator, Emulator::Interface::Schema &schema);
|
||||
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media);
|
||||
void loadMedia(Emulator::Interface *emulator, Emulator::Interface::Media &media, const string &pathname);
|
||||
void saveMedia();
|
||||
void loadMedia(Emulator::Interface::Media &media);
|
||||
void loadMemory();
|
||||
void saveMemory();
|
||||
|
||||
void connect(unsigned port, unsigned device);
|
||||
void power();
|
||||
void reset();
|
||||
void load();
|
||||
void unload();
|
||||
|
||||
void saveState(unsigned slot);
|
||||
|
@ -24,6 +25,9 @@ struct Utility {
|
|||
|
||||
Utility();
|
||||
|
||||
lstring path;
|
||||
lstring pathname;
|
||||
|
||||
private:
|
||||
string statusText;
|
||||
string statusMessage;
|
||||
|
|
Loading…
Reference in New Issue