diff --git a/higan/emulator/emulator.hpp b/higan/emulator/emulator.hpp index 9e8fdbee..fb166825 100644 --- a/higan/emulator/emulator.hpp +++ b/higan/emulator/emulator.hpp @@ -12,7 +12,7 @@ using namespace nall; namespace Emulator { static const string Name = "higan"; - static const string Version = "106.14"; + static const string Version = "106.15"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org/"; diff --git a/higan/gb/system/system.cpp b/higan/gb/system/system.cpp index a9302192..b02e3457 100644 --- a/higan/gb/system/system.cpp +++ b/higan/gb/system/system.cpp @@ -57,8 +57,8 @@ auto System::load(Emulator::Interface* interface, Model model_, maybe syst } else return false; auto document = BML::unserialize(information.manifest); - if(auto name = document["game/memory[1]/name"].text()) { - if(auto fp = platform->open(systemID(), name, File::Read, File::Required)) { + if(auto memory = Emulator::Game::Memory{document["game/board/memory(type=ROM,content=Boot,architecture=LR35902)"]}) { + if(auto fp = platform->open(systemID(), memory.name(), File::Read, File::Required)) { fp->read(bootROM.sgb, 256); } } diff --git a/higan/target-tomoko/configuration/configuration.cpp b/higan/target-tomoko/configuration/configuration.cpp index a7220038..e67efc1a 100644 --- a/higan/target-tomoko/configuration/configuration.cpp +++ b/higan/target-tomoko/configuration/configuration.cpp @@ -59,6 +59,8 @@ Settings::Settings() { set("Emulation/AutoSaveMemory/Enable", true); set("Emulation/AutoSaveMemory/Interval", 30); + set("Systems", ""); + set("Crashed", false); } diff --git a/higan/target-tomoko/presentation/presentation.cpp b/higan/target-tomoko/presentation/presentation.cpp index 423d1835..f89a1183 100644 --- a/higan/target-tomoko/presentation/presentation.cpp +++ b/higan/target-tomoko/presentation/presentation.cpp @@ -6,42 +6,9 @@ unique_pointer presentation; Presentation::Presentation() { presentation = this; - libraryMenu.setText("Library"); - string_vector manufacturers; - for(auto& emulator : program->emulators) { - if(!manufacturers.find(emulator->information.manufacturer)) { - manufacturers.append(emulator->information.manufacturer); - } - } - for(auto& manufacturer : manufacturers) { - Menu manufacturerMenu{&libraryMenu}; - manufacturerMenu.setText(manufacturer); - for(auto& emulator : program->emulators) { - if(emulator->information.manufacturer != manufacturer) continue; - for(auto& medium : emulator->media) { - auto item = new MenuItem{&manufacturerMenu}; - item->setText({medium.name, " ..."}).onActivate([=] { - program->loadMedium(*emulator, medium); - }); - } - } - } - //add icarus menu options -- but only if icarus binary is present - if(execute("icarus", "--name").output.strip() == "icarus") { - libraryMenu.append(MenuSeparator()); - libraryMenu.append(MenuItem().setText("Load ROM File ...").onActivate([&] { - audio->clear(); - if(auto location = execute("icarus", "--import")) { - program->mediumQueue.append(location.output.strip()); - program->loadMedium(); - } - })); - libraryMenu.append(MenuItem().setText("Import ROM Files ...").onActivate([&] { - invoke("icarus"); - })); - } + libraryMenu.setText("System"); - systemMenu.setText("System").setVisible(false); + systemMenu.setVisible(false); resetSystem.setText("Soft Reset").onActivate([&] { program->softReset(); }); powerSystem.setText("Power Cycle").onActivate([&] { program->powerCycle(); }); unloadSystem.setText("Unload").onActivate([&] { program->unloadMedium(); }); @@ -100,8 +67,9 @@ Presentation::Presentation() { statusBar.setVisible(showStatusBar.checked()); if(visible()) resizeViewport(); }); - showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(0); }); - showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(1); }); + showSystemSettings.setText("Systems ...").onActivate([&] { settingsManager->show(0); }); + showVideoSettings.setText("Video ...").onActivate([&] { settingsManager->show(1); }); + showAudioSettings.setText("Audio ...").onActivate([&] { settingsManager->show(2); }); showInputSettings.setText("Input ...").onActivate([&] { if(emulator) { //default input panel to current core's input settings @@ -113,10 +81,10 @@ Presentation::Presentation() { } } } - settingsManager->show(2); + settingsManager->show(3); }); - showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(3); }); - showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(4); }); + showHotkeySettings.setText("Hotkeys ...").onActivate([&] { settingsManager->show(4); }); + showAdvancedSettings.setText("Advanced ...").onActivate([&] { settingsManager->show(5); }); toolsMenu.setText("Tools").setVisible(false); saveQuickStateMenu.setText("Save Quick State"); @@ -335,6 +303,42 @@ auto Presentation::toggleFullScreen() -> void { resizeViewport(); } +auto Presentation::loadSystems() -> void { + libraryMenu.reset(); + for(auto system : settings.find("Systems/System")) { + if(system["Hidden"].boolean()) continue; + MenuItem item; + string boot = system["Boot"].text(); + item.setText({system["Name"].text(), " ..."}).onActivate([=] { + bool booted = false; + for(auto& emulator : program->emulators) { + if(boot == emulator->information.name) { + program->loadMedium(*emulator, emulator->media(0)); + booted = true; + break; + } + } + if(!booted && directory::exists(boot)) { + program->mediumQueue.append(boot); + program->loadMedium(); + } + }); + libraryMenu.append(item); + } + + //add icarus menu option -- but only if icarus binary is present + if(execute("icarus", "--name").output.strip() == "icarus") { + libraryMenu.append(MenuSeparator()); + libraryMenu.append(MenuItem().setText("Load ROM File ...").onActivate([&] { + audio->clear(); + if(auto location = execute("icarus", "--import")) { + program->mediumQueue.append(location.output.strip()); + program->loadMedium(); + } + })); + } +} + auto Presentation::loadShaders() -> void { auto pathname = locate("Video Shaders/"); diff --git a/higan/target-tomoko/presentation/presentation.hpp b/higan/target-tomoko/presentation/presentation.hpp index 9557623d..f790818c 100644 --- a/higan/target-tomoko/presentation/presentation.hpp +++ b/higan/target-tomoko/presentation/presentation.hpp @@ -14,6 +14,7 @@ struct Presentation : Window { auto clearViewport() -> void; auto resizeViewport(bool resizeWindow = true) -> void; auto toggleFullScreen() -> void; + auto loadSystems() -> void; auto loadShaders() -> void; MenuBar menuBar{this}; @@ -45,6 +46,7 @@ struct Presentation : Window { MenuCheckItem muteAudio{&settingsMenu}; MenuCheckItem showStatusBar{&settingsMenu}; MenuSeparator settingsSeparator{&settingsMenu}; + MenuItem showSystemSettings{&settingsMenu}; MenuItem showVideoSettings{&settingsMenu}; MenuItem showAudioSettings{&settingsMenu}; MenuItem showInputSettings{&settingsMenu}; diff --git a/higan/target-tomoko/program/program.cpp b/higan/target-tomoko/program/program.cpp index 76554146..a25b4c23 100644 --- a/higan/target-tomoko/program/program.cpp +++ b/higan/target-tomoko/program/program.cpp @@ -51,6 +51,7 @@ Program::Program(string_vector args) { settings.save(); new InputManager; + new SystemProperties; new SettingsManager; new CheatDatabase; new ToolsManager; diff --git a/higan/target-tomoko/settings/settings.cpp b/higan/target-tomoko/settings/settings.cpp index de6fb420..f8bfaa12 100644 --- a/higan/target-tomoko/settings/settings.cpp +++ b/higan/target-tomoko/settings/settings.cpp @@ -1,4 +1,9 @@ #include "../tomoko.hpp" + +#include "system-properties.cpp" +unique_pointer systemProperties; + +#include "systems.cpp" #include "video.cpp" #include "audio.cpp" #include "input.cpp" diff --git a/higan/target-tomoko/settings/settings.hpp b/higan/target-tomoko/settings/settings.hpp index 19c61135..e3092186 100644 --- a/higan/target-tomoko/settings/settings.hpp +++ b/higan/target-tomoko/settings/settings.hpp @@ -1,3 +1,39 @@ +struct SystemProperties : Window { + SystemProperties(); + auto append() -> void; + auto modify(Markup::Node) -> void; + + VerticalLayout layout{this}; + HorizontalLayout nameLayout{&layout, Size{~0, 0}}; + Label nameLabel{&nameLayout, Size{40, 0}}; + LineEdit nameEdit{&nameLayout, Size{~0, 0}}; + HorizontalLayout bootLayout{&layout, Size{~0, 0}}; + Label bootLabel{&bootLayout, Size{40, 0}}; + ComboEdit bootEdit{&bootLayout, Size{~0, 0}}; + Button bootBrowse{&bootLayout, Size{80, 0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Widget spacer{&controlLayout, Size{40, 0}}; + CheckLabel hiddenOption{&controlLayout, Size{~0, 0}}; + Button acceptButton{&controlLayout, Size{80, 0}}; + Button cancelButton{&controlLayout, Size{80, 0}}; +}; + +struct SystemSettings : TabFrameItem { + SystemSettings(TabFrame*); + auto reload() -> void; + auto acceptProperties() -> void; + + VerticalLayout layout{this}; + TableView systemList{&layout, Size{~0, ~0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Button upButton{&controlLayout, Size{0, 0}}; + Button downButton{&controlLayout, Size{0, 0}}; + Widget spacer{&controlLayout, Size{~0, 0}}; + Button appendButton{&controlLayout, Size{80, 0}}; + Button modifyButton{&controlLayout, Size{80, 0}}; + Button removeButton{&controlLayout, Size{80, 0}}; +}; + struct VideoSettings : TabFrameItem { VideoSettings(TabFrame*); @@ -150,6 +186,7 @@ struct SettingsManager : Window { VerticalLayout layout{this}; TabFrame panel{&layout, Size{~0, ~0}}; + SystemSettings systems{&panel}; VideoSettings video{&panel}; AudioSettings audio{&panel}; InputSettings input{&panel}; @@ -161,4 +198,5 @@ struct SettingsManager : Window { auto show(uint setting) -> void; }; +extern unique_pointer systemProperties; extern unique_pointer settingsManager; diff --git a/higan/target-tomoko/settings/system-properties.cpp b/higan/target-tomoko/settings/system-properties.cpp new file mode 100644 index 00000000..b6fa54dc --- /dev/null +++ b/higan/target-tomoko/settings/system-properties.cpp @@ -0,0 +1,50 @@ +SystemProperties::SystemProperties() { + systemProperties = this; + + layout.setMargin(5); + nameLabel.setText("Name:"); + bootLabel.setText("Boot:"); + bootEdit.setEditable(false); + for(auto& emulator : program->emulators) { + bootEdit.append(ComboEditItem().setText(emulator->information.name)); + } + bootBrowse.setText("Browse ...").onActivate([&] { + if(auto location = BrowserDialog().setTitle("Select Boot Game").setPath(settings["Library/Location"].text()).selectFolder()) { + bootEdit.setText(location); + } + }); + hiddenOption.setText("Hidden"); + acceptButton.onActivate([&] { + setVisible(false); + settingsManager->systems.acceptProperties(); + }); + cancelButton.setText("Cancel").onActivate([&] { + setVisible(false); + }); + + setTitle("System Properties"); + setSize({480, layout.minimumSize().height()}); + setDismissable(); +} + +auto SystemProperties::append() -> void { + setCentered(*settingsManager); + nameEdit.setText(""); + bootEdit.setText(""); + hiddenOption.setChecked(false); + acceptButton.setText("Append"); + setFocused(); + setVisible(); + nameEdit.setFocused(); +} + +auto SystemProperties::modify(Markup::Node system) -> void { + setCentered(*settingsManager); + nameEdit.setText(system["Name"].text()); + bootEdit.setText(system["Boot"].text()); + hiddenOption.setChecked(system["Hidden"].boolean()); + acceptButton.setText("Modify"); + setFocused(); + setVisible(); + nameEdit.setFocused(); +} diff --git a/higan/target-tomoko/settings/systems.cpp b/higan/target-tomoko/settings/systems.cpp new file mode 100644 index 00000000..278fe597 --- /dev/null +++ b/higan/target-tomoko/settings/systems.cpp @@ -0,0 +1,85 @@ +SystemSettings::SystemSettings(TabFrame* parent) : TabFrameItem(parent) { + setIcon(Icon::Device::Storage); + setText("Systems"); + + layout.setMargin(5); + + systemList.onActivate([&] { + modifyButton.doActivate(); + }); + + systemList.onChange([&] { + auto selected = (bool)systemList.selected(); + upButton.setEnabled(selected); + downButton.setEnabled(selected); + modifyButton.setEnabled(selected); + removeButton.setEnabled(selected); + }); + + upButton.setIcon(Icon::Go::Up); + downButton.setIcon(Icon::Go::Down); + + appendButton.setText("Append").onActivate([&] { + systemProperties->append(); + }); + + modifyButton.setText("Modify").onActivate([&] { + if(auto item = systemList.selected()) { + if(auto system = settings.find("Systems/System")[item.offset()]) { + systemProperties->modify(system); + } + } + }); + + removeButton.setText("Remove").onActivate([&] { + if(auto item = systemList.selected()) { + if(auto system = settings.find("Systems/System")[item.offset()]) { + if(MessageDialog().setParent(*settingsManager).setText({ + "Are you sure you want to delete this system?\n\n" + "Name: ", system["Name"].text() + }).question() == "Yes") { + settings["Systems"].remove(system); + reload(); + } + } + } + }); + + reload(); +} + +auto SystemSettings::reload() -> void { + systemList.reset(); + systemList.append(TableViewHeader().setVisible() + .append(TableViewColumn().setText("Name")) + .append(TableViewColumn().setText("Boot").setExpandable()) + ); + for(auto system : settings.find("Systems/System")) { + systemList.append(TableViewItem() + .append(TableViewCell().setText(system["Name"].text())) + .append(TableViewCell().setText(Location::base(system["Boot"].text()).trimRight("/", 1L))) + .setForegroundColor(system["Hidden"].boolean() ? Color{160, 160, 160} : Color{}) + ); + } + systemList.resizeColumns().doChange(); + presentation->loadSystems(); +} + +auto SystemSettings::acceptProperties() -> void { + if(systemProperties->acceptButton.text() == "Append") { + Markup::Node system{"System"}; + system.append({"Name", systemProperties->nameEdit.text()}); + system.append({"Boot", systemProperties->bootEdit.text()}); + system.append({"Hidden", systemProperties->hiddenOption.checked()}); + settings["Systems"].append(system); + } else if(systemProperties->acceptButton.text() == "Modify") { + if(auto item = systemList.selected()) { + if(auto system = settings.find("Systems/System")[item.offset()]) { + system("Name").setValue(systemProperties->nameEdit.text()); + system("Boot").setValue(systemProperties->bootEdit.text()); + system("Hidden").setValue(systemProperties->hiddenOption.checked()); + } + } + } + reload(); +} diff --git a/higan/target-tomoko/tools/tools.cpp b/higan/target-tomoko/tools/tools.cpp index 62b73af1..f9cba80e 100644 --- a/higan/target-tomoko/tools/tools.cpp +++ b/higan/target-tomoko/tools/tools.cpp @@ -1,10 +1,12 @@ #include "../tomoko.hpp" + #include "cheat-database.cpp" +unique_pointer cheatDatabase; + #include "cheat-editor.cpp" #include "state-manager.cpp" #include "manifest-viewer.cpp" #include "game-notes.cpp" -unique_pointer cheatDatabase; unique_pointer toolsManager; ToolsManager::ToolsManager() { diff --git a/hiro/core/core.hpp b/hiro/core/core.hpp index 97073687..0a6c627b 100644 --- a/hiro/core/core.hpp +++ b/hiro/core/core.hpp @@ -1144,6 +1144,7 @@ struct mComboEdit : mWidget { auto backgroundColor() const -> Color; auto doActivate() const -> void; auto doChange() const -> void; + auto editable() const -> bool; auto foregroundColor() const -> Color; auto item(uint position) const -> ComboEditItem; auto itemCount() const -> uint; @@ -1153,6 +1154,7 @@ struct mComboEdit : mWidget { auto remove(sComboEditItem item) -> type&; auto reset() -> type&; auto setBackgroundColor(Color color = {}) -> type&; + auto setEditable(bool editable = true) -> type&; auto setForegroundColor(Color color = {}) -> type&; auto setParent(mObject* parent = nullptr, int offset = -1) -> type& override; auto setText(const string& text = "") -> type&; @@ -1161,6 +1163,7 @@ struct mComboEdit : mWidget { //private: struct State { Color backgroundColor; + bool editable = true; Color foregroundColor; vector items; function onActivate; diff --git a/hiro/core/shared.hpp b/hiro/core/shared.hpp index 2dcec6f1..1b2fe228 100644 --- a/hiro/core/shared.hpp +++ b/hiro/core/shared.hpp @@ -342,6 +342,7 @@ struct ComboEdit : sComboEdit { auto backgroundColor() const { return self().backgroundColor(); } auto doActivate() const { return self().doActivate(); } auto doChange() const { return self().doChange(); } + auto editable() const { return self().editable(); } auto foregroundColor() const { return self().foregroundColor(); } auto item(uint position) const { return self().item(position); } auto itemCount() const { return self().itemCount(); } @@ -351,6 +352,7 @@ struct ComboEdit : sComboEdit { auto remove(sComboEditItem item) { return self().remove(item), *this; } auto reset() { return self().reset(), *this; } auto setBackgroundColor(Color color = {}) { return self().setBackgroundColor(color), *this; } + auto setEditable(bool editable = true) { return self().setEditable(editable), *this; } auto setForegroundColor(Color color = {}) { return self().setForegroundColor(color), *this; } auto setText(const string& text = "") { return self().setText(text), *this; } auto text() const { return self().text(); } diff --git a/hiro/core/widget/combo-edit.cpp b/hiro/core/widget/combo-edit.cpp index 1071cd00..1a457c2c 100644 --- a/hiro/core/widget/combo-edit.cpp +++ b/hiro/core/widget/combo-edit.cpp @@ -29,6 +29,10 @@ auto mComboEdit::doChange() const -> void { if(state.onChange) return state.onChange(); } +auto mComboEdit::editable() const -> bool { + return state.editable; +} + auto mComboEdit::foregroundColor() const -> Color { return state.foregroundColor; } @@ -81,6 +85,12 @@ auto mComboEdit::setBackgroundColor(Color color) -> type& { return *this; } +auto mComboEdit::setEditable(bool editable) -> type& { + state.editable = editable; + signal(setEditable, editable); + return *this; +} + auto mComboEdit::setForegroundColor(Color color) -> type& { state.foregroundColor = color; signal(setForegroundColor, color); diff --git a/hiro/gtk/widget/combo-edit.cpp b/hiro/gtk/widget/combo-edit.cpp index 49fe7551..f90dae4f 100644 --- a/hiro/gtk/widget/combo-edit.cpp +++ b/hiro/gtk/widget/combo-edit.cpp @@ -32,6 +32,7 @@ auto pComboEdit::construct() -> void { close(stdErr); setBackgroundColor(state().backgroundColor); + setEditable(state().editable); setForegroundColor(state().foregroundColor); for(auto& item : state().items) append(item); @@ -86,6 +87,10 @@ auto pComboEdit::setBackgroundColor(Color color) -> void { gtk_widget_modify_base(gtk_bin_get_child(GTK_BIN(gtkComboBox)), GTK_STATE_NORMAL, color ? &gdkColor : nullptr); } +auto pComboEdit::setEditable(bool editable) -> void { + gtk_editable_set_editable(GTK_EDITABLE(gtk_bin_get_child(GTK_BIN(gtkComboBox))), editable); +} + auto pComboEdit::setForegroundColor(Color color) -> void { GdkColor gdkColor = CreateColor(color); gtk_widget_modify_text(gtk_bin_get_child(GTK_BIN(gtkComboBox)), GTK_STATE_NORMAL, color ? &gdkColor : nullptr); diff --git a/hiro/gtk/widget/combo-edit.hpp b/hiro/gtk/widget/combo-edit.hpp index 78808bbc..f5d48116 100644 --- a/hiro/gtk/widget/combo-edit.hpp +++ b/hiro/gtk/widget/combo-edit.hpp @@ -10,6 +10,7 @@ struct pComboEdit : pWidget { auto remove(sComboEditItem item) -> void; auto reset() -> void; auto setBackgroundColor(Color color) -> void; + auto setEditable(bool editable) -> void; auto setForegroundColor(Color color) -> void; auto setFont(const Font& font) -> void override; auto setText(const string& text) -> void; diff --git a/nall/string/markup/node.hpp b/nall/string/markup/node.hpp index c29b8d50..fdf95973 100644 --- a/nall/string/markup/node.hpp +++ b/nall/string/markup/node.hpp @@ -8,8 +8,8 @@ using SharedNode = shared_pointer; struct ManagedNode { ManagedNode() = default; - explicit ManagedNode(const string& name) : _name(name) {} - explicit ManagedNode(const string& name, const string& value) : _name(name), _value(value) {} + ManagedNode(const string& name) : _name(name) {} + ManagedNode(const string& name, const string& value) : _name(name), _value(value) {} auto clone() const -> SharedNode { SharedNode clone{new ManagedNode(_name, _value)}; @@ -46,8 +46,8 @@ protected: struct Node { Node() : shared(new ManagedNode) {} Node(const SharedNode& source) : shared(source ? source : new ManagedNode) {} - explicit Node(const string& name) : shared(new ManagedNode(name)) {} - explicit Node(const string& name, const string& value) : shared(new ManagedNode(name, value)) {} + Node(const string& name) : shared(new ManagedNode(name)) {} + Node(const string& name, const string& value) : shared(new ManagedNode(name, value)) {} auto unique() const -> bool { return shared.unique(); } auto clone() const -> Node { return shared->clone(); }