diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 7a05e711..e6f9b57e 100755 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -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"; } diff --git a/bsnes/emulator/interface.hpp b/bsnes/emulator/interface.hpp index 05eb0521..0e8c1e98 100755 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -12,29 +12,18 @@ struct Interface { double aspectRatio; unsigned frequency; bool resettable; - - struct Media { - string name; - string extension; - }; - vector media; } information; struct Media { unsigned id; string name; - string extension; + string type; string path; + string extension; }; - vector firmware; - struct Schema : Media { - vector slot; - Schema(const Media &media) { - id = media.id, name = media.name, extension = media.extension, path = media.path; - } - }; - vector schema; + vector firmware; + vector media; struct Memory { unsigned id; @@ -42,21 +31,24 @@ struct Interface { }; vector memory; + struct Device { + unsigned id; + unsigned portmask; + string name; + struct Input { + unsigned id; + unsigned type; //0 = digital, 1 = analog + string name; + unsigned guid; + }; + vector input; + vector order; + }; + vector device; + struct Port { unsigned id; string name; - struct Device { - unsigned id; - string name; - struct Input { - unsigned id; - unsigned type; //0 = digital, 1 = analog - string name; - unsigned guid; - }; - vector input; - vector order; - }; vector device; }; vector 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() {} }; diff --git a/bsnes/fc/interface/interface.cpp b/bsnes/fc/interface/interface.cpp index 4c748802..a1b333ec 100755 --- a/bsnes/fc/interface/interface.cpp +++ b/bsnes/fc/interface/interface.cpp @@ -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,35 +83,32 @@ 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); + 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"}); + device.input.append({3, 0, "Start" }); + device.input.append({4, 0, "Up" }); + device.input.append({5, 0, "Down" }); + device.input.append({6, 0, "Left" }); + device.input.append({7, 0, "Right" }); + device.order = {4, 5, 6, 7, 1, 0, 2, 3}; + this->device.append(device); } - { - Port port{1, "Port 2"}; - port.device.append(controller()); - this->port.append(port); + 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); + } + } } } -Emulator::Interface::Port::Device Interface::controller() { - Port::Device device{0, "Controller"}; - device.input.append({0, 0, "A" }); - device.input.append({1, 0, "B" }); - device.input.append({2, 0, "Select"}); - device.input.append({3, 0, "Start" }); - device.input.append({4, 0, "Up" }); - device.input.append({5, 0, "Down" }); - device.input.append({6, 0, "Left" }); - device.input.append({7, 0, "Right" }); - device.order = {4, 5, 6, 7, 1, 0, 2, 3}; - return device; -} - } diff --git a/bsnes/fc/interface/interface.hpp b/bsnes/fc/interface/interface.hpp index 4351fac4..d167cc1a 100755 --- a/bsnes/fc/interface/interface.hpp +++ b/bsnes/fc/interface/interface.hpp @@ -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; diff --git a/bsnes/gb/interface/interface.cpp b/bsnes/gb/interface/interface.cpp index 4c473649..6e93f56c 100755 --- a/bsnes/gb/interface/interface.cpp +++ b/bsnes/gb/interface/interface.cpp @@ -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,33 +98,28 @@ 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.input.append({0, 0, "Up" }); - device.input.append({1, 0, "Down" }); - device.input.append({2, 0, "Left" }); - device.input.append({3, 0, "Right" }); - device.input.append({4, 0, "B" }); - device.input.append({5, 0, "A" }); - 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); + Device device{0, ID::Device, "Controller"}; + device.input.append({0, 0, "Up" }); + device.input.append({1, 0, "Down" }); + device.input.append({2, 0, "Left" }); + device.input.append({3, 0, "Right" }); + device.input.append({4, 0, "B" }); + device.input.append({5, 0, "A" }); + device.input.append({6, 0, "Select"}); + device.input.append({7, 0, "Start" }); + device.order = {0, 1, 2, 3, 4, 5, 6, 7}; + this->device.append(device); } + + port.append({ID::Device, "Device", {device[0]}}); } } diff --git a/bsnes/gb/interface/interface.hpp b/bsnes/gb/interface/interface.hpp index 4ebdaf80..b41e8209 100755 --- a/bsnes/gb/interface/interface.hpp +++ b/bsnes/gb/interface/interface.hpp @@ -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(); diff --git a/bsnes/gba/interface/interface.cpp b/bsnes/gba/interface/interface.cpp index 10674a3e..b5dee7c4 100755 --- a/bsnes/gba/interface/interface.cpp +++ b/bsnes/gba/interface/interface.cpp @@ -86,31 +86,27 @@ 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.input.append({0, 0, "A" }); - device.input.append({1, 0, "B" }); - device.input.append({2, 0, "Select"}); - device.input.append({3, 0, "Start" }); - device.input.append({4, 0, "Right" }); - device.input.append({5, 0, "Left" }); - device.input.append({6, 0, "Up" }); - device.input.append({7, 0, "Down" }); - 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); + Device device{0, ID::Device, "Controller"}; + device.input.append({0, 0, "A" }); + device.input.append({1, 0, "B" }); + device.input.append({2, 0, "Select"}); + device.input.append({3, 0, "Start" }); + device.input.append({4, 0, "Right" }); + device.input.append({5, 0, "Left" }); + device.input.append({6, 0, "Up" }); + device.input.append({7, 0, "Down" }); + device.input.append({8, 0, "R" }); + device.input.append({9, 0, "L" }); + device.order = {6, 7, 5, 4, 1, 0, 9, 8, 2, 3}; + this->device.append(device); } + + port.append({ID::Device, "Device", {device[0]}}); } } diff --git a/bsnes/gba/interface/interface.hpp b/bsnes/gba/interface/interface.hpp index 38a4e82e..8f58fa21 100755 --- a/bsnes/gba/interface/interface.hpp +++ b/bsnes/gba/interface/interface.hpp @@ -10,6 +10,10 @@ struct ID { EEPROM, FlashROM, }; + + enum : unsigned { + Device = 1, + }; }; struct Interface : Emulator::Interface { diff --git a/bsnes/sfc/cartridge/cartridge.cpp b/bsnes/sfc/cartridge/cartridge.cpp index 35877df7..afa8a9ba 100755 --- a/bsnes/sfc/cartridge/cartridge.cpp +++ b/bsnes/sfc/cartridge/cartridge.cpp @@ -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(ram_size, 0xff), ram_size); interface->memory.append({ID::RAM, "save.ram"}); diff --git a/bsnes/sfc/cartridge/cartridge.hpp b/bsnes/sfc/cartridge/cartridge.hpp index 5f55db0d..0ae79020 100755 --- a/bsnes/sfc/cartridge/cartridge.hpp +++ b/bsnes/sfc/cartridge/cartridge.hpp @@ -25,7 +25,7 @@ struct Cartridge : property { readonly has_gb_slot; readonly has_bs_cart; readonly has_bs_slot; - readonly has_st_slot; + readonly has_st_slots; readonly has_nss_dip; readonly has_superfx; readonly has_sa1; diff --git a/bsnes/sfc/cartridge/markup.cpp b/bsnes/sfc/cartridge/markup.cpp index 9ecbee2a..9a887541 100755 --- a/bsnes/sfc/cartridge/markup.cpp +++ b/bsnes/sfc/cartridge/markup.cpp @@ -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; + if(necdsp.revision == NECDSP::Revision::uPD7725) { + interface->mediaRequest({ID::Nec7725DSP, "", "", firmware}); + } - 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." }); - } - } - - 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; diff --git a/bsnes/sfc/cheat/cheat.hpp b/bsnes/sfc/cheat/cheat.hpp index 306b99b1..2ea2ebc9 100755 --- a/bsnes/sfc/cheat/cheat.hpp +++ b/bsnes/sfc/cheat/cheat.hpp @@ -3,7 +3,7 @@ struct CheatCode { unsigned data; }; -struct Cheat : public linear_vector { +struct Cheat : public vector { uint8 *override; bool enabled() const; diff --git a/bsnes/sfc/chip/icd2/interface/interface.cpp b/bsnes/sfc/chip/icd2/interface/interface.cpp index 3b046f0d..6c47e63f 100755 --- a/bsnes/sfc/chip/icd2/interface/interface.cpp +++ b/bsnes/sfc/chip/icd2/interface/interface.cpp @@ -96,21 +96,21 @@ int16_t ICD2::inputPoll(unsigned port, unsigned device, unsigned id) { unsigned data = 0x00; switch(joyp_id & mlt_req) { - case 0: data = ~r6004; break; - case 1: data = ~r6005; break; - case 2: data = ~r6006; break; - case 3: data = ~r6007; break; + case 0: data = ~r6004; break; + case 1: data = ~r6005; break; + case 2: data = ~r6006; break; + case 3: data = ~r6007; break; } 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; diff --git a/bsnes/sfc/chip/necdsp/necdsp.cpp b/bsnes/sfc/chip/necdsp/necdsp.cpp index 05bc0450..8b27aa51 100755 --- a/bsnes/sfc/chip/necdsp/necdsp.cpp +++ b/bsnes/sfc/chip/necdsp/necdsp.cpp @@ -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"}); } } diff --git a/bsnes/sfc/chip/sufamiturbo/sufamiturbo.cpp b/bsnes/sfc/chip/sufamiturbo/sufamiturbo.cpp index b1d16338..fe7e5996 100755 --- a/bsnes/sfc/chip/sufamiturbo/sufamiturbo.cpp +++ b/bsnes/sfc/chip/sufamiturbo/sufamiturbo.cpp @@ -11,13 +11,13 @@ void SufamiTurbo::load() { slotB.ram.map(allocate(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(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(128 * 1024, 0xff), 128 * 1024); } diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index 2c5dab3c..7c50021e 100755 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -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,154 +228,119 @@ 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); + 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"}); + device.input.append({ 3, 0, "Start" }); + device.input.append({ 4, 0, "Up" }); + device.input.append({ 5, 0, "Down" }); + device.input.append({ 6, 0, "Left" }); + device.input.append({ 7, 0, "Right" }); + device.input.append({ 8, 0, "A" }); + device.input.append({ 9, 0, "X" }); + 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}; + this->device.append(device); } { - 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); + 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" }}); + device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}}); + device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }}); + device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }}); + device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }}); + device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }}); + device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }}); + device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }}); + device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }}); + device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }}); + device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }}); + 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); + } + this->device.append(device); } { - 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); + 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}; + this->device.append(device); } { - 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); + 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"}); + device.input.append({3, 0, "Cursor" }); + device.input.append({4, 0, "Turbo" }); + device.input.append({5, 0, "Pause" }); + device.order = {0, 1, 2, 3, 4, 5}; + this->device.append(device); } { - 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); + 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}; + this->device.append(device); + } + + { + 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"}); + device.input.append({3, 0, "Port 1 - Start" }); + device.order.append(0, 1, 2, 3); + device.input.append({4, 1, "Port 2 - X-axis" }); + device.input.append({5, 1, "Port 2 - Y-axis" }); + device.input.append({6, 0, "Port 2 - Trigger"}); + device.input.append({7, 0, "Port 2 - Start" }); + device.order.append(4, 5, 6, 7); + this->device.append(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); + } + } } } -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.input.append({ 0, 0, "B" }); - device.input.append({ 1, 0, "Y" }); - device.input.append({ 2, 0, "Select"}); - device.input.append({ 3, 0, "Start" }); - device.input.append({ 4, 0, "Up" }); - device.input.append({ 5, 0, "Down" }); - device.input.append({ 6, 0, "Left" }); - device.input.append({ 7, 0, "Right" }); - device.input.append({ 8, 0, "A" }); - device.input.append({ 9, 0, "X" }); - 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; -} - -Emulator::Interface::Port::Device Interface::multitap() { - Port::Device device{2, "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" }}); - device.input.append({n + 2, 0, {"Port ", p, " - ", "Select"}}); - device.input.append({n + 3, 0, {"Port ", p, " - ", "Start" }}); - device.input.append({n + 4, 0, {"Port ", p, " - ", "Up" }}); - device.input.append({n + 5, 0, {"Port ", p, " - ", "Down" }}); - device.input.append({n + 6, 0, {"Port ", p, " - ", "Left" }}); - device.input.append({n + 7, 0, {"Port ", p, " - ", "Right" }}); - device.input.append({n + 8, 0, {"Port ", p, " - ", "A" }}); - device.input.append({n + 9, 0, {"Port ", p, " - ", "X" }}); - device.input.append({n + 10, 0, {"Port ", p, " - ", "L" }}); - device.input.append({n + 11, 0, {"Port ", p, " - ", "R" }}); - 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; -} - -Emulator::Interface::Port::Device Interface::mouse() { - Port::Device device{3, "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; -} - -Emulator::Interface::Port::Device Interface::superScope() { - Port::Device device{4, "Super Scope"}; - 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, "Cursor" }); - device.input.append({4, 0, "Turbo" }); - device.input.append({5, 0, "Pause" }); - device.order = {0, 1, 2, 3, 4, 5}; - return device; -} - -Emulator::Interface::Port::Device Interface::justifier() { - Port::Device device{5, "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; -} - -Emulator::Interface::Port::Device Interface::justifiers() { - Port::Device device{6, "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"}); - device.input.append({3, 0, "Port 1 - Start" }); - device.order.append(0, 1, 2, 3); - device.input.append({4, 1, "Port 2 - X-axis" }); - device.input.append({5, 1, "Port 2 - Y-axis" }); - 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; -} - -Emulator::Interface::Port::Device Interface::usart() { - Port::Device device{7, "Serial USART"}; - return device; -} - } diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index ec0b1bc7..3daeb99f 100755 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -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; diff --git a/bsnes/sfc/system/serialization.cpp b/bsnes/sfc/system/serialization.cpp index 22517121..d2625999 100755 --- a/bsnes/sfc/system/serialization.cpp +++ b/bsnes/sfc/system/serialization.cpp @@ -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); diff --git a/bsnes/sfc/system/system.cpp b/bsnes/sfc/system/system.cpp index f4bc1d4b..2c9f814a 100755 --- a/bsnes/sfc/system/system.cpp +++ b/bsnes/sfc/system/system.cpp @@ -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(); diff --git a/bsnes/target-ethos/Makefile b/bsnes/target-ethos/Makefile index baf0a0a8..cc88b7cd 100755 --- a/bsnes/target-ethos/Makefile +++ b/bsnes/target-ethos/Makefile @@ -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)) diff --git a/bsnes/target-ethos/bootstrap.cpp b/bsnes/target-ethos/bootstrap.cpp index a48b5925..143bef4e 100755 --- a/bsnes/target-ethos/bootstrap.cpp +++ b/bsnes/target-ethos/bootstrap.cpp @@ -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); } } diff --git a/bsnes/target-ethos/ethos.cpp b/bsnes/target-ethos/ethos.cpp index 45914787..6e27f8a7 100755 --- a/bsnes/target-ethos/ethos.cpp +++ b/bsnes/target-ethos/ethos.cpp @@ -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(); diff --git a/bsnes/target-ethos/ethos.hpp b/bsnes/target-ethos/ethos.hpp index 6a595460..3ad98172 100755 --- a/bsnes/target-ethos/ethos.hpp +++ b/bsnes/target-ethos/ethos.hpp @@ -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(); diff --git a/bsnes/target-ethos/general/browser.cpp b/bsnes/target-ethos/general/browser.cpp index e3598429..461dea0b 100755 --- a/bsnes/target-ethos/general/browser.cpp +++ b/bsnes/target-ethos/general/browser.cpp @@ -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) { diff --git a/bsnes/target-ethos/general/presentation.cpp b/bsnes/target-ethos/general/presentation.cpp index e1b2272c..8dafa03c 100755 --- a/bsnes/target-ethos/general/presentation.cpp +++ b/bsnes/target-ethos/general/presentation.cpp @@ -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); diff --git a/bsnes/target-ethos/general/presentation.hpp b/bsnes/target-ethos/general/presentation.hpp index a187071a..f9b15023 100755 --- a/bsnes/target-ethos/general/presentation.hpp +++ b/bsnes/target-ethos/general/presentation.hpp @@ -21,8 +21,8 @@ struct Presentation : Window { vector emulatorList; Menu loadMenu; - vector loadListDirect; - vector loadListSlotted; + vector loadListSystem; + vector 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); diff --git a/bsnes/target-ethos/interface/interface.cpp b/bsnes/target-ethos/interface/interface.cpp index 10897174..afad547a 100755 --- a/bsnes/target-ethos/interface/interface.cpp +++ b/bsnes/target-ethos/interface/interface.cpp @@ -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); } diff --git a/bsnes/target-ethos/settings/hotkey.cpp b/bsnes/target-ethos/settings/hotkey.cpp index 89d9b1f3..b93fd2c6 100755 --- a/bsnes/target-ethos/settings/hotkey.cpp +++ b/bsnes/target-ethos/settings/hotkey.cpp @@ -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); } diff --git a/bsnes/target-ethos/settings/hotkey.hpp b/bsnes/target-ethos/settings/hotkey.hpp index 9e194ce8..eb44508f 100755 --- a/bsnes/target-ethos/settings/hotkey.hpp +++ b/bsnes/target-ethos/settings/hotkey.hpp @@ -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(); diff --git a/bsnes/target-ethos/settings/input.cpp b/bsnes/target-ethos/settings/input.cpp index 75687f7b..4b3f8ba9 100755 --- a/bsnes/target-ethos/settings/input.cpp +++ b/bsnes/target-ethos/settings/input.cpp @@ -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]; diff --git a/bsnes/target-ethos/settings/input.hpp b/bsnes/target-ethos/settings/input.hpp index e5fb593d..623ea0d1 100755 --- a/bsnes/target-ethos/settings/input.hpp +++ b/bsnes/target-ethos/settings/input.hpp @@ -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); diff --git a/bsnes/target-ethos/tools/cheat-database.cpp b/bsnes/target-ethos/tools/cheat-database.cpp new file mode 100755 index 00000000..541b62ab --- /dev/null +++ b/bsnes/target-ethos/tools/cheat-database.cpp @@ -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(); +} diff --git a/bsnes/target-ethos/tools/cheat-database.hpp b/bsnes/target-ethos/tools/cheat-database.hpp new file mode 100755 index 00000000..cf7e1d43 --- /dev/null +++ b/bsnes/target-ethos/tools/cheat-database.hpp @@ -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; +}; + +extern CheatDatabase *cheatDatabase; diff --git a/bsnes/target-ethos/tools/cheat-editor.cpp b/bsnes/target-ethos/tools/cheat-editor.cpp new file mode 100755 index 00000000..d673f86f --- /dev/null +++ b/bsnes/target-ethos/tools/cheat-editor.cpp @@ -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("\n"); + fp.print("\n"); + for(unsigned n = 0; n <= lastSave; n++) { + fp.print(" \n"); + fp.print(" \n"); + fp.print(" \n"); + fp.print(" \n"); + } + fp.print("\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; +} diff --git a/bsnes/target-ethos/tools/cheat-editor.hpp b/bsnes/target-ethos/tools/cheat-editor.hpp new file mode 100755 index 00000000..0c757409 --- /dev/null +++ b/bsnes/target-ethos/tools/cheat-editor.hpp @@ -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; diff --git a/bsnes/target-ethos/tools/state-manager.cpp b/bsnes/target-ethos/tools/state-manager.cpp new file mode 100755 index 00000000..eee5c024 --- /dev/null +++ b/bsnes/target-ethos/tools/state-manager.cpp @@ -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(); +} diff --git a/bsnes/target-ethos/tools/state-manager.hpp b/bsnes/target-ethos/tools/state-manager.hpp new file mode 100755 index 00000000..31aa71b4 --- /dev/null +++ b/bsnes/target-ethos/tools/state-manager.hpp @@ -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; diff --git a/bsnes/target-ethos/tools/tools.cpp b/bsnes/target-ethos/tools/tools.cpp new file mode 100755 index 00000000..0c6ddb9b --- /dev/null +++ b/bsnes/target-ethos/tools/tools.cpp @@ -0,0 +1,4 @@ +#include "../ethos.hpp" +#include "cheat-database.cpp" +#include "cheat-editor.cpp" +#include "state-manager.cpp" diff --git a/bsnes/target-ethos/tools/tools.hpp b/bsnes/target-ethos/tools/tools.hpp new file mode 100755 index 00000000..d6df0ea7 --- /dev/null +++ b/bsnes/target-ethos/tools/tools.hpp @@ -0,0 +1,3 @@ +#include "cheat-database.hpp" +#include "cheat-editor.hpp" +#include "state-manager.hpp" diff --git a/bsnes/target-ethos/utility/utility.cpp b/bsnes/target-ethos/utility/utility.cpp index 403a8cc1..77e9c9bb 100755 --- a/bsnes/target-ethos/utility/utility.cpp +++ b/bsnes/target-ethos/utility/utility.cpp @@ -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; diff --git a/bsnes/target-ethos/utility/utility.hpp b/bsnes/target-ethos/utility/utility.hpp index e3c5491c..2bfeb98a 100755 --- a/bsnes/target-ethos/utility/utility.hpp +++ b/bsnes/target-ethos/utility/utility.hpp @@ -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;