From 2eb50fd70bf9d1185e6cbe26e5b9fff22ea7189d Mon Sep 17 00:00:00 2001 From: Tim Allen Date: Tue, 21 Apr 2015 21:51:57 +1000 Subject: [PATCH] Update to v094r15 release. byuu says: Implemented the cheat database dialog, and most of the cheat editor dialog. I still have to handle loading and saving the cheats.bml file for each game. I wanted to finish it today, but I burned out. It's a ton of really annoying work to support cheat codes. There's also some issue with the width calculation for the "code(s)" column in hiro/GTK. Short-term: - add input port changing support - add other input types (mouse-based, etc) - finish cheat codes Long-term: - add slotted cart loader (SGB, BSX, ST) - add DIP switch selection window (NSS) - add overscan masking - add timing configuration (video/audio sync) Not planned: - video color adjustments (will allow emulated color vs raw color; but no more sliders) - pixel shaders - ananke integration (will need to make a command-line version to get my games in) - fancy audio adjustment controls (resampler, latency, volume) - input focus settings - localization support (not enough users) - window geometry memory - anything else not in higan v094 --- emulator/emulator.hpp | 2 +- hiro/gtk/widget/list-view.cpp | 7 +- nall/string/base.hpp | 1 + nall/string/platform.hpp | 18 +++ target-tomoko/GNUmakefile | 9 +- target-tomoko/configuration/configuration.cpp | 4 + target-tomoko/configuration/configuration.hpp | 4 + target-tomoko/input/hotkeys.cpp | 43 ++++++- target-tomoko/library/browser.cpp | 4 +- target-tomoko/library/manager.cpp | 2 +- target-tomoko/presentation/presentation.cpp | 25 ++-- target-tomoko/presentation/presentation.hpp | 4 +- target-tomoko/program/interface.cpp | 10 +- target-tomoko/program/media.cpp | 17 +-- target-tomoko/program/program.cpp | 15 +-- target-tomoko/program/program.hpp | 11 +- target-tomoko/program/state.cpp | 28 +++-- target-tomoko/program/utility.cpp | 21 +++- target-tomoko/settings/advanced.cpp | 10 ++ target-tomoko/settings/input.cpp | 2 +- target-tomoko/settings/settings.cpp | 13 +- target-tomoko/settings/settings.hpp | 14 ++- target-tomoko/tomoko.cpp | 1 + target-tomoko/tomoko.hpp | 2 + target-tomoko/tools/cheat-database.cpp | 52 ++++++++ target-tomoko/tools/cheat-editor.cpp | 111 ++++++++++++++++++ target-tomoko/tools/state-manager.cpp | 98 ++++++++++++++++ target-tomoko/tools/tools.cpp | 28 +++++ target-tomoko/tools/tools.hpp | 86 ++++++++++++++ 29 files changed, 568 insertions(+), 74 deletions(-) create mode 100644 target-tomoko/tools/cheat-database.cpp create mode 100644 target-tomoko/tools/cheat-editor.cpp create mode 100644 target-tomoko/tools/state-manager.cpp create mode 100644 target-tomoko/tools/tools.cpp create mode 100644 target-tomoko/tools/tools.hpp diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 017c603c..7111bdfe 100644 --- a/emulator/emulator.hpp +++ b/emulator/emulator.hpp @@ -3,7 +3,7 @@ namespace Emulator { static const char Name[] = "higan"; - static const char Version[] = "094.14"; + static const char Version[] = "094.15"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/hiro/gtk/widget/list-view.cpp b/hiro/gtk/widget/list-view.cpp index 79b75943..b0e9b8c8 100644 --- a/hiro/gtk/widget/list-view.cpp +++ b/hiro/gtk/widget/list-view.cpp @@ -49,7 +49,12 @@ auto pListView::destruct() -> void { auto pListView::append(sListViewColumn column) -> void { gtk_tree_view_append_column(gtkTreeView, column->self()->gtkColumn); gtk_widget_show_all(column->self()->gtkHeader); + column->setBackgroundColor(column->backgroundColor()); + column->setEditable(column->editable()); column->setFont(column->font()); + column->setForegroundColor(column->foregroundColor()); + column->setHorizontalAlignment(column->horizontalAlignment()); + column->setVerticalAlignment(column->verticalAlignment()); setCheckable(state().checkable); _createModel(); gtk_tree_view_set_rules_hint(gtkTreeView, self().columns() >= 2); //two or more columns + checkbutton column @@ -118,7 +123,7 @@ auto pListView::resizeColumns() -> void { } for(auto row : range(self().items())) { maximumWidth = max(maximumWidth, 8 //margin - + (row == 0 && state().checkable ? 24 : 0) //check box + + (row == 0 && state().checkable ? 32 : 0) //check box + state().items[row]->state.icon(column, {}).width + Font::size(state().columns[column]->font(true), state().items[row]->state.text(column, "")).width() ); diff --git a/nall/string/base.hpp b/nall/string/base.hpp index 178a77dd..5bac59f3 100644 --- a/nall/string/base.hpp +++ b/nall/string/base.hpp @@ -93,6 +93,7 @@ inline auto realpath(rstring name) -> string; inline auto programpath() -> string; inline auto userpath() -> string; inline auto configpath() -> string; +inline auto localpath() -> string; inline auto sharedpath() -> string; inline auto temppath() -> string; diff --git a/nall/string/platform.hpp b/nall/string/platform.hpp index 476ea090..c438bbfd 100644 --- a/nall/string/platform.hpp +++ b/nall/string/platform.hpp @@ -71,6 +71,24 @@ auto configpath() -> string { return result; } +// /home/username/.local/ +// c:/users/username/appdata/local/ +auto localpath() -> string { + #if defined(PLATFORM_WINDOWS) + whcar_t path[PATH_MAX] = L""; + SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, nullptr, 0, path); + string result = (const char*)utf8_t(path); + result.transform("\\", "/"); + #elif defined(PLATFORM_MACOSX) + string result = {userpath(), "Library/Application Support/"}; + #else + string result = {userpath(), ".local/"}; + #endif + if(result.empty()) result = "."; + if(result.endsWith("/") == false) result.append("/"); + return result; +} + // /usr/share // /Library/Application Support/ // c:/ProgramData/ diff --git a/target-tomoko/GNUmakefile b/target-tomoko/GNUmakefile index 1fdb523a..68e0122d 100644 --- a/target-tomoko/GNUmakefile +++ b/target-tomoko/GNUmakefile @@ -9,7 +9,7 @@ include gb/GNUmakefile include gba/GNUmakefile ui_objects := ui-tomoko ui-program ui-configuration ui-input -ui_objects += ui-library ui-settings ui-presentation +ui_objects += ui-library ui-settings ui-tools ui-presentation ui_objects += ruby hiro # platform @@ -55,6 +55,7 @@ obj/ui-configuration.o: $(ui)/configuration/configuration.cpp $(call rwildcard,$ obj/ui-input.o: $(ui)/input/input.cpp $(call rwildcard,$(ui)/) obj/ui-library.o: $(ui)/library/library.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/ui-presentation.o: $(ui)/presentation/presentation.cpp $(call rwildcard,$(ui)/) # targets @@ -67,9 +68,13 @@ ifeq ($(shell id -un),root) else ifeq ($(platform),windows) else ifeq ($(platform),macosx) else + mkdir -p $(prefix)/bin/ + mkdir -p $(prefix)/share/icons/ + mkdir -p $(prefix)/$(name)/ + mkdir -p ~/Emulation/System/ cp out/$(name) $(prefix)/bin/$(name) cp data/higan.png $(prefix)/share/icons/$(name).png - mkdir -p ~/Emulation/System/ + cp data/cheats.bml $(prefix)/$(name)/cheats.bml cp -R profile/* ~/Emulation/System/ endif diff --git a/target-tomoko/configuration/configuration.cpp b/target-tomoko/configuration/configuration.cpp index 06455c61..b5487c49 100644 --- a/target-tomoko/configuration/configuration.cpp +++ b/target-tomoko/configuration/configuration.cpp @@ -8,6 +8,9 @@ ConfigurationManager::ConfigurationManager() { userInterface.append(userInterface.showStatusBar, "ShowStatusBar"); append(userInterface, "UserInterface"); + library.append(library.location, "Location"); + append(library, "Library"); + video.append(video.driver, "Driver"); video.append(video.synchronize, "Synchronize"); video.append(video.scale, "Scale"); @@ -25,6 +28,7 @@ ConfigurationManager::ConfigurationManager() { append(input, "Input"); load({configpath(), "tomoko/settings.bml"}); + if(!library.location) library.location = {userpath(), "Emulation/"}; if(!video.driver) video.driver = ruby::video.safestDriver(); if(!audio.driver) audio.driver = ruby::audio.safestDriver(); if(!input.driver) input.driver = ruby::input.safestDriver(); diff --git a/target-tomoko/configuration/configuration.hpp b/target-tomoko/configuration/configuration.hpp index 02efbba9..5971ef62 100644 --- a/target-tomoko/configuration/configuration.hpp +++ b/target-tomoko/configuration/configuration.hpp @@ -6,6 +6,10 @@ struct ConfigurationManager : Configuration::Document { bool showStatusBar = true; } userInterface; + struct Library : Configuration::Node { + string location; + } library; + struct Video : Configuration::Node { string driver; bool synchronize = false; diff --git a/target-tomoko/input/hotkeys.cpp b/target-tomoko/input/hotkeys.cpp index e00ddb5b..6b87104d 100644 --- a/target-tomoko/input/hotkeys.cpp +++ b/target-tomoko/input/hotkeys.cpp @@ -1,6 +1,5 @@ auto InputManager::appendHotkeys() -> void { - { - auto hotkey = new InputHotkey; + { auto hotkey = new InputHotkey; hotkey->name = "Toggle Fullscreen"; hotkey->action = [] { presentation->toggleFullScreen(); @@ -8,6 +7,46 @@ auto InputManager::appendHotkeys() -> void { hotkeys.append(hotkey); } + { auto hotkey = new InputHotkey; + hotkey->name = "Save State"; + hotkey->action = [] { + program->saveState(0); + }; + hotkeys.append(hotkey); + } + + { auto hotkey = new InputHotkey; + hotkey->name = "Load State"; + hotkey->action = [] { + program->loadState(0); + }; + hotkeys.append(hotkey); + } + + { auto hotkey = new InputHotkey; + hotkey->name = "Pause Emulation"; + hotkey->action = [] { + program->pause = !program->pause; + }; + hotkeys.append(hotkey); + } + + { auto hotkey = new InputHotkey; + hotkey->name = "Power Cycle"; + hotkey->action = [] { + program->powerCycle(); + }; + hotkeys.append(hotkey); + } + + { auto hotkey = new InputHotkey; + hotkey->name = "Soft Reset"; + hotkey->action = [] { + program->softReset(); + }; + hotkeys.append(hotkey); + } + Configuration::Node nodeHotkeys; for(auto& hotkey : hotkeys) { nodeHotkeys.append(hotkey->assignment, string{hotkey->name}.replace(" ", "")); diff --git a/target-tomoko/library/browser.cpp b/target-tomoko/library/browser.cpp index f994f5c7..964d30c8 100644 --- a/target-tomoko/library/browser.cpp +++ b/target-tomoko/library/browser.cpp @@ -4,12 +4,12 @@ LibraryBrowser::LibraryBrowser(TabFrame& parent, Emulator::Interface::Media& med layout.setMargin(5); gameList.onActivate([&] { libraryManager->setVisible(false); - program->loadMedia({userpath(), "Emulation/", this->media.name, "/", gameList.selected()->text(), ".", this->media.type, "/"}); + program->loadMedia({config().library.location, this->media.name, "/", gameList.selected()->text(), ".", this->media.type, "/"}); }); } auto LibraryBrowser::reload() -> void { - string path = {userpath(), "Emulation/", media.name}; + string path = {config().library.location, media.name}; directory::create(path); gameList.reset(); diff --git a/target-tomoko/library/manager.cpp b/target-tomoko/library/manager.cpp index 57a488b9..d611978e 100644 --- a/target-tomoko/library/manager.cpp +++ b/target-tomoko/library/manager.cpp @@ -13,7 +13,6 @@ LibraryManager::LibraryManager() { } } - setTitle("Library"); setSize({640, 800}); setPlacement(0.0, 0.0); } @@ -24,6 +23,7 @@ auto LibraryManager::show(const string& type) -> void { browser->select(); } + setTitle({"Library (", config().library.location, ")"}); setVisible(); setFocused(); } diff --git a/target-tomoko/presentation/presentation.cpp b/target-tomoko/presentation/presentation.cpp index 0aabcd96..063b4456 100644 --- a/target-tomoko/presentation/presentation.cpp +++ b/target-tomoko/presentation/presentation.cpp @@ -17,8 +17,8 @@ Presentation::Presentation() { } systemMenu.setText("System").setVisible(false); - powerSystem.setText("Power"); - resetSystem.setText("Reset"); + powerSystem.setText("Power").onActivate([&] { program->powerCycle(); }); + resetSystem.setText("Reset").onActivate([&] { program->softReset(); }); unloadSystem.setText("Unload").onActivate([&] { program->unloadMedia(); drawSplashScreen(); }); settingsMenu.setText("Settings"); @@ -72,10 +72,7 @@ Presentation::Presentation() { statusBar.setVisible(config().userInterface.showStatusBar); if(visible()) resizeViewport(); }); - showConfiguration.setText("Configuration ...").onActivate([&] { - settingsManager->setVisible(); - settingsManager->setFocused(); - }); + showConfiguration.setText("Configuration ...").onActivate([&] { settingsManager->show(0); }); toolsMenu.setText("Tools").setVisible(false); saveStateMenu.setText("Save State"); @@ -90,8 +87,8 @@ Presentation::Presentation() { loadSlot3.setText("Slot 3").onActivate([&] { program->loadState(3); }); loadSlot4.setText("Slot 4").onActivate([&] { program->loadState(4); }); loadSlot5.setText("Slot 5").onActivate([&] { program->loadState(5); }); - stateManager.setText("State Manager").onActivate([&] {}); - cheatEditor.setText("Cheat Editor").onActivate([&] {}); + cheatEditor.setText("Cheat Editor").onActivate([&] { toolsManager->show(0); }); + stateManager.setText("State Manager").onActivate([&] { toolsManager->show(1); }); statusBar.setFont(Font::sans(8, "Bold")); statusBar.setVisible(config().userInterface.showStatusBar); @@ -108,15 +105,13 @@ auto Presentation::resizeViewport() -> void { signed width = 256; signed height = 240; - if(program->activeEmulator) { - width = program->emulator().information.width; - height = program->emulator().information.height; + if(emulator) { + width = emulator->information.width; + height = emulator->information.height; } if(fullScreen() == false) { - bool arc = config().video.aspectCorrection - && program->activeEmulator - && program->emulator().information.aspectRatio != 1.0; + bool arc = config().video.aspectCorrection && emulator && emulator->information.aspectRatio != 1.0; if(config().video.scale == "Small" ) width *= 1, height *= 1; if(config().video.scale == "Normal") width *= 2, height *= 2; @@ -149,7 +144,7 @@ auto Presentation::resizeViewport() -> void { viewport.setGeometry({x, y, width, height}); } - if(!program->activeEmulator) drawSplashScreen(); + if(!emulator) drawSplashScreen(); } auto Presentation::toggleFullScreen() -> void { diff --git a/target-tomoko/presentation/presentation.hpp b/target-tomoko/presentation/presentation.hpp index b7125f79..443437d8 100644 --- a/target-tomoko/presentation/presentation.hpp +++ b/target-tomoko/presentation/presentation.hpp @@ -46,8 +46,8 @@ struct Presentation : Window { MenuItem loadSlot4{&loadStateMenu}; MenuItem loadSlot5{&loadStateMenu}; MenuSeparator toolsMenuSeparator{&toolsMenu}; - MenuItem stateManager{&toolsMenu}; - MenuItem cheatEditor{&toolsMenu}; + MenuItem cheatEditor{&toolsMenu}; + MenuItem stateManager{&toolsMenu}; FixedLayout layout{this}; Viewport viewport{&layout, Geometry{0, 0, 1, 1}}; diff --git a/target-tomoko/program/interface.cpp b/target-tomoko/program/interface.cpp index 17c14a33..19e6da63 100644 --- a/target-tomoko/program/interface.cpp +++ b/target-tomoko/program/interface.cpp @@ -4,17 +4,17 @@ auto Program::loadRequest(unsigned id, string name, string type) -> void { //request from emulation core to load non-volatile media file auto Program::loadRequest(unsigned id, string path) -> void { - string location = {mediaPaths(emulator().group(id)), path}; + string location = {mediaPaths(emulator->group(id)), path}; if(!file::exists(location)) return; mmapstream stream{location}; - return emulator().load(id, stream); + return emulator->load(id, stream); } //request from emulation core to save non-volatile media file auto Program::saveRequest(unsigned id, string path) -> void { - string location = {mediaPaths(emulator().group(id)), path}; + string location = {mediaPaths(emulator->group(id)), path}; filestream stream{location, file::mode::write}; - return emulator().save(id, stream); + return emulator->save(id, stream); } auto Program::videoColor(unsigned source, uint16 alpha, uint16 red, uint16 green, uint16 blue) -> uint32 { @@ -67,7 +67,7 @@ auto Program::audioSample(int16 lsample, int16 rsample) -> void { auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16 { if(presentation->focused()) { - auto guid = emulator().port[port].device[device].input[input].guid; + auto guid = emulator->port[port].device[device].input[input].guid; auto mapping = (InputMapping*)guid; if(mapping) return mapping->poll(); } diff --git a/target-tomoko/program/media.cpp b/target-tomoko/program/media.cpp index fd05570d..b5b48d84 100644 --- a/target-tomoko/program/media.cpp +++ b/target-tomoko/program/media.cpp @@ -20,22 +20,25 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med mediaPaths(media.id) = location; folderPaths.append(location); - setEmulator(&_emulator); + emulator = &_emulator; updateVideoPalette(); - emulator().load(media.id); - emulator().power(); + emulator->load(media.id); + emulator->power(); presentation->resizeViewport(); - presentation->setTitle(emulator().title()); + presentation->setTitle(emulator->title()); presentation->systemMenu.setVisible(true); presentation->toolsMenu.setVisible(true); + toolsManager->cheatEditor.doRefresh(); + toolsManager->stateManager.doRefresh(); } auto Program::unloadMedia() -> void { - if(activeEmulator == nullptr) return; - emulator().unload(); + if(!emulator) return; + + emulator->unload(); + emulator = nullptr; - setEmulator(nullptr); mediaPaths.reset(); folderPaths.reset(); diff --git a/target-tomoko/program/program.cpp b/target-tomoko/program/program.cpp index 84f5aee2..33e91de0 100644 --- a/target-tomoko/program/program.cpp +++ b/target-tomoko/program/program.cpp @@ -24,6 +24,8 @@ Program::Program() { new InputManager; new LibraryManager; new SettingsManager; + new CheatDatabase; + new ToolsManager; new Presentation; presentation->setVisible(); @@ -56,22 +58,17 @@ Program::Program() { updateVideoFilter(); } -auto Program::emulator() -> Emulator::Interface& { - if(!activeEmulator) throw; - return *activeEmulator; -} - auto Program::main() -> void { updateStatusText(); inputManager->poll(); - if(!activeEmulator || emulator().loaded() == false) { + if(!emulator || !emulator->loaded() || pause) { audio.clear(); usleep(20 * 1000); return; } - emulator().run(); + emulator->run(); } auto Program::quit() -> void { @@ -83,7 +80,3 @@ auto Program::quit() -> void { input.term(); Application::quit(); } - -auto Program::setEmulator(Emulator::Interface* emulator) -> void { - activeEmulator = emulator; -} diff --git a/target-tomoko/program/program.hpp b/target-tomoko/program/program.hpp index b9082305..bd53ecf1 100644 --- a/target-tomoko/program/program.hpp +++ b/target-tomoko/program/program.hpp @@ -1,10 +1,8 @@ struct Program : Emulator::Interface::Bind { //program.cpp Program(); - auto emulator() -> Emulator::Interface&; auto main() -> void; auto quit() -> void; - auto setEmulator(Emulator::Interface*) -> void; //interface.cpp auto loadRequest(unsigned id, string name, string type) -> void override; @@ -25,19 +23,22 @@ struct Program : Emulator::Interface::Bind { auto unloadMedia() -> void; //state.cpp - auto loadState(unsigned slot) -> bool; - auto saveState(unsigned slot) -> bool; + auto stateName(unsigned slot, bool manager = false) -> string; + auto loadState(unsigned slot, bool manager = false) -> bool; + auto saveState(unsigned slot, bool manager = false) -> bool; //utility.cpp + auto powerCycle() -> void; + auto softReset() -> void; auto showMessage(const string& text) -> void; auto updateStatusText() -> void; auto updateVideoFilter() -> void; auto updateVideoPalette() -> void; DSP dsp; + bool pause = false; vector emulators; - Emulator::Interface* activeEmulator = nullptr; vector mediaPaths; vector folderPaths; diff --git a/target-tomoko/program/state.cpp b/target-tomoko/program/state.cpp index 10fe9bf5..341c2364 100644 --- a/target-tomoko/program/state.cpp +++ b/target-tomoko/program/state.cpp @@ -1,18 +1,28 @@ -auto Program::loadState(unsigned slot) -> bool { - if(!activeEmulator) return false; - auto memory = file::read({folderPaths[0], "higan/state-", slot, ".bst"}); +auto Program::stateName(unsigned slot, bool manager) -> string { + return { + folderPaths[0], "higan/states/", + manager ? "managed/" : "quick/", + "slot-", decimal<2>(slot), ".bst" + }; +} + +auto Program::loadState(unsigned slot, bool manager) -> bool { + if(!emulator) return false; + auto location = stateName(slot, manager); + auto memory = file::read(location); if(memory.size() == 0) return showMessage({"Slot ", slot, " does not exist"}), false; serializer s(memory.data(), memory.size()); - if(emulator().unserialize(s) == false) return showMessage({"Slot ", slot, " state incompatible"}), false; + if(emulator->unserialize(s) == false) return showMessage({"Slot ", slot, " state incompatible"}), false; return showMessage({"Loaded from slot ", slot}), true; } -auto Program::saveState(unsigned slot) -> bool { - if(!activeEmulator) return false; - serializer s = emulator().serialize(); +auto Program::saveState(unsigned slot, bool manager) -> bool { + if(!emulator) return false; + auto location = stateName(slot, manager); + serializer s = emulator->serialize(); if(s.size() == 0) return showMessage({"Failed to save state to slot ", slot}), false; - directory::create({folderPaths[0], "higan/"}); - if(file::write({folderPaths[0], "higan/state-", slot, ".bst"}, s.data(), s.size()) == false) { + directory::create(location.pathname()); + if(file::write(location, s.data(), s.size()) == false) { return showMessage({"Unable to write to slot ", slot}), false; } return showMessage({"Saved to slot ", slot}), true; diff --git a/target-tomoko/program/utility.cpp b/target-tomoko/program/utility.cpp index 9962d105..d21a6d21 100644 --- a/target-tomoko/program/utility.cpp +++ b/target-tomoko/program/utility.cpp @@ -1,3 +1,16 @@ +auto Program::powerCycle() -> void { + if(!emulator) return; + emulator->power(); + showMessage("Power cycled"); +} + +auto Program::softReset() -> void { + if(!emulator) return; + if(!emulator->information.resettable) return powerCycle(); + emulator->reset(); + showMessage("System reset"); +} + auto Program::showMessage(const string& text) -> void { statusTime = time(0); statusMessage = text; @@ -9,9 +22,9 @@ auto Program::updateStatusText() -> void { string text; if((currentTime - statusTime) <= 2) { text = statusMessage; - } else if(!activeEmulator || emulator().loaded() == false) { + } else if(!emulator || emulator->loaded() == false) { text = "No cartridge loaded"; - } else if(0) { + } else if(pause) { text = "Paused"; } else { text = statusText; @@ -28,8 +41,8 @@ auto Program::updateVideoFilter() -> void { } auto Program::updateVideoPalette() -> void { - if(!activeEmulator) return; - emulator().paletteUpdate(config().video.colorEmulation + if(!emulator) return; + emulator->paletteUpdate(config().video.colorEmulation ? Emulator::Interface::PaletteMode::Emulation : Emulator::Interface::PaletteMode::Standard ); diff --git a/target-tomoko/settings/advanced.cpp b/target-tomoko/settings/advanced.cpp index f0565164..d2837aab 100644 --- a/target-tomoko/settings/advanced.cpp +++ b/target-tomoko/settings/advanced.cpp @@ -3,6 +3,7 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { setText("Advanced"); layout.setMargin(5); + driverLabel.setText("Driver Selection").setFont(Font::sans(8, "Bold")); videoLabel.setText("Video:"); videoDriver.onChange([&] { config().video.driver = videoDriver.selected()->text(); }); @@ -28,4 +29,13 @@ AdvancedSettings::AdvancedSettings(TabFrame* parent) : TabFrameItem(parent) { inputDriver.append(item); if(config().input.driver == driver) item.setSelected(); } + + libraryLabel.setText("Game Library").setFont(Font::sans(8, "Bold")); + libraryPrefix.setText("Location:"); + libraryLocation.setEditable(false).setText(config().library.location); + libraryChange.setText("Change ...").onActivate([&] { + if(auto location = BrowserDialog().setParent(*presentation).selectFolder()) { + libraryLocation.setText(config().library.location = location); + } + }); } diff --git a/target-tomoko/settings/input.cpp b/target-tomoko/settings/input.cpp index 070142bd..6abe57f8 100644 --- a/target-tomoko/settings/input.cpp +++ b/target-tomoko/settings/input.cpp @@ -63,7 +63,7 @@ auto InputSettings::reloadMappings() -> void { mappingList.reset(); mappingList.append(ListViewColumn().setText("Name")); mappingList.append(ListViewColumn().setText("Mapping").setWidth(~0)); - mappingList.append(ListViewColumn().setText("Device")); + mappingList.append(ListViewColumn().setText("Device").setForegroundColor({0, 128, 0})); for(auto& mapping : activeDevice().mappings) { mappingList.append(ListViewItem().setText(0, mapping->name)); } diff --git a/target-tomoko/settings/settings.cpp b/target-tomoko/settings/settings.cpp index 5b9c01ad..a294cf47 100644 --- a/target-tomoko/settings/settings.cpp +++ b/target-tomoko/settings/settings.cpp @@ -14,6 +14,15 @@ SettingsManager::SettingsManager() { setSize({600, 400}); setPlacement(0.0, 1.0); - input.mappingList.resizeColumns(); - hotkeys.mappingList.resizeColumns(); + onSize([&] { + input.mappingList.resizeColumns(); + hotkeys.mappingList.resizeColumns(); + }); +} + +auto SettingsManager::show(unsigned setting) -> void { + panel.item(setting)->setSelected(); + setVisible(); + setFocused(); + doSize(); } diff --git a/target-tomoko/settings/settings.hpp b/target-tomoko/settings/settings.hpp index 945dc60e..e1a88db7 100644 --- a/target-tomoko/settings/settings.hpp +++ b/target-tomoko/settings/settings.hpp @@ -52,16 +52,22 @@ struct AdvancedSettings : TabFrameItem { ComboButton audioDriver{&driverLayout, Size{~0, 0}}; Label inputLabel{&driverLayout, Size{0, 0}}; ComboButton inputDriver{&driverLayout, Size{~0, 0}}; + Label libraryLabel{&layout, Size{~0, 0}, 2}; + HorizontalLayout libraryLayout{&layout, Size{~0, 0}}; + Label libraryPrefix{&libraryLayout, Size{0, 0}}; + LineEdit libraryLocation{&libraryLayout, Size{~0, 0}}; + Button libraryChange{&libraryLayout, Size{0, 0}}; }; struct SettingsManager : Window { SettingsManager(); + auto show(unsigned setting) -> void; VerticalLayout layout{this}; - TabFrame panelLayout{&layout, Size{~0, ~0}}; - InputSettings input{&panelLayout}; - HotkeySettings hotkeys{&panelLayout}; - AdvancedSettings advanced{&panelLayout}; + TabFrame panel{&layout, Size{~0, ~0}}; + InputSettings input{&panel}; + HotkeySettings hotkeys{&panel}; + AdvancedSettings advanced{&panel}; StatusBar statusBar{this}; }; diff --git a/target-tomoko/tomoko.cpp b/target-tomoko/tomoko.cpp index 4e822626..254ba680 100644 --- a/target-tomoko/tomoko.cpp +++ b/target-tomoko/tomoko.cpp @@ -1,4 +1,5 @@ #include "tomoko.hpp" +Emulator::Interface* emulator = nullptr; #include auto nall::main(lstring args) -> void { diff --git a/target-tomoko/tomoko.hpp b/target-tomoko/tomoko.hpp index 4d1afd04..4fe61724 100644 --- a/target-tomoko/tomoko.hpp +++ b/target-tomoko/tomoko.hpp @@ -1,4 +1,5 @@ #include +extern Emulator::Interface* emulator; #include #include @@ -12,4 +13,5 @@ using namespace hiro; #include "input/input.hpp" #include "library/library.hpp" #include "settings/settings.hpp" +#include "tools/tools.hpp" #include "presentation/presentation.hpp" diff --git a/target-tomoko/tools/cheat-database.cpp b/target-tomoko/tools/cheat-database.cpp new file mode 100644 index 00000000..398cb29a --- /dev/null +++ b/target-tomoko/tools/cheat-database.cpp @@ -0,0 +1,52 @@ +CheatDatabase::CheatDatabase() { + cheatDatabase = this; + + layout.setMargin(5); + cheatList.setCheckable(); + selectAllButton.setText("Select All").onActivate([&] { cheatList.setChecked(true); }); + unselectAllButton.setText("Unselect All").onActivate([&] { cheatList.setChecked(false); }); + addCodesButton.setText("Add Codes").onActivate([&] { addCodes(); }); + + setSize({800, 400}); + setPlacement(0.5, 1.0); +} + +auto CheatDatabase::findCodes() -> void { + if(!emulator) return; + auto sha256 = emulator->sha256(); + + auto contents = string::read({localpath(), "tomoko/cheats.bml"}); + auto document = Markup::Document(contents); + + for(auto& cartridge : document) { + if(cartridge.name != "cartridge") continue; + if(cartridge["sha256"].text() != sha256) continue; + + codes.reset(); + cheatList.reset(); + cheatList.append(ListViewColumn().setWidth(~0)); + for(auto& cheat : cartridge) { + if(cheat.name != "cheat") continue; + codes.append(cheat["code"].text()); + cheatList.append(ListViewItem().setText(0, cheat["description"].text())); + } + + setTitle(cartridge["name"].text()); + setVisible(); + return; + } + + MessageDialog().setParent(*toolsManager).setText("Sorry, no cheats were found for this game.").information(); +} + +auto CheatDatabase::addCodes() -> void { + for(auto item : cheatList.checked()) { + string code = codes(item->offset(), ""); + string description = item->text(0); + if(toolsManager->cheatEditor.addCode(code, description) == false) { + MessageDialog().setParent(*this).setText("Free slots exhausted. Not all codes could be added.").warning(); + break; + } + } + setVisible(false); +} diff --git a/target-tomoko/tools/cheat-editor.cpp b/target-tomoko/tools/cheat-editor.cpp new file mode 100644 index 00000000..c82be8cc --- /dev/null +++ b/target-tomoko/tools/cheat-editor.cpp @@ -0,0 +1,111 @@ +CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Edit::Replace); + setText("Cheat Editor"); + + layout.setMargin(5); + cheatList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0)); + cheatList.append(ListViewColumn().setText("Code(s)").setWidth(0)); + cheatList.append(ListViewColumn().setText("Description").setWidth(~0)); + for(auto slot : range(Slots)) cheatList.append(ListViewItem().setText(0, 1 + slot)); + cheatList.setCheckable(); + cheatList.setHeaderVisible(); + cheatList.onChange([&] { doChange(); }); + cheatList.onToggle([&](sListViewItem) { synchronizeCodes(); }); + codeLabel.setText("Code(s):"); + codeValue.onChange([&] { doModify(); }); + descriptionLabel.setText("Description:"); + descriptionValue.onChange([&] { doModify(); }); + findCodesButton.setText("Find Codes ...").onActivate([&] { cheatDatabase->findCodes(); }); + resetButton.setText("Reset").onActivate([&] { doReset(); }); + eraseButton.setText("Erase").onActivate([&] { doErase(); }); +} + +auto CheatEditor::doChange() -> void { + if(auto item = cheatList.selected()) { + unsigned slot = item->offset(); + codeValue.setEnabled(true).setText(cheats[slot].code); + descriptionValue.setEnabled(true).setText(cheats[slot].description); + eraseButton.setEnabled(true); + } else { + codeValue.setEnabled(false).setText(""); + descriptionValue.setEnabled(false).setText(""); + eraseButton.setEnabled(false); + } +} + +auto CheatEditor::doModify() -> void { + if(auto item = cheatList.selected()) { + unsigned slot = item->offset(); + cheats[slot].code = codeValue.text(); + cheats[slot].description = descriptionValue.text(); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::doRefresh() -> void { + for(auto slot : range(Slots)) { + if(cheats[slot].code || cheats[slot].description) { + lstring codes = cheats[slot].code.split("+"); + if(codes.size() > 1) codes[0].append("+..."); + cheatList.item(slot)->setText(1, codes[0]).setText(2, cheats[slot].description); + } else { + cheatList.item(slot)->setText(1, "").setText(2, "(empty)"); + } + } + + cheatList.resizeColumns(); +} + +auto CheatEditor::doReset() -> void { + if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) { + for(auto& cheat : cheats) { + cheat.code = ""; + cheat.description = ""; + } + cheatList.setSelected(false); + doChange(); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::doErase() -> void { + if(auto item = cheatList.selected()) { + unsigned slot = item->offset(); + cheats[slot].code = ""; + cheats[slot].description = ""; + codeValue.setText(""); + descriptionValue.setText(""); + doRefresh(); + synchronizeCodes(); + } +} + +auto CheatEditor::synchronizeCodes() -> void { + if(!emulator) return; + + lstring codes; + for(auto slot : range(Slots)) { + if(!cheatList.item(slot)->checked()) continue; + if(!cheats[slot].code) continue; + codes.append(cheats[slot].code); + } + + emulator->cheatSet(codes); +} + +//returns true if code was added +//returns false if there are no more free slots for additional codes +auto CheatEditor::addCode(const string& code, const string& description) -> bool { + for(auto& cheat : cheats) { + if(cheat.code || cheat.description) continue; + + cheat.code = code; + cheat.description = description; + doRefresh(); + return true; + } + + return false; +} diff --git a/target-tomoko/tools/state-manager.cpp b/target-tomoko/tools/state-manager.cpp new file mode 100644 index 00000000..0134e0b6 --- /dev/null +++ b/target-tomoko/tools/state-manager.cpp @@ -0,0 +1,98 @@ +StateManager::StateManager(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Application::FileManager); + setText("State Manager"); + + layout.setMargin(5); + stateList.append(ListViewColumn().setText("Slot").setForegroundColor({0, 128, 0}).setHorizontalAlignment(1.0)); + stateList.append(ListViewColumn().setText("Description").setWidth(~0)); + for(unsigned slot = 0; slot < Slots; slot++) { + stateList.append(ListViewItem().setText(0, 1 + slot)); + } + stateList.setHeaderVisible(); + stateList.onActivate([&] { doLoad(); }); + stateList.onChange([&] { doChange(); }); + descriptionLabel.setText("Description:"); + descriptionValue.onChange([&] { doLabel(); }); + saveButton.setText("Save").onActivate([&] { doSave(); }); + loadButton.setText("Load").onActivate([&] { doLoad(); }); + resetButton.setText("Reset").onActivate([&] { doReset(); }); + eraseButton.setText("Erase").onActivate([&] { doErase(); }); +} + +auto StateManager::doChange() -> void { + vector buffer; + if(auto item = stateList.selected()) { + buffer = file::read(program->stateName(1 + item->offset(), true)); + } + + if(buffer.size() >= 584) { + string description; + description.reserve(512); + memory::copy(description.pointer(), buffer.data() + 72, 512); + description.resize(description.length()); + descriptionValue.setEnabled(true).setText(description); + loadButton.setEnabled(true); + eraseButton.setEnabled(true); + } else { + descriptionValue.setEnabled(false).setText(""); + loadButton.setEnabled(false); + eraseButton.setEnabled(false); + } +} + +auto StateManager::doRefresh() -> void { + for(unsigned slot = 0; slot < Slots; slot++) { + auto buffer = file::read(program->stateName(1 + slot, true)); + if(buffer.size() >= 584) { + string description; + description.reserve(512); + memory::copy(description.pointer(), buffer.data() + 72, 512); + description.resize(description.length()); + stateList.item(slot)->setText(1, description); + } else { + stateList.item(slot)->setText(1, "(empty)"); + } + } + doChange(); +} + +auto StateManager::doLabel() -> void { + if(auto item = stateList.selected()) { + auto buffer = file::read(program->stateName(1 + item->offset(), true)); + if(buffer.size() >= 584) { + string description = descriptionValue.text(); + description.reserve(512); + memory::copy(buffer.data() + 72, description.data(), 512); + file::write(program->stateName(1 + item->offset(), true), buffer); + doRefresh(); + } + } +} + +auto StateManager::doLoad() -> void { + if(auto item = stateList.selected()) { + program->loadState(1 + item->offset(), true); + } +} + +auto StateManager::doSave() -> void { + if(auto item = stateList.selected()) { + program->saveState(1 + item->offset(), true); + doRefresh(); + descriptionValue.setFocused(); + } +} + +auto StateManager::doReset() -> void { + if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) { + for(auto slot : range(Slots)) file::remove(program->stateName(1 + slot, true)); + doRefresh(); + } +} + +auto StateManager::doErase() -> void { + if(auto item = stateList.selected()) { + file::remove(program->stateName(1 + item->offset(), true)); + doRefresh(); + } +} diff --git a/target-tomoko/tools/tools.cpp b/target-tomoko/tools/tools.cpp new file mode 100644 index 00000000..30bb198b --- /dev/null +++ b/target-tomoko/tools/tools.cpp @@ -0,0 +1,28 @@ +#include "../tomoko.hpp" +#include "cheat-database.cpp" +#include "cheat-editor.cpp" +#include "state-manager.cpp" +CheatDatabase* cheatDatabase = nullptr; +ToolsManager* toolsManager = nullptr; + +ToolsManager::ToolsManager() { + toolsManager = this; + + layout.setMargin(5); + + setTitle("Tools"); + setSize({600, 400}); + setPlacement(1.0, 1.0); + + onSize([&] { + cheatEditor.cheatList.resizeColumns(); + stateManager.stateList.resizeColumns(); + }); +} + +auto ToolsManager::show(unsigned tool) -> void { + panel.item(tool)->setSelected(); + setVisible(); + setFocused(); + doSize(); +} diff --git a/target-tomoko/tools/tools.hpp b/target-tomoko/tools/tools.hpp new file mode 100644 index 00000000..d4a1e6b8 --- /dev/null +++ b/target-tomoko/tools/tools.hpp @@ -0,0 +1,86 @@ +struct CheatDatabase : Window { + CheatDatabase(); + auto findCodes() -> void; + auto addCodes() -> void; + + vector codes; + + VerticalLayout layout{this}; + ListView cheatList{&layout, Size{~0, ~0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button selectAllButton{&controlLayout, Size{100, 0}}; + Button unselectAllButton{&controlLayout, Size{100, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button addCodesButton{&controlLayout, Size{80, 0}}; +}; + +struct CheatEditor : TabFrameItem { + enum : unsigned { Slots = 128 }; + + CheatEditor(TabFrame*); + auto doChange() -> void; + auto doModify() -> void; + auto doRefresh() -> void; + auto doReset() -> void; + auto doErase() -> void; + auto synchronizeCodes() -> void; + auto addCode(const string& code, const string& description) -> bool; + + struct Cheat { + string code; + string description; + }; + Cheat cheats[Slots]; + + VerticalLayout layout{this}; + ListView cheatList{&layout, Size{~0, ~0}}; + HorizontalLayout codeLayout{&layout, Size{~0, 0}}; + Label codeLabel{&codeLayout, Size{70, 0}}; + LineEdit codeValue{&codeLayout, Size{~0, 0}}; + HorizontalLayout descriptionLayout{&layout, Size{~0, 0}}; + Label descriptionLabel{&descriptionLayout, Size{70, 0}}; + LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button findCodesButton{&controlLayout, Size{120, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button resetButton{&controlLayout, Size{80, 0}}; + Button eraseButton{&controlLayout, Size{80, 0}}; +}; + +struct StateManager : TabFrameItem { + enum : unsigned { Slots = 32 }; + + StateManager(TabFrame*); + auto doChange() -> void; + auto doRefresh() -> void; + auto doLabel() -> void; + auto doLoad() -> void; + auto doSave() -> void; + auto doReset() -> void; + auto doErase() -> void; + + VerticalLayout layout{this}; + ListView stateList{&layout, Size{~0, ~0}}; + HorizontalLayout descriptionLayout{&layout, Size{~0, 0}}; + Label descriptionLabel{&descriptionLayout, Size{70, 0}}; + LineEdit descriptionValue{&descriptionLayout, Size{~0, 0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button saveButton{&controlLayout, Size{80, 0}}; + Button loadButton{&controlLayout, Size{80, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button resetButton{&controlLayout, Size{80, 0}}; + Button eraseButton{&controlLayout, Size{80, 0}}; +}; + +struct ToolsManager : Window { + ToolsManager(); + auto show(unsigned tool) -> void; + + VerticalLayout layout{this}; + TabFrame panel{&layout, Size{~0, ~0}}; + CheatEditor cheatEditor{&panel}; + StateManager stateManager{&panel}; +}; + +extern CheatDatabase* cheatDatabase; +extern ToolsManager* toolsManager;