diff --git a/icarus/core/bsx-satellaview.cpp b/icarus/core/bsx-satellaview.cpp index 3ec5cca3..9d23a939 100644 --- a/icarus/core/bsx-satellaview.cpp +++ b/icarus/core/bsx-satellaview.cpp @@ -20,12 +20,12 @@ auto Icarus::bsxSatellaviewManifest(vector& buffer, const string& locat auto Icarus::bsxSatellaviewImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "BS-X Satellaview/", name, ".bs/"}; + string target{settings["Library/Location"].text(), "BS-X Satellaview/", name, ".bs/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; - if(settings.useDatabase && !markup) { + if(settings["icarus/UseDatabase"].boolean() && !markup) { auto digest = Hash::SHA256(buffer.data(), buffer.size()).digest(); for(auto node : database.bsxSatellaview) { if(node.name() != "release") continue; @@ -37,7 +37,7 @@ auto Icarus::bsxSatellaviewImport(vector& buffer, const string& locatio } } - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { BsxSatellaviewCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -50,7 +50,7 @@ auto Icarus::bsxSatellaviewImport(vector& buffer, const string& locatio if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "program.rom"}, buffer); return success(); } diff --git a/icarus/core/famicom.cpp b/icarus/core/famicom.cpp index 25e1a46c..738a4faf 100644 --- a/icarus/core/famicom.cpp +++ b/icarus/core/famicom.cpp @@ -22,12 +22,12 @@ auto Icarus::famicomManifest(vector& buffer, const string& location) -> auto Icarus::famicomImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Famicom/", name, ".fc/"}; + string target{settings["Library/Location"].text(), "Famicom/", name, ".fc/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; -//if(settings.useHeuristics && !markup) { +//if(settings["icarus/UseHeuristics"].boolean() && !markup) { FamicomCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -40,7 +40,7 @@ auto Icarus::famicomImport(vector& buffer, const string& location) -> b if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "ines.rom"}, buffer.data(), 16); file::write({target, "program.rom"}, buffer.data() + 16, cartridge.prgrom); if(!cartridge.chrrom) return success(); diff --git a/icarus/core/game-boy-advance.cpp b/icarus/core/game-boy-advance.cpp index b0d46d99..aab3f7a3 100644 --- a/icarus/core/game-boy-advance.cpp +++ b/icarus/core/game-boy-advance.cpp @@ -20,12 +20,12 @@ auto Icarus::gameBoyAdvanceManifest(vector& buffer, const string& locat auto Icarus::gameBoyAdvanceImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Game Boy Advance/", name, ".gba/"}; + string target{settings["Library/Location"].text(), "Game Boy Advance/", name, ".gba/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { GameBoyAdvanceCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -38,7 +38,7 @@ auto Icarus::gameBoyAdvanceImport(vector& buffer, const string& locatio if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "program.rom"}, buffer); return success(); } diff --git a/icarus/core/game-boy-color.cpp b/icarus/core/game-boy-color.cpp index 4f522f1e..b7ede8dc 100644 --- a/icarus/core/game-boy-color.cpp +++ b/icarus/core/game-boy-color.cpp @@ -20,12 +20,12 @@ auto Icarus::gameBoyColorManifest(vector& buffer, const string& locatio auto Icarus::gameBoyColorImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Game Boy Color/", name, ".gbc/"}; + string target{settings["Library/Location"].text(), "Game Boy Color/", name, ".gbc/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { GameBoyCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -38,7 +38,7 @@ auto Icarus::gameBoyColorImport(vector& buffer, const string& location) if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "program.rom"}, buffer); return success(); } diff --git a/icarus/core/game-boy.cpp b/icarus/core/game-boy.cpp index df2155fa..fdb58a15 100644 --- a/icarus/core/game-boy.cpp +++ b/icarus/core/game-boy.cpp @@ -20,12 +20,12 @@ auto Icarus::gameBoyManifest(vector& buffer, const string& location) -> auto Icarus::gameBoyImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Game Boy/", name, ".gb/"}; + string target{settings["Library/Location"].text(), "Game Boy/", name, ".gb/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { GameBoyCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -38,7 +38,7 @@ auto Icarus::gameBoyImport(vector& buffer, const string& location) -> b if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "program.rom"}, buffer); return success(); } diff --git a/icarus/core/sufami-turbo.cpp b/icarus/core/sufami-turbo.cpp index 1d67a2ba..9eddc270 100644 --- a/icarus/core/sufami-turbo.cpp +++ b/icarus/core/sufami-turbo.cpp @@ -20,12 +20,12 @@ auto Icarus::sufamiTurboManifest(vector& buffer, const string& location auto Icarus::sufamiTurboImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Sufami Turbo/", name, ".st/"}; + string target{settings["Library/Location"].text(), "Sufami Turbo/", name, ".st/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; - if(settings.useDatabase && !markup) { + if(settings["icarus/UseDatabase"].boolean() && !markup) { auto digest = Hash::SHA256(buffer.data(), buffer.size()).digest(); for(auto node : database.sufamiTurbo) { if(node.name() != "release") continue; @@ -37,7 +37,7 @@ auto Icarus::sufamiTurboImport(vector& buffer, const string& location) } } - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { SufamiTurboCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { markup.append("\n"); @@ -50,7 +50,7 @@ auto Icarus::sufamiTurboImport(vector& buffer, const string& location) if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); file::write({target, "program.rom"}, buffer); return success(); } diff --git a/icarus/core/super-famicom.cpp b/icarus/core/super-famicom.cpp index 60f084a1..aa5e567b 100644 --- a/icarus/core/super-famicom.cpp +++ b/icarus/core/super-famicom.cpp @@ -25,14 +25,14 @@ auto Icarus::superFamicomManifest(vector& buffer, const string& locatio auto Icarus::superFamicomImport(vector& buffer, const string& location) -> bool { auto name = prefixname(location); auto source = pathname(location); - string target{settings.libraryPath, "Super Famicom/", name, ".sfc/"}; + string target{settings["Library/Location"].text(), "Super Famicom/", name, ".sfc/"}; //if(directory::exists(target)) return failure("game already exists"); string markup; vector roms; bool firmwareAppended = true; - if(settings.useDatabase && !markup) { + if(settings["icarus/UseDatabase"].boolean() && !markup) { auto digest = Hash::SHA256(buffer.data(), buffer.size()).digest(); for(auto node : database.superFamicom) { if(node.name() != "release") continue; @@ -44,7 +44,7 @@ auto Icarus::superFamicomImport(vector& buffer, const string& location) } } - if(settings.useHeuristics && !markup) { + if(settings["icarus/UseHeuristics"].boolean() && !markup) { SuperFamicomCartridge cartridge{buffer.data(), buffer.size()}; if(markup = cartridge.markup) { firmwareAppended = cartridge.firmware_appended; @@ -59,7 +59,7 @@ auto Icarus::superFamicomImport(vector& buffer, const string& location) superFamicomImportScanManifest(roms, document["cartridge"]); for(auto rom : roms) { auto name = rom["name"].text(); - auto size = rom["size"].decimal(); + auto size = rom["size"].natural(); if(name == "program.rom" || name == "data.rom" || firmwareAppended) continue; if(file::size({source, name}) != size) return failure({"firmware (", name, ") missing or invalid"}); } @@ -67,11 +67,11 @@ auto Icarus::superFamicomImport(vector& buffer, const string& location) if(!markup) return failure("failed to parse ROM image"); if(!directory::create(target)) return failure("library path unwritable"); - if(settings.createManifests) file::write({target, "manifest.bml"}, markup); + if(settings["icarus/CreateManifests"].boolean()) file::write({target, "manifest.bml"}, markup); unsigned offset = (buffer.size() & 0x7fff) == 512 ? 512 : 0; //skip header if present for(auto rom : roms) { auto name = rom["name"].text(); - auto size = rom["size"].decimal(); + auto size = rom["size"].natural(); if(name == "program.rom" || name == "data.rom" || firmwareAppended) { if(size > buffer.size() - offset) return failure("ROM image is missing data"); file::write({target, name}, buffer.data() + offset, size); diff --git a/icarus/icarus.cpp b/icarus/icarus.cpp index 73cf5c28..4d4fd8c3 100644 --- a/icarus/icarus.cpp +++ b/icarus/icarus.cpp @@ -4,6 +4,14 @@ using namespace nall; #include using namespace hiro; +//if file already exists in the same path as the binary; use it (portable mode) +//if not, use default requested path (*nix/user mode) +auto locate(string pathname, string filename) -> string { + string location{programpath(), filename}; + if(file_system_object::exists(location)) return location; + return {pathname, filename}; +} + #include "settings.cpp" Settings settings; @@ -27,6 +35,7 @@ Icarus icarus; #include "ui/ui.hpp" #include "ui/scan-dialog.cpp" +#include "ui/settings-dialog.cpp" #include "ui/import-dialog.cpp" #include "ui/error-dialog.cpp" @@ -38,6 +47,7 @@ auto nall::main(lstring args) -> void { } new ScanDialog; + new SettingsDialog; new ImportDialog; new ErrorDialog; scanDialog->show(); diff --git a/icarus/settings.cpp b/icarus/settings.cpp index bb331dc6..90194ba8 100644 --- a/icarus/settings.cpp +++ b/icarus/settings.cpp @@ -1,31 +1,24 @@ -struct Settings : Configuration::Document { +struct Settings : Markup::Node { Settings(); ~Settings(); - - Configuration::Node root; - string activePath; - string libraryPath; - bool createManifests = false; - bool useDatabase = true; - bool useHeuristics = true; }; Settings::Settings() { - root.append(activePath, "ActivePath"); - root.append(libraryPath, "LibraryPath"); - root.append(createManifests, "CreateManifests"); - root.append(useDatabase, "UseDatabase"); - root.append(useHeuristics, "UseHeuristics"); - append(root, "Settings"); + Markup::Node::operator=(BML::unserialize(string::read(locate({configpath(), "icarus/"}, "settings.bml")))); - directory::create({configpath(), "icarus/"}); - load({configpath(), "icarus/settings.bml"}); - save({configpath(), "icarus/settings.bml"}); + auto set = [&](const string& name, const string& value) { + //create node and set to default value only if it does not already exist + if(!operator[](name)) operator()(name).setValue(value); + }; - if(!activePath) activePath = userpath(); - if(!libraryPath) libraryPath = {userpath(), "Emulation/"}; + set("Library/Location", {userpath(), "Emulation/"}); + + set("icarus/Path", userpath()); + set("icarus/CreateManifests", false); + set("icarus/UseDatabase", true); + set("icarus/UseHeuristics", true); } Settings::~Settings() { - save({configpath(), "icarus/settings.bml"}); + file::write(locate({configpath(), "icarus/"}, "settings.bml"), BML::serialize(*this)); } diff --git a/icarus/ui/scan-dialog.cpp b/icarus/ui/scan-dialog.cpp index 6d6b0cdf..4d0e917f 100644 --- a/icarus/ui/scan-dialog.cpp +++ b/icarus/ui/scan-dialog.cpp @@ -5,7 +5,7 @@ ScanDialog::ScanDialog() { layout.setMargin(5); pathEdit.onActivate([&] { refresh(); }); refreshButton.setImage(Icon::Action::Refresh).setBordered(false).onActivate([&] { - pathEdit.setText(settings.activePath); + pathEdit.setText(settings["icarus/Path"].text()); refresh(); }); homeButton.setImage(Icon::Go::Home).setBordered(false).onActivate([&] { @@ -13,7 +13,7 @@ ScanDialog::ScanDialog() { refresh(); }); upButton.setImage(Icon::Go::Up).setBordered(false).onActivate([&] { - pathEdit.setText(dirname(settings.activePath)); + pathEdit.setText(dirname(settings["icarus/Path"].text())); refresh(); }); scanList.onActivate([&] { activate(); }); @@ -27,8 +27,10 @@ ScanDialog::ScanDialog() { if(item.cell(0).checkable()) item.cell(0).setChecked(false); } }); - createManifestsLabel.setChecked(settings.createManifests).setText("Create Manifests").onToggle([&] { - settings.createManifests = createManifestsLabel.checked(); + settingsButton.setText("Settings ...").onActivate([&] { + settingsDialog->setCentered(*this); + settingsDialog->setVisible(); + settingsDialog->setFocused(); }); importButton.setText("Import ...").onActivate([&] { import(); }); @@ -39,7 +41,7 @@ ScanDialog::ScanDialog() { auto ScanDialog::show() -> void { setVisible(); - pathEdit.setText(settings.activePath); + pathEdit.setText(settings["icarus/Path"].text()); refresh(); } @@ -50,7 +52,8 @@ auto ScanDialog::refresh() -> void { auto pathname = pathEdit.text().transform("\\", "/").rtrim("/").append("/"); if(!directory::exists(pathname)) return; - pathEdit.setText(settings.activePath = pathname); + settings["icarus/Path"].setValue(pathname); + pathEdit.setText(pathname); auto contents = directory::icontents(pathname); for(auto& name : contents) { @@ -72,7 +75,7 @@ auto ScanDialog::refresh() -> void { auto ScanDialog::activate() -> void { if(auto item = scanList.selected()) { - string location{settings.activePath, item.cell(0).text()}; + string location{settings["icarus/Path"].text(), item.cell(0).text()}; if(directory::exists(location) && !gamePakType(suffixname(location))) { pathEdit.setText(location); refresh(); @@ -84,7 +87,7 @@ auto ScanDialog::import() -> void { lstring filenames; for(auto& item : scanList.items()) { if(item.cell(0).checked()) { - filenames.append(string{settings.activePath, item.cell(0).text()}); + filenames.append(string{settings["icarus/Path"].text(), item.cell(0).text()}); } } diff --git a/icarus/ui/settings-dialog.cpp b/icarus/ui/settings-dialog.cpp new file mode 100644 index 00000000..80640d3f --- /dev/null +++ b/icarus/ui/settings-dialog.cpp @@ -0,0 +1,24 @@ +SettingsDialog::SettingsDialog() { + settingsDialog = this; + + layout.setMargin(5); + locationLabel.setText("Library Location:"); + locationEdit.setEditable(false).setText(settings["Library/Location"].text()); + changeLocationButton.setText("Change ...").onActivate([&] { + if(auto location = BrowserDialog().setParent(*this).setTitle("Select Library Location").selectFolder()) { + settings["Library/Location"].setValue(location); + locationEdit.setText(location); + } + }); + createManifestsOption.setText("Create Manifests (not recommended; breaks backward-compatibility)") + .setChecked(settings["icarus/CreateManifests"].boolean()).onToggle([&] { + settings["icarus/CreateManifests"].setValue(createManifestsOption.checked()); + }); + useDatabaseOption.setText("Use Database (highly recommended; provides bit-perfect memory mapping)") + .setChecked(settings["icarus/UseDatabase"].boolean()).onToggle([&] { + settings["icarus/UseDatabase"].setValue(useDatabaseOption.checked()); + }); + + setTitle("icarus Settings"); + setSize({480, layout.minimumSize().height()}); +} diff --git a/icarus/ui/ui.hpp b/icarus/ui/ui.hpp index 09294d88..dff89c63 100644 --- a/icarus/ui/ui.hpp +++ b/icarus/ui/ui.hpp @@ -19,8 +19,21 @@ struct ScanDialog : Window { HorizontalLayout controlLayout{&layout, Size{~0, 0}}; Button selectAllButton{&controlLayout, Size{100, 0}}; Button unselectAllButton{&controlLayout, Size{100, 0}}; - CheckLabel createManifestsLabel{&controlLayout, Size{~0, 0}}; - Button importButton{&controlLayout, Size{80, 0}}; + Widget controlSpacer{&controlLayout, Size{~0, 0}}; + Button settingsButton{&controlLayout, Size{100, 0}}; + Button importButton{&controlLayout, Size{100, 0}}; +}; + +struct SettingsDialog : Window { + SettingsDialog(); + + VerticalLayout layout{this}; + HorizontalLayout locationLayout{&layout, Size{~0, 0}}; + Label locationLabel{&locationLayout, Size{0, 0}}; + LineEdit locationEdit{&locationLayout, Size{~0, 0}}; + Button changeLocationButton{&locationLayout, Size{80, 0}}; + CheckLabel createManifestsOption{&layout, Size{~0, 0}, 2}; + CheckLabel useDatabaseOption{&layout, Size{~0, 0}}; }; struct ImportDialog : Window { @@ -50,5 +63,6 @@ struct ErrorDialog : Window { }; ScanDialog* scanDialog = nullptr; +SettingsDialog* settingsDialog = nullptr; ImportDialog* importDialog = nullptr; ErrorDialog* errorDialog = nullptr;