From 3cb04b101b92b6c0127aad24632e0fc316f2a072 Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Sun, 6 May 2012 16:34:46 +1000 Subject: [PATCH] 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. --- bsnes/emulator/emulator.hpp | 2 +- bsnes/emulator/interface.hpp | 51 ++- bsnes/fc/interface/interface.cpp | 61 ++-- bsnes/fc/interface/interface.hpp | 11 +- bsnes/gb/interface/interface.cpp | 51 +-- bsnes/gb/interface/interface.hpp | 7 + bsnes/gba/interface/interface.cpp | 36 +-- bsnes/gba/interface/interface.hpp | 4 + bsnes/sfc/cartridge/cartridge.cpp | 23 +- bsnes/sfc/cartridge/cartridge.hpp | 2 +- bsnes/sfc/cartridge/markup.cpp | 83 +---- bsnes/sfc/cheat/cheat.hpp | 2 +- bsnes/sfc/chip/icd2/interface/interface.cpp | 24 +- bsnes/sfc/chip/necdsp/necdsp.cpp | 2 +- bsnes/sfc/chip/sufamiturbo/sufamiturbo.cpp | 4 +- bsnes/sfc/interface/interface.cpp | 328 ++++++++++++-------- bsnes/sfc/interface/interface.hpp | 28 +- bsnes/sfc/system/serialization.cpp | 2 +- bsnes/sfc/system/system.cpp | 4 +- bsnes/target-ethos/Makefile | 4 +- bsnes/target-ethos/bootstrap.cpp | 2 +- bsnes/target-ethos/ethos.cpp | 5 +- bsnes/target-ethos/ethos.hpp | 1 + bsnes/target-ethos/general/browser.cpp | 2 +- bsnes/target-ethos/general/presentation.cpp | 24 +- bsnes/target-ethos/general/presentation.hpp | 6 +- bsnes/target-ethos/interface/interface.cpp | 8 +- bsnes/target-ethos/settings/hotkey.cpp | 10 +- bsnes/target-ethos/settings/hotkey.hpp | 4 +- bsnes/target-ethos/settings/input.cpp | 27 +- bsnes/target-ethos/settings/input.hpp | 8 +- bsnes/target-ethos/tools/cheat-database.cpp | 78 +++++ bsnes/target-ethos/tools/cheat-database.hpp | 22 ++ bsnes/target-ethos/tools/cheat-editor.cpp | 187 +++++++++++ bsnes/target-ethos/tools/cheat-editor.hpp | 39 +++ bsnes/target-ethos/tools/state-manager.cpp | 155 +++++++++ bsnes/target-ethos/tools/state-manager.hpp | 37 +++ bsnes/target-ethos/tools/tools.cpp | 4 + bsnes/target-ethos/tools/tools.hpp | 3 + bsnes/target-ethos/utility/utility.cpp | 98 ++++-- bsnes/target-ethos/utility/utility.hpp | 12 +- 41 files changed, 1067 insertions(+), 394 deletions(-) create mode 100755 bsnes/target-ethos/tools/cheat-database.cpp create mode 100755 bsnes/target-ethos/tools/cheat-database.hpp create mode 100755 bsnes/target-ethos/tools/cheat-editor.cpp create mode 100755 bsnes/target-ethos/tools/cheat-editor.hpp create mode 100755 bsnes/target-ethos/tools/state-manager.cpp create mode 100755 bsnes/target-ethos/tools/state-manager.hpp create mode 100755 bsnes/target-ethos/tools/tools.cpp create mode 100755 bsnes/target-ethos/tools/tools.hpp 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;