mirror of https://github.com/bsnes-emu/bsnes.git
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
This commit is contained in:
parent
24dce7dd92
commit
e030428054
|
@ -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
|
||||
-----
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -61,6 +61,7 @@ struct Interface {
|
|||
virtual auto hashes() -> vector<string> { return {}; }
|
||||
virtual auto manifests() -> vector<string> { return {}; }
|
||||
virtual auto titles() -> vector<string> { return {}; }
|
||||
virtual auto title() -> string { return {}; }
|
||||
virtual auto load() -> bool { return false; }
|
||||
virtual auto save() -> void {}
|
||||
virtual auto unload() -> void {}
|
||||
|
|
|
@ -37,6 +37,17 @@ auto Cartridge::titles() const -> vector<string> {
|
|||
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 = {};
|
||||
|
|
|
@ -5,6 +5,7 @@ struct Cartridge {
|
|||
auto hashes() const -> vector<string>;
|
||||
auto manifests() const -> vector<string>;
|
||||
auto titles() const -> vector<string>;
|
||||
auto title() const -> string;
|
||||
|
||||
auto load() -> bool;
|
||||
auto save() -> void;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -70,6 +70,10 @@ auto Interface::titles() -> vector<string> {
|
|||
return cartridge.titles();
|
||||
}
|
||||
|
||||
auto Interface::title() -> string {
|
||||
return cartridge.title();
|
||||
}
|
||||
|
||||
auto Interface::load() -> bool {
|
||||
return system.load(this);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ struct Interface : Emulator::Interface {
|
|||
auto hashes() -> vector<string> override;
|
||||
auto manifests() -> vector<string> override;
|
||||
auto titles() -> vector<string> override;
|
||||
auto title() -> string override;
|
||||
auto load() -> bool override;
|
||||
auto save() -> void override;
|
||||
auto unload() -> void override;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<uint8_t>& data) -> void {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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<Cheat> cheats;
|
||||
uint64_t activateTimeout = 0;
|
||||
|
|
Loading…
Reference in New Issue