diff --git a/emulator/emulator.hpp b/emulator/emulator.hpp index 7111bdfe..84f71df4 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.15"; + static const char Version[] = "094.16"; static const char Author[] = "byuu"; static const char License[] = "GPLv3"; static const char Website[] = "http://byuu.org/"; diff --git a/target-tomoko/configuration/configuration.cpp b/target-tomoko/configuration/configuration.cpp index b5487c49..f2b7d868 100644 --- a/target-tomoko/configuration/configuration.cpp +++ b/target-tomoko/configuration/configuration.cpp @@ -17,6 +17,10 @@ ConfigurationManager::ConfigurationManager() { video.append(video.aspectCorrection, "AspectCorrection"); video.append(video.filter, "Filter"); video.append(video.colorEmulation, "ColorEmulation"); + video.overscan.append(video.overscan.mask, "Mask"); + video.overscan.append(video.overscan.horizontal, "Horizontal"); + video.overscan.append(video.overscan.vertical, "Vertical"); + video.append(video.overscan, "Overscan"); append(video, "Video"); audio.append(audio.driver, "Driver"); diff --git a/target-tomoko/configuration/configuration.hpp b/target-tomoko/configuration/configuration.hpp index 5971ef62..f02822d2 100644 --- a/target-tomoko/configuration/configuration.hpp +++ b/target-tomoko/configuration/configuration.hpp @@ -17,6 +17,12 @@ struct ConfigurationManager : Configuration::Document { bool aspectCorrection = true; string filter = "Blur"; bool colorEmulation = true; + + struct Overscan : Configuration::Node { + bool mask = false; + unsigned horizontal = 8; + unsigned vertical = 8; + } overscan; } video; struct Audio : Configuration::Node { diff --git a/target-tomoko/presentation/presentation.cpp b/target-tomoko/presentation/presentation.cpp index 063b4456..a004f3a5 100644 --- a/target-tomoko/presentation/presentation.cpp +++ b/target-tomoko/presentation/presentation.cpp @@ -39,6 +39,10 @@ Presentation::Presentation() { config().video.scale = "Large"; resizeViewport(); }); + aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] { + config().video.aspectCorrection = aspectCorrection.checked(); + resizeViewport(); + }); videoFilterMenu.setText("Video Filter"); MenuRadioItem::group({videoFilterNone, videoFilterBlur}); if(config().video.filter == "None") videoFilterNone.setChecked(); @@ -49,11 +53,8 @@ Presentation::Presentation() { config().video.colorEmulation = colorEmulation.checked(); program->updateVideoPalette(); }); - aspectCorrection.setText("Aspect Correction").setChecked(config().video.aspectCorrection).onToggle([&] { - config().video.aspectCorrection = aspectCorrection.checked(); - resizeViewport(); - }); - maskOverscan.setText("Mask Overscan").onToggle([&] { + maskOverscan.setText("Mask Overscan").setChecked(config().video.overscan.mask).onToggle([&] { + config().video.overscan.mask = maskOverscan.checked(); }); synchronizeVideo.setText("Synchronize Video").setChecked(config().video.synchronize).onToggle([&] { config().video.synchronize = synchronizeVideo.checked(); diff --git a/target-tomoko/presentation/presentation.hpp b/target-tomoko/presentation/presentation.hpp index 443437d8..2c94ef71 100644 --- a/target-tomoko/presentation/presentation.hpp +++ b/target-tomoko/presentation/presentation.hpp @@ -19,12 +19,12 @@ struct Presentation : Window { MenuRadioItem videoScaleLarge{&videoScaleMenu}; MenuSeparator videoScaleSeparator{&videoScaleMenu}; MenuCheckItem aspectCorrection{&videoScaleMenu}; - MenuCheckItem maskOverscan{&videoScaleMenu}; Menu videoFilterMenu{&settingsMenu}; MenuRadioItem videoFilterNone{&videoFilterMenu}; MenuRadioItem videoFilterBlur{&videoFilterMenu}; MenuSeparator videoFilterSeparator{&videoFilterMenu}; MenuCheckItem colorEmulation{&videoFilterMenu}; + MenuCheckItem maskOverscan{&videoFilterMenu}; MenuSeparator settingsMenuSeparator1{&settingsMenu}; MenuCheckItem synchronizeVideo{&settingsMenu}; MenuCheckItem synchronizeAudio{&settingsMenu}; diff --git a/target-tomoko/program/interface.cpp b/target-tomoko/program/interface.cpp index 19e6da63..271e3e36 100644 --- a/target-tomoko/program/interface.cpp +++ b/target-tomoko/program/interface.cpp @@ -40,6 +40,21 @@ auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned p } } + if(emulator->information.overscan && config().video.overscan.mask) { + unsigned h = config().video.overscan.horizontal; + unsigned v = config().video.overscan.vertical; + + if(h) for(auto y : range(height)) { + memory::fill(output + y * length, 4 * h); + memory::fill(output + y * length + (width - h), 4 * h); + } + + if(v) for(auto y : range(v)) { + memory::fill(output + y * length, 4 * width); + memory::fill(output + (height - 1 - y) * length, 4 * width); + } + } + video.unlock(); video.refresh(); } diff --git a/target-tomoko/program/media.cpp b/target-tomoko/program/media.cpp index b5b48d84..2dd037c1 100644 --- a/target-tomoko/program/media.cpp +++ b/target-tomoko/program/media.cpp @@ -29,13 +29,14 @@ auto Program::loadMedia(Emulator::Interface& _emulator, Emulator::Interface::Med presentation->setTitle(emulator->title()); presentation->systemMenu.setVisible(true); presentation->toolsMenu.setVisible(true); - toolsManager->cheatEditor.doRefresh(); + toolsManager->cheatEditor.loadCheats(); toolsManager->stateManager.doRefresh(); } auto Program::unloadMedia() -> void { if(!emulator) return; + toolsManager->cheatEditor.saveCheats(); emulator->unload(); emulator = nullptr; @@ -45,4 +46,5 @@ auto Program::unloadMedia() -> void { presentation->setTitle({"tomoko v", Emulator::Version}); presentation->systemMenu.setVisible(false); presentation->toolsMenu.setVisible(false); + toolsManager->setVisible(false); } diff --git a/target-tomoko/tools/cheat-database.cpp b/target-tomoko/tools/cheat-database.cpp index 398cb29a..82d6cbac 100644 --- a/target-tomoko/tools/cheat-database.cpp +++ b/target-tomoko/tools/cheat-database.cpp @@ -49,4 +49,5 @@ auto CheatDatabase::addCodes() -> void { } } setVisible(false); + toolsManager->cheatEditor.doRefresh(); } diff --git a/target-tomoko/tools/cheat-editor.cpp b/target-tomoko/tools/cheat-editor.cpp index c82be8cc..299d6f57 100644 --- a/target-tomoko/tools/cheat-editor.cpp +++ b/target-tomoko/tools/cheat-editor.cpp @@ -4,13 +4,16 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { 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("Code(s)")); 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(); }); + cheatList.onChange([&] { doChangeSelected(); }); + cheatList.onToggle([&](sListViewItem item) { + cheats[item->offset()].enabled = item->checked(); + synchronizeCodes(); + }); codeLabel.setText("Code(s):"); codeValue.onChange([&] { doModify(); }); descriptionLabel.setText("Description:"); @@ -20,11 +23,11 @@ CheatEditor::CheatEditor(TabFrame* parent) : TabFrameItem(parent) { eraseButton.setText("Erase").onActivate([&] { doErase(); }); } -auto CheatEditor::doChange() -> void { +auto CheatEditor::doChangeSelected() -> void { if(auto item = cheatList.selected()) { - unsigned slot = item->offset(); - codeValue.setEnabled(true).setText(cheats[slot].code); - descriptionValue.setEnabled(true).setText(cheats[slot].description); + auto& cheat = cheats[item->offset()]; + codeValue.setEnabled(true).setText(cheat.code); + descriptionValue.setEnabled(true).setText(cheat.description); eraseButton.setEnabled(true); } else { codeValue.setEnabled(false).setText(""); @@ -35,9 +38,9 @@ auto CheatEditor::doChange() -> void { auto CheatEditor::doModify() -> void { if(auto item = cheatList.selected()) { - unsigned slot = item->offset(); - cheats[slot].code = codeValue.text(); - cheats[slot].description = descriptionValue.text(); + auto& cheat = cheats[item->offset()]; + cheat.code = codeValue.text(); + cheat.description = descriptionValue.text(); doRefresh(); synchronizeCodes(); } @@ -45,26 +48,28 @@ auto CheatEditor::doModify() -> void { auto CheatEditor::doRefresh() -> void { for(auto slot : range(Slots)) { - if(cheats[slot].code || cheats[slot].description) { - lstring codes = cheats[slot].code.split("+"); + auto& cheat = cheats[slot]; + if(cheat.code || cheat.description) { + lstring codes = cheat.code.split("+"); if(codes.size() > 1) codes[0].append("+..."); - cheatList.item(slot)->setText(1, codes[0]).setText(2, cheats[slot].description); + cheatList.item(slot)->setChecked(cheat.enabled).setText(1, codes[0]).setText(2, cheat.description); } else { - cheatList.item(slot)->setText(1, "").setText(2, "(empty)"); + cheatList.item(slot)->setChecked(false).setText(1, "").setText(2, "(empty)"); } } cheatList.resizeColumns(); } -auto CheatEditor::doReset() -> void { - if(MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) { +auto CheatEditor::doReset(bool force) -> void { + if(force || MessageDialog().setParent(*toolsManager).setText("Permanently erase all slots?").question() == 0) { for(auto& cheat : cheats) { + cheat.enabled = false; cheat.code = ""; cheat.description = ""; } cheatList.setSelected(false); - doChange(); + doChangeSelected(); doRefresh(); synchronizeCodes(); } @@ -72,9 +77,10 @@ auto CheatEditor::doReset() -> void { auto CheatEditor::doErase() -> void { if(auto item = cheatList.selected()) { - unsigned slot = item->offset(); - cheats[slot].code = ""; - cheats[slot].description = ""; + auto& cheat = cheats[item->offset()]; + cheat.enabled = false; + cheat.code = ""; + cheat.description = ""; codeValue.setText(""); descriptionValue.setText(""); doRefresh(); @@ -86,10 +92,9 @@ 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); + for(auto& cheat : cheats) { + if(!cheat.enabled || !cheat.code) continue; + codes.append(cheat.code); } emulator->cheatSet(codes); @@ -97,15 +102,45 @@ auto CheatEditor::synchronizeCodes() -> void { //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 { +auto CheatEditor::addCode(const string& code, const string& description, bool enabled) -> bool { for(auto& cheat : cheats) { if(cheat.code || cheat.description) continue; - + cheat.enabled = enabled; cheat.code = code; cheat.description = description; - doRefresh(); return true; } return false; } + +auto CheatEditor::loadCheats() -> void { + doReset(true); + auto contents = string::read({program->folderPaths[0], "cheats.bml"}); + auto document = Markup::Document(contents); + for(auto& cheat : document["cartridge"]) { + if(cheat.name != "cheat") continue; + if(!addCode(cheat["code"].text(), cheat["description"].text(), cheat["enabled"].exists())) break; + } + doRefresh(); + synchronizeCodes(); +} + +auto CheatEditor::saveCheats() -> void { + if(!emulator) return; + string document = {"cartridge sha256:", emulator->sha256(), "\n"}; + unsigned count = 0; + for(auto& cheat : cheats) { + if(!cheat.code && !cheat.description) continue; + document.append(" cheat", cheat.enabled ? " enabled" : "", "\n"); + document.append(" description:", cheat.description, "\n"); + document.append(" code:", cheat.code, "\n"); + count++; + } + if(count) { + file::write({program->folderPaths[0], "cheats.bml"}, document); + } else { + file::remove({program->folderPaths[0], "cheats.bml"}); + } + doReset(true); +} diff --git a/target-tomoko/tools/tools.hpp b/target-tomoko/tools/tools.hpp index d4a1e6b8..048e949c 100644 --- a/target-tomoko/tools/tools.hpp +++ b/target-tomoko/tools/tools.hpp @@ -18,15 +18,18 @@ struct CheatEditor : TabFrameItem { enum : unsigned { Slots = 128 }; CheatEditor(TabFrame*); - auto doChange() -> void; + auto doChangeSelected() -> void; auto doModify() -> void; auto doRefresh() -> void; - auto doReset() -> void; + auto doReset(bool force = false) -> void; auto doErase() -> void; auto synchronizeCodes() -> void; - auto addCode(const string& code, const string& description) -> bool; + auto addCode(const string& code, const string& description, bool enabled = false) -> bool; + auto loadCheats() -> void; + auto saveCheats() -> void; struct Cheat { + bool enabled = false; string code; string description; };