diff --git a/bsnes/emulator/emulator.hpp b/bsnes/emulator/emulator.hpp index 03d99998..fa173bdd 100644 --- a/bsnes/emulator/emulator.hpp +++ b/bsnes/emulator/emulator.hpp @@ -32,7 +32,7 @@ using namespace nall; namespace Emulator { static const string Name = "bsnes"; - static const string Version = "107.10"; + static const string Version = "107.11"; 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 5bbb1fe2..ed19f512 100644 --- a/bsnes/emulator/interface.hpp +++ b/bsnes/emulator/interface.hpp @@ -84,6 +84,7 @@ struct Interface { virtual auto unserialize(serializer&) -> bool { return false; } //cheat functions + virtual auto read(uint24 address) -> uint8 { return 0; } virtual auto cheats(const vector& = {}) -> void {} //configuration diff --git a/bsnes/gb/Core/memory.c b/bsnes/gb/Core/memory.c index 76d8821d..75a3f6dd 100644 --- a/bsnes/gb/Core/memory.c +++ b/bsnes/gb/Core/memory.c @@ -421,6 +421,13 @@ static GB_read_function_t * const read_map[] = read_ram, read_high_memory, /* EXXX FXXX */ }; +static GB_read_memory_callback_t GB_read_memory_callback_v = 0; + +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback) +{ + GB_read_memory_callback_v = callback; +} + uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) { if (gb->n_watchpoints) { @@ -429,6 +436,11 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr) if (is_addr_in_dma_use(gb, addr)) { addr = gb->dma_current_src; } + if (GB_read_memory_callback_v) { + uint8_t data = read_map[addr >> 12](gb, addr); + data = GB_read_memory_callback_v(gb, addr, data); + return data; + } return read_map[addr >> 12](gb, addr); } diff --git a/bsnes/gb/Core/memory.h b/bsnes/gb/Core/memory.h index 03d636d0..f0d03907 100644 --- a/bsnes/gb/Core/memory.h +++ b/bsnes/gb/Core/memory.h @@ -3,6 +3,9 @@ #include "gb_struct_def.h" #include +typedef uint8_t (*GB_read_memory_callback_t)(GB_gameboy_t *gb, uint16_t addr, uint8_t data); +void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback); + uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr); void GB_write_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value); #ifdef GB_INTERNAL diff --git a/bsnes/sfc/coprocessor/icd/icd.cpp b/bsnes/sfc/coprocessor/icd/icd.cpp index 9e673117..bab08593 100644 --- a/bsnes/sfc/coprocessor/icd/icd.cpp +++ b/bsnes/sfc/coprocessor/icd/icd.cpp @@ -27,6 +27,11 @@ namespace SameBoy { icd.joypWrite(p14, p15); } + static auto read_memory(GB_gameboy_t*, uint16_t addr, uint8_t data) -> uint8_t { + if(auto replace = icd.cheats.find(addr, data)) return replace(); + return data; + } + static auto rgb_encode(GB_gameboy_t*, uint8_t r, uint8_t g, uint8_t b) -> uint32_t { return r << 16 | g << 8 | b << 0; } @@ -78,6 +83,7 @@ auto ICD::load() -> bool { GB_set_icd_vreset_callback(&sameboy, &SameBoy::vreset); GB_set_icd_pixel_callback(&sameboy, &SameBoy::icd_pixel); GB_set_joyp_write_callback(&sameboy, &SameBoy::joyp_write); + GB_set_read_memory_callback(&sameboy, &SameBoy::read_memory); GB_set_rgb_encode_callback(&sameboy, &SameBoy::rgb_encode); GB_apu_set_sample_callback(&sameboy, &SameBoy::sample); GB_set_vblank_callback(&sameboy, &SameBoy::vblank); diff --git a/bsnes/sfc/coprocessor/icd/icd.hpp b/bsnes/sfc/coprocessor/icd/icd.hpp index 0863f638..12dac2ef 100644 --- a/bsnes/sfc/coprocessor/icd/icd.hpp +++ b/bsnes/sfc/coprocessor/icd/icd.hpp @@ -1,5 +1,6 @@ struct ICD : Emulator::Platform, Thread { shared_pointer stream; + Emulator::Cheat cheats; inline auto pathID() const -> uint { return information.pathID; } diff --git a/bsnes/sfc/interface/interface.cpp b/bsnes/sfc/interface/interface.cpp index 59f2d256..dd2f68d6 100644 --- a/bsnes/sfc/interface/interface.cpp +++ b/bsnes/sfc/interface/interface.cpp @@ -244,8 +244,15 @@ auto Interface::unserialize(serializer& s) -> bool { return system.unserialize(s); } +auto Interface::read(uint24 address) -> uint8 { + return cpu.readDisassembler(address); +} + auto Interface::cheats(const vector& list) -> void { - if(cartridge.has.ICD) return; //TODO: SameBoy cheat code support + if(cartridge.has.ICD) { + icd.cheats.assign(list); + return; + } //make all ROM data writable temporarily Memory::GlobalWriteEnable = true; diff --git a/bsnes/sfc/interface/interface.hpp b/bsnes/sfc/interface/interface.hpp index 270f1e55..756750a4 100644 --- a/bsnes/sfc/interface/interface.hpp +++ b/bsnes/sfc/interface/interface.hpp @@ -60,6 +60,7 @@ struct Interface : Emulator::Interface { auto serialize() -> serializer override; auto unserialize(serializer&) -> bool override; + auto read(uint24 address) -> uint8 override; auto cheats(const vector&) -> void override; auto configuration() -> string override; diff --git a/bsnes/target-bsnes/presentation/presentation.cpp b/bsnes/target-bsnes/presentation/presentation.cpp index 61c14a87..c119ccbd 100644 --- a/bsnes/target-bsnes/presentation/presentation.cpp +++ b/bsnes/target-bsnes/presentation/presentation.cpp @@ -158,9 +158,10 @@ auto Presentation::create() -> void { captureScreenshot.setIcon(Icon::Emblem::Image).setText("Capture Screenshot").onActivate([&] { program.captureScreenshot(); }); - cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow.show(0); }); - stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow.show(1); }); - manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow.show(2); }); + cheatFinder.setIcon(Icon::Edit::Find).setText("Cheat Finder ...").onActivate([&] { toolsWindow.show(0); }); + cheatEditor.setIcon(Icon::Edit::Replace).setText("Cheat Editor ...").onActivate([&] { toolsWindow.show(1); }); + stateManager.setIcon(Icon::Application::FileManager).setText("State Manager ...").onActivate([&] { toolsWindow.show(2); }); + manifestViewer.setIcon(Icon::Emblem::Text).setText("Manifest Viewer ...").onActivate([&] { toolsWindow.show(3); }); helpMenu.setText(tr("Help")); documentation.setIcon(Icon::Application::Browser).setText({tr("Documentation"), " ..."}).onActivate([&] { diff --git a/bsnes/target-bsnes/presentation/presentation.hpp b/bsnes/target-bsnes/presentation/presentation.hpp index 86a8a161..8790a17e 100644 --- a/bsnes/target-bsnes/presentation/presentation.hpp +++ b/bsnes/target-bsnes/presentation/presentation.hpp @@ -105,6 +105,7 @@ struct Presentation : Window { MenuItem frameAdvance{&toolsMenu}; MenuItem captureScreenshot{&toolsMenu}; MenuSeparator toolsSeparatorB{&toolsMenu}; + MenuItem cheatFinder{&toolsMenu}; MenuItem cheatEditor{&toolsMenu}; MenuItem stateManager{&toolsMenu}; MenuItem manifestViewer{&toolsMenu}; diff --git a/bsnes/target-bsnes/program/game.cpp b/bsnes/target-bsnes/program/game.cpp index ff4d25e1..9685abc7 100644 --- a/bsnes/target-bsnes/program/game.cpp +++ b/bsnes/target-bsnes/program/game.cpp @@ -46,6 +46,7 @@ auto Program::load() -> void { presentation.pauseEmulation.setChecked(false); presentation.updateProgramIcon(); presentation.updateStatusIcon(); + cheatFinder.restart(); //clear any old cheat search results cheatEditor.loadCheats(); stateManager.loadStates(); manifestViewer.loadManifest(); diff --git a/bsnes/target-bsnes/program/program.cpp b/bsnes/target-bsnes/program/program.cpp index a02ab4ac..7265692d 100644 --- a/bsnes/target-bsnes/program/program.cpp +++ b/bsnes/target-bsnes/program/program.cpp @@ -31,6 +31,7 @@ auto Program::create() -> void { driverSettings.create(); toolsWindow.create(); + cheatFinder.create(); cheatDatabase.create(); cheatWindow.create(); cheatEditor.create(); diff --git a/bsnes/target-bsnes/program/utility.cpp b/bsnes/target-bsnes/program/utility.cpp index c893797b..6ec587bf 100644 --- a/bsnes/target-bsnes/program/utility.cpp +++ b/bsnes/target-bsnes/program/utility.cpp @@ -34,8 +34,10 @@ auto Program::updateStatus() -> void { auto Program::captureScreenshot() -> bool { if(emulator->loaded() && screenshot.data) { if(auto filename = screenshotPath()) { - image capture; + //RGB555 -> RGB888 + image capture{0, 16, 0x8000, 0x7c00, 0x03e0, 0x001f}; capture.copy(screenshot.data, screenshot.pitch, screenshot.width, screenshot.height); + capture.transform(0, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff); //normalize pixel aspect ratio to 1:1 if(capture.width() == 512 && capture.height() == 240) capture.scale(512, 480, false); //hires diff --git a/bsnes/target-bsnes/tools/cheat-editor.cpp b/bsnes/target-bsnes/tools/cheat-editor.cpp index 46297a37..f6065708 100644 --- a/bsnes/target-bsnes/tools/cheat-editor.cpp +++ b/bsnes/target-bsnes/tools/cheat-editor.cpp @@ -16,11 +16,14 @@ auto CheatDatabase::create() -> void { } auto CheatDatabase::findCheats() -> void { - auto sha256 = emulator->hashes()[0]; + //hack to locate Super Game Boy cheat codes + auto sha256a = emulator->hashes()(0, "none"); + auto sha256b = emulator->hashes()(1, "none"); auto document = BML::unserialize(string::read(locate("Database/Cheat Codes.bml"))); for(auto game : document.find("cartridge")) { - if(game["sha256"].text() != sha256) continue; + if(game["sha256"].text() != sha256a + && game["sha256"].text() != sha256b) continue; cheatList.reset(); for(auto cheat : game.find("cheat")) { diff --git a/bsnes/target-bsnes/tools/cheat-finder.cpp b/bsnes/target-bsnes/tools/cheat-finder.cpp new file mode 100644 index 00000000..f6ab956b --- /dev/null +++ b/bsnes/target-bsnes/tools/cheat-finder.cpp @@ -0,0 +1,124 @@ +auto CheatFinder::create() -> void { + setIcon(Icon::Edit::Find); + setText("Cheat Finder"); + + layout.setPadding(5_sx); + searchList.setHeadered(); + searchValue.onActivate([&] { eventScan(); }); + searchLabel.setText("Value:"); + searchSize.append(ComboButtonItem().setText("Byte")); + searchSize.append(ComboButtonItem().setText("Word")); + searchSize.append(ComboButtonItem().setText("Long")); + searchMode.append(ComboButtonItem().setText("==")); + searchMode.append(ComboButtonItem().setText("!=")); + searchMode.append(ComboButtonItem().setText(">=")); + searchMode.append(ComboButtonItem().setText("<=")); + searchMode.append(ComboButtonItem().setText(">")); + searchMode.append(ComboButtonItem().setText("<")); + searchSpan.append(ComboButtonItem().setText("WRAM")); + searchSpan.append(ComboButtonItem().setText("All")); + searchScan.setText("Scan").onActivate([&] { eventScan(); }); + searchClear.setText("Clear").onActivate([&] { eventClear(); }); + + refresh(); +} + +auto CheatFinder::restart() -> void { + searchValue.setText(""); + searchSize.items().first().setSelected(); + searchMode.items().first().setSelected(); + searchSpan.items().first().setSelected(); + candidates.reset(); + refresh(); +} + +auto CheatFinder::refresh() -> void { + searchList.reset(); + searchList.append(TableViewColumn().setText("Address")); + searchList.append(TableViewColumn().setText("Value").setExpandable()); + + for(auto& candidate : candidates) { + TableViewItem item{&searchList}; + item.append(TableViewCell().setText({"0x", hex(candidate.address, 6L)})); + if(candidate.size == 0) { + item.append(TableViewCell().setText({"0x", hex(candidate.data, 2L), " (", candidate.data, ")"})); + } + if(candidate.size == 1) { + item.append(TableViewCell().setText({"0x", hex(candidate.data, 4L), " (", candidate.data, ")"})); + } + if(candidate.size == 2) { + item.append(TableViewCell().setText({"0x", hex(candidate.data, 6L), " (", candidate.data, ")"})); + } + } + + for(uint n : range(2)) { + Application::processEvents(); + searchList.resizeColumns(); + } +} + +auto CheatFinder::eventScan() -> void { + uint32_t size = searchSize.selected().offset(); + uint32_t mode = searchMode.selected().offset(); + uint32_t span = searchSpan.selected().offset(); + uint32_t data = searchValue.text().replace("$", "0x").replace("#", "").natural(); + if(size == 0) data &= 0xff; + if(size == 1) data &= 0xffff; + if(size == 2) data &= 0xffffff; + + if(!candidates) { + for(uint address : range(1 << 24)) { + if((address & 0x40e000) == 0x000000) continue; //00-3f,80-bf:0000-1fff (WRAM mirrors) + if((address & 0x40e000) == 0x002000) continue; //00-3f,80-bf:2000-3fff (I/O) + if((address & 0x40e000) == 0x004000) continue; //00-3f,80-bf:4000-5fff (I/O) + if(span == 0 && (address < 0x7e0000 || address > 0x7fffff)) continue; + + auto value = read(size, address); + if(compare(mode, value, data)) { + candidates.append({address, value, size, mode, span}); + if(candidates.size() >= 4096) break; + } + } + } else { + vector result; + for(auto& candidate : candidates) { + uint address = candidate.address; + if((address & 0x40e000) == 0x000000) continue; //00-3f,80-bf:0000-1fff (WRAM mirrors) + if((address & 0x40e000) == 0x002000) continue; //00-3f,80-bf:2000-3fff (I/O) + if((address & 0x40e000) == 0x004000) continue; //00-3f,80-bf:4000-5fff (I/O) + if(span == 0 && (address < 0x7e0000 || address > 0x7fffff)) continue; + + auto value = read(size, address); + if(compare(mode, value, data)) { + result.append({address, value, size, mode, span}); + if(result.size() >= 4096) break; + } + } + candidates = result; + } + + refresh(); +} + +auto CheatFinder::eventClear() -> void { + candidates.reset(); + refresh(); +} + +auto CheatFinder::read(uint32_t size, uint32_t address) -> uint32_t { + uint32_t value = 0; + if(size >= 0) value |= emulator->read(address + 0) << 0; + if(size >= 1) value |= emulator->read(address + 1) << 8; + if(size >= 2) value |= emulator->read(address + 2) << 16; + return value; +} + +auto CheatFinder::compare(uint32_t mode, uint32_t x, uint32_t y) -> bool { + if(mode == 0) return x == y; + if(mode == 1) return x != y; + if(mode == 2) return x >= y; + if(mode == 3) return x <= y; + if(mode == 4) return x > y; + if(mode == 5) return x < y; + return false; +} diff --git a/bsnes/target-bsnes/tools/tools.cpp b/bsnes/target-bsnes/tools/tools.cpp index 39f89dc8..2e2a6868 100644 --- a/bsnes/target-bsnes/tools/tools.cpp +++ b/bsnes/target-bsnes/tools/tools.cpp @@ -1,4 +1,5 @@ #include "../bsnes.hpp" +#include "cheat-finder.cpp" #include "cheat-editor.cpp" #include "state-manager.cpp" #include "manifest-viewer.cpp" @@ -7,6 +8,7 @@ CheatDatabase& cheatDatabase = Instances::cheatDatabase(); namespace Instances { Instance cheatWindow; } CheatWindow& cheatWindow = Instances::cheatWindow(); CheatEditor cheatEditor; +CheatFinder cheatFinder; namespace Instances { Instance stateWindow; } StateWindow& stateWindow = Instances::stateWindow(); StateManager stateManager; @@ -16,13 +18,14 @@ ToolsWindow& toolsWindow = Instances::toolsWindow(); auto ToolsWindow::create() -> void { layout.setPadding(5_sx); + panel.append(cheatFinder); panel.append(cheatEditor); panel.append(stateManager); panel.append(manifestViewer); panel.onChange([&] { uint offset = panel.selected().offset(); - if(offset != 0) cheatDatabase.setVisible(false), cheatWindow.setVisible(false); - if(offset != 1) stateWindow.setVisible(false); + if(offset != 1) cheatDatabase.setVisible(false), cheatWindow.setVisible(false); + if(offset != 2) stateWindow.setVisible(false); }); setTitle("Tools"); diff --git a/bsnes/target-bsnes/tools/tools.hpp b/bsnes/target-bsnes/tools/tools.hpp index e38f4e46..5dd9af6c 100644 --- a/bsnes/target-bsnes/tools/tools.hpp +++ b/bsnes/target-bsnes/tools/tools.hpp @@ -1,3 +1,35 @@ +struct CheatCandidate { + uint32_t address; + uint32_t data; + uint32_t size; + uint32_t mode; + uint32_t span; +}; + +struct CheatFinder : TabFrameItem { + auto create() -> void; + auto restart() -> void; + auto refresh() -> void; + auto eventScan() -> void; + auto eventClear() -> void; + auto read(uint32_t size, uint32_t address) -> uint32_t; + auto compare(uint32_t mode, uint32_t x, uint32_t y) -> bool; + +public: + vector candidates; + + VerticalLayout layout{this}; + TableView searchList{&layout, Size{~0, ~0}}; + HorizontalLayout controlLayout{&layout, Size{~0, 0}}; + Label searchLabel{&controlLayout, Size{0, 0}}; + LineEdit searchValue{&controlLayout, Size{~0, 0}}; + ComboButton searchSize{&controlLayout, Size{0, 0}}; + ComboButton searchMode{&controlLayout, Size{0, 0}}; + ComboButton searchSpan{&controlLayout, Size{0, 0}}; + Button searchScan{&controlLayout, Size{80, 0}}; + Button searchClear{&controlLayout, Size{80, 0}}; +}; + struct Cheat { auto operator==(const Cheat& compare) const -> bool { return name == compare.name && code == compare.code && enable == compare.enable; @@ -158,6 +190,7 @@ public: }; namespace Instances { extern Instance cheatDatabase; } +extern CheatFinder cheatFinder; extern CheatDatabase& cheatDatabase; namespace Instances { extern Instance cheatWindow; } extern CheatWindow& cheatWindow; diff --git a/nall/intrinsics.hpp b/nall/intrinsics.hpp index a522ea03..be277cbd 100755 --- a/nall/intrinsics.hpp +++ b/nall/intrinsics.hpp @@ -36,6 +36,7 @@ namespace nall { #pragma clang diagnostic ignored "-Wswitch-bool" #pragma clang diagnostic ignored "-Wtautological-compare" #pragma clang diagnostic ignored "-Wabsolute-value" + #pragma clang diagnostic ignored "-Wunused-result" //temporary #pragma clang diagnostic ignored "-Winconsistent-missing-override" @@ -48,6 +49,7 @@ namespace nall { #pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Wswitch-bool" + #pragma GCC diagnostic ignored "-Wunused-result" #elif defined(_MSC_VER) #define COMPILER_MICROSOFT constexpr auto compiler() -> Compiler { return Compiler::Microsoft; } diff --git a/ruby/ruby.cpp b/ruby/ruby.cpp index ae0569e3..378bb75b 100755 --- a/ruby/ruby.cpp +++ b/ruby/ruby.cpp @@ -4,6 +4,7 @@ using namespace ruby; #undef deprecated #undef mkdir +#undef noinline #undef usleep #if defined(DISPLAY_XORG)