From e030428054bf9a60bc33505d4b19a7230646eff6 Mon Sep 17 00:00:00 2001 From: byuu <2107894+byuu@users.noreply.github.com> Date: Mon, 5 Aug 2019 09:27:51 +0900 Subject: [PATCH] v108.5 * double-click a cheat finder result to add a new cheat code * fixed v108.1 regression not enabling coprocessor LLE when requested * add "[HLE] " title bar indicator for HLE mode * default to LLE mode for coprocessors * simplify game titles in main window (eg omit SGB BIOS name) * add more GUI tooltips to explain options * pause emulator during modal loops (helps Windows menubar navigation) * add support for decoding Game Genie + Pro Action Replay SNES cheats * add support for decoding Game Genie + GameShark Game Boy cheats * add tool-tip explanation to verified/unverified status bar icon --- README.md | 3 +- bsnes/emulator/emulator.hpp | 2 +- bsnes/emulator/interface.hpp | 1 + bsnes/sfc/cartridge/cartridge.cpp | 11 + bsnes/sfc/cartridge/cartridge.hpp | 1 + bsnes/sfc/cartridge/load.cpp | 6 +- bsnes/sfc/coprocessor/sa1/bwram.cpp | 2 +- bsnes/sfc/coprocessor/sa1/iram.cpp | 2 +- bsnes/sfc/coprocessor/sa1/rom.cpp | 2 +- bsnes/sfc/cpu/timing.cpp | 4 +- bsnes/sfc/interface/configuration.cpp | 4 +- bsnes/sfc/interface/configuration.hpp | 6 +- bsnes/sfc/interface/interface.cpp | 4 + bsnes/sfc/interface/interface.hpp | 1 + .../presentation/presentation.cpp | 19 +- bsnes/target-bsnes/program/game.cpp | 6 +- bsnes/target-bsnes/program/hacks.cpp | 4 +- bsnes/target-bsnes/program/program.cpp | 12 +- bsnes/target-bsnes/settings/emulator.cpp | 11 +- bsnes/target-bsnes/settings/settings.cpp | 38 ++-- bsnes/target-bsnes/settings/settings.hpp | 12 +- bsnes/target-bsnes/tools/cheat-editor.cpp | 210 +++++++++++++++++- bsnes/target-bsnes/tools/cheat-finder.cpp | 24 +- bsnes/target-bsnes/tools/tools.hpp | 3 + 24 files changed, 326 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 2c991be0..02accd60 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,13 @@ Standard Features - Sprite limit disable support - Cubic audio interpolation support - Optional high-level emulation of most SNES coprocessors - - SuperFX overclocking of up to 800% + - CPU, SA1, and SuperFX overclocking support - Frame advance support - Screenshot support - Cheat code search support - Movie recording and playback support - Rewind support + - HiDPI support Links ----- diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 2595793b..13ac9cfd 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -31,7 +31,7 @@ using namespace nall; namespace Emulator { static const string Name = "bsnes"; - static const string Version = "108.4"; + static const string Version = "108.5"; static const string Author = "byuu"; static const string License = "GPLv3"; static const string Website = "https://byuu.org"; diff --git a/bsnes/emulator/interface.hpp b/bsnes/emulator/interface.hpp index ed19f512..9b89ff95 100644 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -61,6 +61,7 @@ struct Interface { virtual auto hashes() -> vector { return {}; } virtual auto manifests() -> vector { return {}; } virtual auto titles() -> vector { return {}; } + virtual auto title() -> string { return {}; } virtual auto load() -> bool { return false; } virtual auto save() -> void {} virtual auto unload() -> void {} diff --git a/bsnes/sfc/cartridge/cartridge.cpp b/bsnes/sfc/cartridge/cartridge.cpp index 2e939d1b..ad42d242 100644 --- a/bsnes/sfc/cartridge/cartridge.cpp +++ b/bsnes/sfc/cartridge/cartridge.cpp @@ -37,6 +37,17 @@ auto Cartridge::titles() const -> vector { return titles; } +auto Cartridge::title() const -> string { + if(slotGameBoy.label) return slotGameBoy.label; + if(has.MCC && slotBSMemory.label) return slotBSMemory.label; + if(slotBSMemory.label) return {game.label, " + ", slotBSMemory.label}; + if(slotSufamiTurboA.label && slotSufamiTurboB.label) return {slotSufamiTurboA.label, " + ", slotSufamiTurboB.label}; + if(slotSufamiTurboA.label) return slotSufamiTurboA.label; + if(slotSufamiTurboB.label) return slotSufamiTurboB.label; + if(has.Cx4 || has.DSP1 || has.DSP2 || has.DSP4 || has.ST0010) return {"[HLE] ", game.label}; + return game.label; +} + auto Cartridge::load() -> bool { information = {}; has = {}; diff --git a/bsnes/sfc/cartridge/cartridge.hpp b/bsnes/sfc/cartridge/cartridge.hpp index 671f209b..32c7f2d0 100644 --- a/bsnes/sfc/cartridge/cartridge.hpp +++ b/bsnes/sfc/cartridge/cartridge.hpp @@ -5,6 +5,7 @@ struct Cartridge { auto hashes() const -> vector; auto manifests() const -> vector; auto titles() const -> vector; + auto title() const -> string; auto load() -> bool; auto save() -> void; diff --git a/bsnes/sfc/cartridge/load.cpp b/bsnes/sfc/cartridge/load.cpp index 0f94f43e..5f2db767 100644 --- a/bsnes/sfc/cartridge/load.cpp +++ b/bsnes/sfc/cartridge/load.cpp @@ -428,7 +428,7 @@ auto Cartridge::loadHitachiDSP(Markup::Node node, uint roms) -> void { } } - if(configuration.hacks.coprocessors.hle) { + if(configuration.hacks.coprocessor.preferHLE) { has.Cx4 = true; for(auto map : node.find("map")) { loadMap(map, {&Cx4::read, &cx4}, {&Cx4::write, &cx4}); @@ -503,7 +503,7 @@ auto Cartridge::loaduPD7725(Markup::Node node) -> void { } } - if(failed || configuration.hacks.coprocessors.hle) { + if(failed || configuration.hacks.coprocessor.preferHLE) { auto manifest = BML::serialize(game.document); if(manifest.find("identifier: DSP1")) { //also matches DSP1B has.DSP1 = true; @@ -583,7 +583,7 @@ auto Cartridge::loaduPD96050(Markup::Node node) -> void { } } - if(failed || configuration.hacks.coprocessors.hle) { + if(failed || configuration.hacks.coprocessor.preferHLE) { auto manifest = BML::serialize(game.document); if(manifest.find("identifier: ST010")) { has.ST0010 = true; diff --git a/bsnes/sfc/coprocessor/sa1/bwram.cpp b/bsnes/sfc/coprocessor/sa1/bwram.cpp index 5de8caf8..b8ec9479 100644 --- a/bsnes/sfc/coprocessor/sa1/bwram.cpp +++ b/bsnes/sfc/coprocessor/sa1/bwram.cpp @@ -1,5 +1,5 @@ auto SA1::BWRAM::conflict() const -> bool { - if(configuration.hacks.coprocessors.delayedSync) return false; + if(configuration.hacks.coprocessor.delayedSync) return false; if((cpu.r.mar & 0x40e000) == 0x006000) return true; //00-3f,80-bf:6000-7fff if((cpu.r.mar & 0xf00000) == 0x400000) return true; //40-4f:0000-ffff diff --git a/bsnes/sfc/coprocessor/sa1/iram.cpp b/bsnes/sfc/coprocessor/sa1/iram.cpp index 9e489559..b4b01ddf 100644 --- a/bsnes/sfc/coprocessor/sa1/iram.cpp +++ b/bsnes/sfc/coprocessor/sa1/iram.cpp @@ -1,5 +1,5 @@ auto SA1::IRAM::conflict() const -> bool { - if(configuration.hacks.coprocessors.delayedSync) return false; + if(configuration.hacks.coprocessor.delayedSync) return false; if((cpu.r.mar & 0x40f800) == 0x003000) return cpu.refresh() == 0; //00-3f,80-bf:3000-37ff return false; diff --git a/bsnes/sfc/coprocessor/sa1/rom.cpp b/bsnes/sfc/coprocessor/sa1/rom.cpp index 4284d1fb..6aa85f51 100644 --- a/bsnes/sfc/coprocessor/sa1/rom.cpp +++ b/bsnes/sfc/coprocessor/sa1/rom.cpp @@ -1,5 +1,5 @@ auto SA1::ROM::conflict() const -> bool { - if(configuration.hacks.coprocessors.delayedSync) return false; + if(configuration.hacks.coprocessor.delayedSync) return false; if((cpu.r.mar & 0x408000) == 0x008000) return true; //00-3f,80-bf:8000-ffff if((cpu.r.mar & 0xc00000) == 0xc00000) return true; //c0-ff:0000-ffff diff --git a/bsnes/sfc/cpu/timing.cpp b/bsnes/sfc/cpu/timing.cpp index 8d48fbc1..04eea3ac 100644 --- a/bsnes/sfc/cpu/timing.cpp +++ b/bsnes/sfc/cpu/timing.cpp @@ -36,7 +36,7 @@ auto CPU::step() -> void { overclocking.counter += Clocks; if(overclocking.counter < overclocking.target) { if constexpr(Synchronize) { - if(configuration.hacks.coprocessors.delayedSync) return; + if(configuration.hacks.coprocessor.delayedSync) return; synchronizeCoprocessors(); } return; @@ -64,7 +64,7 @@ auto CPU::step() -> void { } if constexpr(Synchronize) { - if(configuration.hacks.coprocessors.delayedSync) return; + if(configuration.hacks.coprocessor.delayedSync) return; synchronizeCoprocessors(); } } diff --git a/bsnes/sfc/interface/configuration.cpp b/bsnes/sfc/interface/configuration.cpp index a4141fc1..22b8fd11 100644 --- a/bsnes/sfc/interface/configuration.cpp +++ b/bsnes/sfc/interface/configuration.cpp @@ -25,8 +25,8 @@ auto Configuration::process(Markup::Node document, bool load) -> void { bind(boolean, "Hacks/PPU/Mode7/Mosaic", hacks.ppu.mode7.mosaic); bind(boolean, "Hacks/DSP/Fast", hacks.dsp.fast); bind(boolean, "Hacks/DSP/Cubic", hacks.dsp.cubic); - bind(boolean, "Hacks/Coprocessors/HLE", hacks.coprocessors.hle); - bind(boolean, "Hacks/Coprocessors/DelayedSync", hacks.coprocessors.delayedSync); + bind(boolean, "Hacks/Coprocessor/DelayedSync", hacks.coprocessor.delayedSync); + bind(boolean, "Hacks/Coprocessor/PreferHLE", hacks.coprocessor.preferHLE); bind(natural, "Hacks/SA1/Overclock", hacks.sa1.overclock); bind(natural, "Hacks/SuperFX/Overclock", hacks.superfx.overclock); diff --git a/bsnes/sfc/interface/configuration.hpp b/bsnes/sfc/interface/configuration.hpp index 813be3ed..1b8986b3 100644 --- a/bsnes/sfc/interface/configuration.hpp +++ b/bsnes/sfc/interface/configuration.hpp @@ -42,10 +42,10 @@ struct Configuration { bool fast = true; bool cubic = false; } dsp; - struct Coprocessors { + struct Coprocessor { bool delayedSync = true; - bool hle = true; - } coprocessors; + bool preferHLE = false; + } coprocessor; struct SA1 { uint overclock = 100; } sa1; diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index dd2f68d6..1290e3e3 100644 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -70,6 +70,10 @@ auto Interface::titles() -> vector { return cartridge.titles(); } +auto Interface::title() -> string { + return cartridge.title(); +} + auto Interface::load() -> bool { return system.load(this); } diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index 756750a4..cfd46669 100644 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -40,6 +40,7 @@ struct Interface : Emulator::Interface { auto hashes() -> vector override; auto manifests() -> vector override; auto titles() -> vector override; + auto title() -> string override; auto load() -> bool override; auto save() -> void override; auto unload() -> void override; diff --git a/bsnes/target-bsnes/presentation/presentation.cpp b/bsnes/target-bsnes/presentation/presentation.cpp index b0b37fc2..40261ca6 100644 --- a/bsnes/target-bsnes/presentation/presentation.cpp +++ b/bsnes/target-bsnes/presentation/presentation.cpp @@ -277,12 +277,23 @@ auto Presentation::updateStatusIcon() -> void { icon.allocate(16, StatusHeight); icon.fill(0xff202020); - if(emulator->loaded()) { - image emblem{program.verified() ? (image)Icon::Emblem::Program : (image)Icon::Emblem::Binary}; + if(emulator->loaded() && program.verified()) { + image emblem{Icon::Emblem::Program}; icon.impose(image::blend::sourceAlpha, 0, (StatusHeight - 16) / 2, emblem, 0, 0, 16, 16); + statusIcon.setIcon(icon).setToolTip( + "This is a known clean game image.\n" + "PCB emulation is 100% accurate." + ); + } else if(emulator->loaded()) { + image emblem{Icon::Emblem::Binary}; + icon.impose(image::blend::sourceAlpha, 0, (StatusHeight - 16) / 2, emblem, 0, 0, 16, 16); + statusIcon.setIcon(icon).setToolTip( + "This is not a verified game image.\n" + "PCB emulation is relying on heuristics." + ); + } else { + statusIcon.setIcon(icon).setToolTip(); } - - statusIcon.setIcon(icon); } auto Presentation::resizeWindow() -> void { diff --git a/bsnes/target-bsnes/program/game.cpp b/bsnes/target-bsnes/program/game.cpp index d8988392..4c878e92 100644 --- a/bsnes/target-bsnes/program/game.cpp +++ b/bsnes/target-bsnes/program/game.cpp @@ -10,8 +10,8 @@ auto Program::load() -> void { emulator->configure("Hacks/PPU/Mode7/Mosaic", settings.emulator.hack.ppu.mode7.mosaic); emulator->configure("Hacks/DSP/Fast", settings.emulator.hack.dsp.fast); emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic); - emulator->configure("Hacks/Coprocessor/DelayedSync", settings.emulator.hack.coprocessors.delayedSync); - emulator->configure("Hacks/Coprocessor/HLE", settings.emulator.hack.coprocessors.hle); + emulator->configure("Hacks/Coprocessor/DelayedSync", settings.emulator.hack.coprocessor.delayedSync); + emulator->configure("Hacks/Coprocessor/PreferHLE", settings.emulator.hack.coprocessor.preferHLE); emulator->configure("Hacks/SuperFX/Overclock", settings.emulator.hack.superfx.overclock); if(!emulator->load()) return; @@ -45,7 +45,7 @@ auto Program::load() -> void { appliedPatch() ? " and patch applied" : "" }); presentation.setFocused(); - presentation.setTitle(emulator->titles().merge(" + ")); + presentation.setTitle(emulator->title()); presentation.resetSystem.setEnabled(true); presentation.unloadGame.setEnabled(true); presentation.toolsMenu.setVisible(true); diff --git a/bsnes/target-bsnes/program/hacks.cpp b/bsnes/target-bsnes/program/hacks.cpp index 1c83d352..91deb40f 100644 --- a/bsnes/target-bsnes/program/hacks.cpp +++ b/bsnes/target-bsnes/program/hacks.cpp @@ -2,7 +2,7 @@ auto Program::hackCompatibility() -> void { bool fastPPU = emulatorSettings.fastPPU.checked(); bool fastPPUNoSpriteLimit = emulatorSettings.noSpriteLimit.checked(); bool fastDSP = emulatorSettings.fastDSP.checked(); - bool coprocessorsDelayedSync = emulatorSettings.coprocessorsDelayedSyncOption.checked(); + bool coprocessorDelayedSync = emulatorSettings.coprocessorDelayedSyncOption.checked(); auto title = superFamicom.title; if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") fastPPU = false; @@ -17,7 +17,7 @@ auto Program::hackCompatibility() -> void { emulator->configure("Hacks/PPU/Mode7/Mosaic", settings.emulator.hack.ppu.mode7.mosaic); emulator->configure("Hacks/DSP/Fast", fastDSP); emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic); - emulator->configure("Hacks/Coprocessors/DelayedSync", coprocessorsDelayedSync); + emulator->configure("Hacks/Coprocessor/DelayedSync", coprocessorDelayedSync); } auto Program::hackPatchMemory(vector& data) -> void { diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index 636aa1a4..9df621af 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -75,15 +75,19 @@ auto Program::create() -> void { auto Program::main() -> void { updateStatus(); video.poll(); + + if(Application::modal()) { + audio.clear(); + return; + } + inputManager.poll(); inputManager.pollHotkeys(); if(inactive()) { audio.clear(); - if(!Application::modal()) { - usleep(20 * 1000); - viewportRefresh(); - } + usleep(20 * 1000); + viewportRefresh(); return; } diff --git a/bsnes/target-bsnes/settings/emulator.cpp b/bsnes/target-bsnes/settings/emulator.cpp index 9fc44a01..d6a2011f 100644 --- a/bsnes/target-bsnes/settings/emulator.cpp +++ b/bsnes/target-bsnes/settings/emulator.cpp @@ -74,11 +74,14 @@ auto EmulatorSettings::create() -> void { emulator->configure("Hacks/DSP/Cubic", settings.emulator.hack.dsp.cubic); }); coprocessorLabel.setText("Coprocessors").setFont(Font().setBold()); - coprocessorsDelayedSyncOption.setText("Fast mode").setChecked(settings.emulator.hack.coprocessors.delayedSync).onToggle([&] { - settings.emulator.hack.coprocessors.delayedSync = coprocessorsDelayedSyncOption.checked(); + coprocessorDelayedSyncOption.setText("Fast mode").setChecked(settings.emulator.hack.coprocessor.delayedSync).onToggle([&] { + settings.emulator.hack.coprocessor.delayedSync = coprocessorDelayedSyncOption.checked(); }); - coprocessorsHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessors.hle).onToggle([&] { - settings.emulator.hack.coprocessors.hle = coprocessorsHLEOption.checked(); + coprocessorPreferHLEOption.setText("Prefer HLE").setChecked(settings.emulator.hack.coprocessor.preferHLE).setToolTip( + "When checked, less accurate HLE emulation will always be used when available.\n" + "When unchecked, HLE will only be used when LLE firmware is missing." + ).onToggle([&] { + settings.emulator.hack.coprocessor.preferHLE = coprocessorPreferHLEOption.checked(); }); hacksNote.setText("Note: some hack setting changes do not take effect until after reloading games."); } diff --git a/bsnes/target-bsnes/settings/settings.cpp b/bsnes/target-bsnes/settings/settings.cpp index 06a460f7..67823c11 100644 --- a/bsnes/target-bsnes/settings/settings.cpp +++ b/bsnes/target-bsnes/settings/settings.cpp @@ -104,25 +104,25 @@ auto Settings::process(bool load) -> void { bind(natural, "Rewind/Length", rewind.length); bind(boolean, "Rewind/Mute", rewind.mute); - bind(boolean, "Emulator/WarnOnUnverifiedGames", emulator.warnOnUnverifiedGames); - bind(boolean, "Emulator/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable); - bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval); - bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload); - bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad); - bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock); - bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast); - bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit); - bind(natural, "Emulator/Hack/PPU/Mode7/Scale", emulator.hack.ppu.mode7.scale); - bind(boolean, "Emulator/Hack/PPU/Mode7/Perspective", emulator.hack.ppu.mode7.perspective); - bind(boolean, "Emulator/Hack/PPU/Mode7/Supersample", emulator.hack.ppu.mode7.supersample); - bind(boolean, "Emulator/Hack/PPU/Mode7/Mosaic", emulator.hack.ppu.mode7.mosaic); - bind(boolean, "Emulator/Hack/DSP/Fast", emulator.hack.dsp.fast); - bind(boolean, "Emulator/Hack/DSP/Cubic", emulator.hack.dsp.cubic); - bind(boolean, "Emulator/Hack/Coprocessors/DelayedSync", emulator.hack.coprocessors.delayedSync); - bind(boolean, "Emulator/Hack/Coprocessors/HLE", emulator.hack.coprocessors.hle); - bind(natural, "Emulator/Hack/SA1/Overclock", emulator.hack.sa1.overclock); - bind(natural, "Emulator/Hack/SuperFX/Overclock", emulator.hack.superfx.overclock); - bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable); + bind(boolean, "Emulator/WarnOnUnverifiedGames", emulator.warnOnUnverifiedGames); + bind(boolean, "Emulator/AutoSaveMemory/Enable", emulator.autoSaveMemory.enable); + bind(natural, "Emulator/AutoSaveMemory/Interval", emulator.autoSaveMemory.interval); + bind(boolean, "Emulator/AutoSaveStateOnUnload", emulator.autoSaveStateOnUnload); + bind(boolean, "Emulator/AutoLoadStateOnLoad", emulator.autoLoadStateOnLoad); + bind(natural, "Emulator/Hack/CPU/Overclock", emulator.hack.cpu.overclock); + bind(boolean, "Emulator/Hack/PPU/Fast", emulator.hack.ppu.fast); + bind(boolean, "Emulator/Hack/PPU/NoSpriteLimit", emulator.hack.ppu.noSpriteLimit); + bind(natural, "Emulator/Hack/PPU/Mode7/Scale", emulator.hack.ppu.mode7.scale); + bind(boolean, "Emulator/Hack/PPU/Mode7/Perspective", emulator.hack.ppu.mode7.perspective); + bind(boolean, "Emulator/Hack/PPU/Mode7/Supersample", emulator.hack.ppu.mode7.supersample); + bind(boolean, "Emulator/Hack/PPU/Mode7/Mosaic", emulator.hack.ppu.mode7.mosaic); + bind(boolean, "Emulator/Hack/DSP/Fast", emulator.hack.dsp.fast); + bind(boolean, "Emulator/Hack/DSP/Cubic", emulator.hack.dsp.cubic); + bind(boolean, "Emulator/Hack/Coprocessor/DelayedSync", emulator.hack.coprocessor.delayedSync); + bind(boolean, "Emulator/Hack/Coprocessor/PreferHLE", emulator.hack.coprocessor.preferHLE); + bind(natural, "Emulator/Hack/SA1/Overclock", emulator.hack.sa1.overclock); + bind(natural, "Emulator/Hack/SuperFX/Overclock", emulator.hack.superfx.overclock); + bind(boolean, "Emulator/Cheats/Enable", emulator.cheats.enable); bind(boolean, "General/StatusBar", general.statusBar); bind(boolean, "General/ScreenSaver", general.screenSaver); diff --git a/bsnes/target-bsnes/settings/settings.hpp b/bsnes/target-bsnes/settings/settings.hpp index 9e64eccb..e1ef039f 100644 --- a/bsnes/target-bsnes/settings/settings.hpp +++ b/bsnes/target-bsnes/settings/settings.hpp @@ -109,10 +109,10 @@ struct Settings : Markup::Node { bool fast = true; bool cubic = false; } dsp; - struct Coprocessors { + struct Coprocessor { bool delayedSync = true; - bool hle = true; - } coprocessors; + bool preferHLE = false; + } coprocessor; struct SA1 { uint overclock = 100; } sa1; @@ -341,9 +341,9 @@ public: CheckLabel fastDSP{&dspLayout, Size{0, 0}}; CheckLabel cubicInterpolation{&dspLayout, Size{0, 0}}; Label coprocessorLabel{this, Size{~0, 0}, 2}; - HorizontalLayout coprocessorsLayout{this, Size{~0, 0}}; - CheckLabel coprocessorsDelayedSyncOption{&coprocessorsLayout, Size{0, 0}}; - CheckLabel coprocessorsHLEOption{&coprocessorsLayout, Size{0, 0}}; + HorizontalLayout coprocessorLayout{this, Size{~0, 0}}; + CheckLabel coprocessorDelayedSyncOption{&coprocessorLayout, Size{0, 0}}; + CheckLabel coprocessorPreferHLEOption{&coprocessorLayout, Size{0, 0}}; Label hacksNote{this, Size{~0, 0}}; }; diff --git a/bsnes/target-bsnes/tools/cheat-editor.cpp b/bsnes/target-bsnes/tools/cheat-editor.cpp index d593f5ec..8214d5b3 100644 --- a/bsnes/target-bsnes/tools/cheat-editor.cpp +++ b/bsnes/target-bsnes/tools/cheat-editor.cpp @@ -29,9 +29,12 @@ auto CheatDatabase::findCheats() -> void { for(auto cheat : game.find("cheat")) { //convert old cheat format (address/data and address/compare/data) //to new cheat format (address=data and address=compare?data) - auto code = cheat["code"].text(); - code.replace("/", "=", 1L); - code.replace("/", "?", 1L); + auto codes = cheat["code"].text().split("+").strip(); + for(auto& code : codes) { + code.replace("/", "=", 1L); + code.replace("/", "?", 1L); + } + auto code = codes.merge("+"); cheatList.append(ListViewItem() .setCheckable() .setText(cheat["description"].text()) @@ -98,7 +101,35 @@ auto CheatWindow::doChange() -> void { } auto CheatWindow::doAccept() -> void { - Cheat cheat = {nameValue.text().strip(), codeValue.text().split("\n").strip().merge("+"), enableOption.checked()}; + auto codes = codeValue.text().downcase().transform("+", "\n").split("\n").strip(); + string invalid; //if empty after below for-loop, code is considered valid + for(auto& code : codes) { + if(!program.gameBoy.program) { + if(!cheatEditor.decodeSNES(code)) { + invalid = + "Invalid code(s), please only use codes in the following format:\n" + "\n" + "Game Genie (eeee-eeee)\n" + "Pro Action Replay (aaaaaadd)\n" + "higan (aaaaaa=dd)\n" + "higan (aaaaaa=cc?dd)"; + } + } else { + if(!cheatEditor.decodeGB(code)) { + invalid = + "Invalid code(s), please only use codes in the following format:\n" + "\n" + "Game Genie (eee-eee)\n" + "Game Genie (eee-eee-eee)\n" + "GameShark (01ddaaaa)\n" + "higan (aaaa=dd)\n" + "higan (aaaa=cc?dd)"; + } + } + } + if(invalid) return (void)MessageDialog().setAlignment(*toolsWindow).setText(invalid).error(); + + Cheat cheat = {nameValue.text().strip(), codes.merge("+"), enableOption.checked()}; if(acceptButton.text() == "Add") { cheatEditor.addCheat(cheat); } else { @@ -260,3 +291,174 @@ auto CheatEditor::synchronizeCodes() -> void { } emulator->cheats(codes); } + +// + +auto CheatEditor::decodeSNES(string& code) -> bool { + //Game Genie + if(code.size() == 9 && code[4] == '-') { + //strip '-' + code = {code.slice(0, 4), code.slice(5, 4)}; + //validate + for(uint n : code) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //decode + code.transform("df4709156bc8a23e", "0123456789abcdef"); + uint32_t r = toHex(code); + //abcd efgh ijkl mnop qrst uvwx + //ijkl qrst opab cduv wxef ghmn + uint address = + (!!(r & 0x002000) << 23) | (!!(r & 0x001000) << 22) + | (!!(r & 0x000800) << 21) | (!!(r & 0x000400) << 20) + | (!!(r & 0x000020) << 19) | (!!(r & 0x000010) << 18) + | (!!(r & 0x000008) << 17) | (!!(r & 0x000004) << 16) + | (!!(r & 0x800000) << 15) | (!!(r & 0x400000) << 14) + | (!!(r & 0x200000) << 13) | (!!(r & 0x100000) << 12) + | (!!(r & 0x000002) << 11) | (!!(r & 0x000001) << 10) + | (!!(r & 0x008000) << 9) | (!!(r & 0x004000) << 8) + | (!!(r & 0x080000) << 7) | (!!(r & 0x040000) << 6) + | (!!(r & 0x020000) << 5) | (!!(r & 0x010000) << 4) + | (!!(r & 0x000200) << 3) | (!!(r & 0x000100) << 2) + | (!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0); + uint data = r >> 24; + code = {hex(address, 6L), "=", hex(data, 2L)}; + return true; + } + + //Pro Action Replay + if(code.size() == 8) { + //validate + for(uint n : code) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //decode + uint32_t r = toHex(code); + uint address = r >> 8; + uint data = r & 0xff; + code = {hex(address, 6L), "=", hex(data, 2L)}; + return true; + } + + //higan: address=data + if(code.size() == 9 && code[6] == '=') { + string nibbles = {code.slice(0, 6), code.slice(7, 2)}; + //validate + for(uint n : nibbles) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //already in decoded form + return true; + } + + //higan: address=compare?data + if(code.size() == 12 && code[6] == '=' && code[9] == '?') { + string nibbles = {code.slice(0, 6), code.slice(7, 2), code.slice(10, 2)}; + //validate + for(uint n : nibbles) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //already in decoded form + return true; + } + + //unrecognized code format + return false; +} + +auto CheatEditor::decodeGB(string& code) -> bool { + auto nibble = [&](const string& s, uint index) -> uint { + if(index >= s.size()) return 0; + if(s[index] >= '0' && s[index] <= '9') return s[index] - '0'; + return s[index] - 'a' + 10; + }; + + //Game Genie + if(code.size() == 7 && code[3] == '-') { + code = {code.slice(0, 3), code.slice(4, 3)}; + //validate + for(uint n : code) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + uint data = nibble(code, 0) << 4 | nibble(code, 1) << 0; + uint address = (nibble(code, 5) ^ 15) << 12 | nibble(code, 2) << 8 | nibble(code, 3) << 4 | nibble(code, 4) << 0; + code = {hex(address, 4L), "=", hex(data, 2L)}; + return true; + } + + //Game Genie + if(code.size() == 11 && code[3] == '-' && code[7] == '-') { + code = {code.slice(0, 3), code.slice(4, 3), code.slice(8, 3)}; + //validate + for(uint n : code) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + uint data = nibble(code, 0) << 4 | nibble(code, 1) << 0; + uint address = (nibble(code, 5) ^ 15) << 12 | nibble(code, 2) << 8 | nibble(code, 3) << 4 | nibble(code, 4) << 0; + uint8_t t = nibble(code, 6) << 4 | nibble(code, 8) << 0; + t = t >> 2 | t << 6; + uint compare = t ^ 0xba; + code = {hex(address, 4L), "=", hex(compare, 2L), "?", hex(data, 2L)}; + return true; + } + + //GameShark + if(code.size() == 8) { + //validate + for(uint n : code) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //first two characters are the code type / VRAM bank, which is almost always 01. + //other values are presumably supported, but I have no info on them, so they're not supported. + if(code[0] != '0') return false; + if(code[1] != '1') return false; + uint data = toHex(code.slice(2, 2)); + uint16_t address = toHex(code.slice(4, 4)); + address = address >> 8 | address << 8; + code = {hex(address, 4L), "=", hex(data, 2L)}; + return true; + } + + //higan: address=data + if(code.size() == 7 && code[4] == '=') { + string nibbles = {code.slice(0, 4), code.slice(5, 2)}; + //validate + for(uint n : nibbles) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //already in decoded form + return true; + } + + //higan: address=compare?data + if(code.size() == 10 && code[4] == '=' && code[7] == '?') { + string nibbles = {code.slice(0, 4), code.slice(5, 2), code.slice(8, 2)}; + //validate + for(uint n : nibbles) { + if(n >= '0' && n <= '9') continue; + if(n >= 'a' && n <= 'f') continue; + return false; + } + //already in decoded form + return true; + } + + //unrecognized code format + return false; +} diff --git a/bsnes/target-bsnes/tools/cheat-finder.cpp b/bsnes/target-bsnes/tools/cheat-finder.cpp index b6f56842..3227dc32 100644 --- a/bsnes/target-bsnes/tools/cheat-finder.cpp +++ b/bsnes/target-bsnes/tools/cheat-finder.cpp @@ -3,6 +3,28 @@ auto CheatFinder::create() -> void { setVisible(false); searchList.setHeadered(); + searchList.onActivate([&](auto cell) { + if(auto item = searchList.selected()) { + uint address = toHex(item.cell(0).text().trimLeft("0x", 1L)); + string data = item.cell(1).text().trimLeft("0x", 1L).split(" ", 1L).first(); + string code; + if(data.size() == 2) { + code.append(hex(address + 0, 6L), "=", data.slice(0, 2), "\n"); + } + if(data.size() == 4) { + code.append(hex(address + 0, 6L), "=", data.slice(2, 2), "\n"); + code.append(hex(address + 1, 6L), "=", data.slice(0, 2), "\n"); + } + if(data.size() == 6) { + code.append(hex(address + 0, 6L), "=", data.slice(4, 2), "\n"); + code.append(hex(address + 1, 6L), "=", data.slice(2, 2), "\n"); + code.append(hex(address + 2, 6L), "=", data.slice(0, 2), "\n"); + } + toolsWindow.show(1); + cheatEditor.addButton.doActivate(); + cheatWindow.codeValue.setText(code).doChange(); + } + }); searchValue.onActivate([&] { eventScan(); }); searchLabel.setText("Value:"); searchSize.append(ComboButtonItem().setText("Byte")); @@ -34,7 +56,7 @@ auto CheatFinder::restart() -> void { auto CheatFinder::refresh() -> void { searchList.reset(); searchList.append(TableViewColumn().setText("Address")); - searchList.append(TableViewColumn().setText("Value").setExpandable()); + searchList.append(TableViewColumn().setText("Value")); for(auto& candidate : candidates) { TableViewItem item{&searchList}; diff --git a/bsnes/target-bsnes/tools/tools.hpp b/bsnes/target-bsnes/tools/tools.hpp index 0e0edb39..17102a74 100644 --- a/bsnes/target-bsnes/tools/tools.hpp +++ b/bsnes/target-bsnes/tools/tools.hpp @@ -88,6 +88,9 @@ struct CheatEditor : VerticalLayout { auto saveCheats() -> void; auto synchronizeCodes() -> void; + auto decodeSNES(string& code) -> bool; + auto decodeGB(string& code) -> bool; + public: vector cheats; uint64_t activateTimeout = 0;