From 0904638df08fb617f6fe672587e4bd704c2d42e7 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 19 Jul 2023 20:51:45 +0300 Subject: [PATCH 1/5] [Cheats] Add boilerplate --- CMakeLists.txt | 4 ++-- include/cheats.hpp | 24 ++++++++++++++++++++++++ include/emulator.hpp | 5 +++++ src/core/cheats.cpp | 5 +++++ src/emulator.cpp | 6 ++++++ 5 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 include/cheats.hpp create mode 100644 src/core/cheats.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 620a1c27..867f1e81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp - src/httpserver.cpp src/stb_image_write.c + src/httpserver.cpp src/stb_image_write.c src/core/cheats.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -149,7 +149,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/result/result_common.hpp include/result/result_fs.hpp include/result/result_fnd.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp - include/config.hpp include/services/ir_user.hpp include/httpserver.hpp + include/config.hpp include/services/ir_user.hpp include/httpserver.hpp include/cheats.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/cheats.hpp b/include/cheats.hpp new file mode 100644 index 00000000..22843544 --- /dev/null +++ b/include/cheats.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +#include "helpers.hpp" + +class ActionReplay { + using Cheat = std::vector; // A cheat is really just a bunch of u32 opcodes + std::vector cheats; + + u32 offset1, offset2; // Memory offset registers. Non-persistent. + u32 data1, data2; // Data offset registers. Non-persistent. + u32 storage1, storage2; // Storage registers. Persistent. + + // When an instruction does not specify which offset or data register to use, we use the "active" one + // Which is by default #1 and may be changed by certain AR operations + u32 *activeOffset, *activeData; + + public: + ActionReplay(); + void addCheat(const Cheat& cheat); + void runCheats(); + void reset(); +}; \ No newline at end of file diff --git a/include/emulator.hpp b/include/emulator.hpp index d99eff1d..187d6eca 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -7,6 +7,7 @@ #include #include "PICA/gpu.hpp" +#include "cheats.hpp" #include "config.hpp" #include "cpu.hpp" #include "crypto/aes_engine.hpp" @@ -31,6 +32,7 @@ class Emulator { Memory memory; Kernel kernel; Crypto::AESEngine aesEngine; + ActionReplay cheats; SDL_Window* window; @@ -41,6 +43,9 @@ class Emulator { SDL_GameController* gameController = nullptr; int gameControllerID; + // Shows whether we've loaded any action replay codes + bool haveCheats = false; + // Variables to keep track of whether the user is controlling the 3DS analog stick with their keyboard // This is done so when a gamepad is connected, we won't automatically override the 3DS analog stick settings with the gamepad's state // And so the user can still use the keyboard to control the analog diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp new file mode 100644 index 00000000..a797d092 --- /dev/null +++ b/src/core/cheats.cpp @@ -0,0 +1,5 @@ +#include "cheats.hpp" + +ActionReplay::ActionReplay() { reset(); } + +void ActionReplay::reset() { cheats.clear(); } \ No newline at end of file diff --git a/src/emulator.cpp b/src/emulator.cpp index 8ab6e06e..75f8ab1c 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -76,6 +76,12 @@ void Emulator::reset(ReloadOption reload) { // Otherwise resetting the kernel or cpu might nuke them cpu.setReg(13, VirtualAddrs::StackTop); // Set initial SP + // We're resetting without reloading the ROM, so yeet cheats + if (reload == ReloadOption::NoReload) { + haveCheats = false; + cheats.reset(); + } + // If a ROM is active and we reset, with the reload option enabled then reload it. // This is necessary to set up stack, executable memory, .data/.rodata/.bss all over again if (reload == ReloadOption::Reload && romType != ROMType::None && romPath.has_value()) { From ae69c8f8c462c4a01b40628d7bd4b9004f8a72dc Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Wed, 19 Jul 2023 21:09:44 +0300 Subject: [PATCH 2/5] [Cheats] Split gateway and AR --- CMakeLists.txt | 3 ++- include/action_replay.hpp | 22 ++++++++++++++++++++++ include/cheats.hpp | 30 +++++++++++++++++------------- src/core/action_replay.cpp | 14 ++++++++++++++ src/core/cheats.cpp | 25 +++++++++++++++++++++++-- 5 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 include/action_replay.hpp create mode 100644 src/core/action_replay.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 867f1e81..1d9c5b07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ endif() set(SOURCE_FILES src/main.cpp src/emulator.cpp src/io_file.cpp src/config.cpp src/core/CPU/cpu_dynarmic.cpp src/core/CPU/dynarmic_cycles.cpp src/core/memory.cpp src/renderer.cpp src/core/renderer_null/renderer_null.cpp - src/httpserver.cpp src/stb_image_write.c src/core/cheats.cpp + src/httpserver.cpp src/stb_image_write.c src/core/cheats.cpp src/core/action_replay.cpp ) set(CRYPTO_SOURCE_FILES src/core/crypto/aes_engine.cpp) set(KERNEL_SOURCE_FILES src/core/kernel/kernel.cpp src/core/kernel/resource_limits.cpp @@ -150,6 +150,7 @@ set(HEADER_FILES include/emulator.hpp include/helpers.hpp include/termcolor.hpp include/result/result_gsp.hpp include/result/result_kernel.hpp include/result/result_os.hpp include/crypto/aes_engine.hpp include/metaprogramming.hpp include/PICA/pica_vertex.hpp include/config.hpp include/services/ir_user.hpp include/httpserver.hpp include/cheats.hpp + include/action_replay.hpp ) set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp diff --git a/include/action_replay.hpp b/include/action_replay.hpp new file mode 100644 index 00000000..4b5ab709 --- /dev/null +++ b/include/action_replay.hpp @@ -0,0 +1,22 @@ +#pragma once +#include +#include + +#include "helpers.hpp" + +class ActionReplay { + using Cheat = std::vector; // A cheat is really just a bunch of u32 opcodes + + u32 offset1, offset2; // Memory offset registers. Non-persistent. + u32 data1, data2; // Data offset registers. Non-persistent. + u32 storage1, storage2; // Storage registers. Persistent. + + // When an instruction does not specify which offset or data register to use, we use the "active" one + // Which is by default #1 and may be changed by certain AR operations + u32 *activeOffset, *activeData; + + public: + ActionReplay(); + void runCheat(const Cheat& cheat); + void reset(); +}; \ No newline at end of file diff --git a/include/cheats.hpp b/include/cheats.hpp index 22843544..ca69fa33 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -2,23 +2,27 @@ #include #include +#include "action_replay.hpp" #include "helpers.hpp" -class ActionReplay { - using Cheat = std::vector; // A cheat is really just a bunch of u32 opcodes - std::vector cheats; - - u32 offset1, offset2; // Memory offset registers. Non-persistent. - u32 data1, data2; // Data offset registers. Non-persistent. - u32 storage1, storage2; // Storage registers. Persistent. - - // When an instruction does not specify which offset or data register to use, we use the "active" one - // Which is by default #1 and may be changed by certain AR operations - u32 *activeOffset, *activeData; - +class Cheats { public: - ActionReplay(); + enum class CheatType { + ActionReplay, // CTRPF cheats + Gateway, + }; + + struct Cheat { + CheatType type; + std::vector instructions; + }; + + Cheats(); void addCheat(const Cheat& cheat); void runCheats(); void reset(); + + private: + ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes + std::vector cheats; }; \ No newline at end of file diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp new file mode 100644 index 00000000..293d879e --- /dev/null +++ b/src/core/action_replay.cpp @@ -0,0 +1,14 @@ +#include "action_replay.hpp" + +ActionReplay::ActionReplay() { reset(); } + +void ActionReplay::reset() { + // Default value of storage regs is 0 + storage1 = 0; + storage2 = 0; +} + +void ActionReplay::runCheat(const Cheat& cheat) { + // Set offset and data registers to 0 at the start of a cheat + data1 = data2 = offset1 = offset2 = 0; +} \ No newline at end of file diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index a797d092..9443670e 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1,5 +1,26 @@ #include "cheats.hpp" -ActionReplay::ActionReplay() { reset(); } +Cheats::Cheats() { reset(); } -void ActionReplay::reset() { cheats.clear(); } \ No newline at end of file +void Cheats::reset() { + cheats.clear(); // Unload loaded cheats + ar.reset(); // Reset AR boi +} + +void Cheats::runCheats() { + for (const Cheat& cheat : cheats) { + switch (cheat.type) { + case CheatType::ActionReplay: { + ar.runCheat(cheat.instructions); + break; + } + + case CheatType::Gateway: { + Helpers::panic("Gateway cheats not supported yet! Only Action Replay is supported!"); + break; + } + + default: Helpers::panic("Unknown cheat type"); + } + } +} \ No newline at end of file From 97f8ea6cfd24ac23341d9e19bec4b487b978161c Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 20 Jul 2023 13:50:32 +0300 Subject: [PATCH 3/5] Get our first AR code running --- include/action_replay.hpp | 25 +++++++++++++++-- include/cheats.hpp | 7 +++-- include/emulator.hpp | 2 +- src/core/action_replay.cpp | 57 +++++++++++++++++++++++++++++++++++++- src/core/cheats.cpp | 8 ++++-- src/emulator.cpp | 34 +++++++++++++---------- 6 files changed, 110 insertions(+), 23 deletions(-) diff --git a/include/action_replay.hpp b/include/action_replay.hpp index 4b5ab709..6c6a8ba2 100644 --- a/include/action_replay.hpp +++ b/include/action_replay.hpp @@ -3,9 +3,10 @@ #include #include "helpers.hpp" +#include "memory.hpp" class ActionReplay { - using Cheat = std::vector; // A cheat is really just a bunch of u32 opcodes + using Cheat = std::vector; // A cheat is really just a bunch of 64-bit opcodes neatly encoded into 32-bit chunks u32 offset1, offset2; // Memory offset registers. Non-persistent. u32 data1, data2; // Data offset registers. Non-persistent. @@ -14,9 +15,29 @@ class ActionReplay { // When an instruction does not specify which offset or data register to use, we use the "active" one // Which is by default #1 and may be changed by certain AR operations u32 *activeOffset, *activeData; + + // Program counter + u32 pc = 0; + Memory& mem; + + // Has the cheat ended? + bool running = false; + // Run 1 AR instruction + void runInstruction(const Cheat& cheat, u32 instruction); + + // Action Replay has a billion D-type opcodes so this handles all of them + void executeDType(const Cheat& cheat, u32 instruction); + + u8 read8(u32 addr); + u16 read16(u32 addr); + u32 read32(u32 addr); + + void write8(u32 addr, u8 value); + void write16(u32 addr, u16 value); + void write32(u32 addr, u32 value); public: - ActionReplay(); + ActionReplay(Memory& mem); void runCheat(const Cheat& cheat); void reset(); }; \ No newline at end of file diff --git a/include/cheats.hpp b/include/cheats.hpp index ca69fa33..d2ee8166 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -5,6 +5,9 @@ #include "action_replay.hpp" #include "helpers.hpp" +// Forward-declare this since it's just passed and we don't want to include memory.hpp and increase compile time +class Memory; + class Cheats { public: enum class CheatType { @@ -17,10 +20,10 @@ class Cheats { std::vector instructions; }; - Cheats(); + Cheats(Memory& mem); void addCheat(const Cheat& cheat); - void runCheats(); void reset(); + void run(); private: ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes diff --git a/include/emulator.hpp b/include/emulator.hpp index 187d6eca..040b93b2 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -32,7 +32,7 @@ class Emulator { Memory memory; Kernel kernel; Crypto::AESEngine aesEngine; - ActionReplay cheats; + Cheats cheats; SDL_Window* window; diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp index 293d879e..2153bd1a 100644 --- a/src/core/action_replay.cpp +++ b/src/core/action_replay.cpp @@ -1,6 +1,6 @@ #include "action_replay.hpp" -ActionReplay::ActionReplay() { reset(); } +ActionReplay::ActionReplay(Memory& mem) : mem(mem) { reset(); } void ActionReplay::reset() { // Default value of storage regs is 0 @@ -11,4 +11,59 @@ void ActionReplay::reset() { void ActionReplay::runCheat(const Cheat& cheat) { // Set offset and data registers to 0 at the start of a cheat data1 = data2 = offset1 = offset2 = 0; + pc = 0; + running = true; + + activeOffset = &offset1; + activeData = &data1; + + while (running) { + // See if we can fetch 1 64-bit opcode, otherwise we're out of bounds. Cheats seem to end when going out of bounds? + if (pc + 1 >= cheat.size()) { + return; + } + + // Fetch instruction + const u32 instruction = cheat[pc++]; + runInstruction(cheat, instruction); + } +} + +u8 ActionReplay::read8(u32 addr) { return mem.read8(addr); } +u16 ActionReplay::read16(u32 addr) { return mem.read16(addr); } +u32 ActionReplay::read32(u32 addr) { return mem.read32(addr); } + +void ActionReplay::write8(u32 addr, u8 value) { mem.write8(addr, value); } +void ActionReplay::write16(u32 addr, u16 value) { mem.write16(addr, value); } +void ActionReplay::write32(u32 addr, u32 value) { mem.write32(addr, value); } + +void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) { + // Top nibble determines the instruction type + const u32 type = instruction >> 28; + + switch (type) { + // 8 bit write to [XXXXXXX + offset] + case 0x2: { + const u32 baseAddr = Helpers::getBits<0, 28>(instruction); + const u8 value = u8(cheat[pc++]); + write8(baseAddr + *activeOffset, value); + break; + } + + // Less Than (YYYYYYYY < [XXXXXXX + offset]) + case 0x4: { + const u32 baseAddr = Helpers::getBits<0, 28>(instruction); + const u32 imm = cheat[pc++]; + const u32 value = read32(baseAddr + *activeOffset); + Helpers::panic("TODO: How do ActionReplay conditional blocks work?"); + break; + } + + case 0xD: executeDType(cheat, instruction); break; + default: Helpers::panic("Unimplemented ActionReplay instruction type %X", type); break; + } +} + +void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) { + Helpers::panic("ActionReplay: Unimplemented d-type opcode: %08X", instruction); } \ No newline at end of file diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 9443670e..fd772550 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -1,13 +1,15 @@ #include "cheats.hpp" -Cheats::Cheats() { reset(); } +Cheats::Cheats(Memory& mem) : ar(mem) { reset(); } void Cheats::reset() { cheats.clear(); // Unload loaded cheats - ar.reset(); // Reset AR boi + ar.reset(); // Reset ActionReplay } -void Cheats::runCheats() { +void Cheats::addCheat(const Cheat& cheat) { cheats.push_back(cheat); } + +void Cheats::run() { for (const Cheat& cheat : cheats) { switch (cheat.type) { case CheatType::ActionReplay: { diff --git a/src/emulator.cpp b/src/emulator.cpp index 75f8ab1c..ff9fc22f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -16,7 +16,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; Emulator::Emulator() : config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config), - memory(cpu.getTicksRef()) { + memory(cpu.getTicksRef()), cheats(memory) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { Helpers::panic("Failed to initialize SDL2"); } @@ -104,20 +104,9 @@ void Emulator::run() { #endif while (running) { + runFrame(); ServiceManager& srv = kernel.getServiceManager(); - if (romType != ROMType::None) { -#ifdef PANDA3DS_ENABLE_HTTP_SERVER - pollHttpServer(); -#endif - runFrame(); // Run 1 frame of instructions - gpu.display(); // Display graphics - - // Send VBlank interrupts - srv.sendGPUInterrupt(GPUInterrupt::VBlank0); - srv.sendGPUInterrupt(GPUInterrupt::VBlank1); - } - SDL_Event event; while (SDL_PollEvent(&event)) { namespace Keys = HID::Keys; @@ -344,7 +333,24 @@ void Emulator::run() { } } -void Emulator::runFrame() { cpu.runFrame(); } +void Emulator::runFrame() { + if (romType != ROMType::None) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + pollHttpServer(); +#endif + cpu.runFrame(); // Run 1 frame of instructions + gpu.display(); // Display graphics + + // Send VBlank interrupts + ServiceManager& srv = kernel.getServiceManager(); + srv.sendGPUInterrupt(GPUInterrupt::VBlank0); + srv.sendGPUInterrupt(GPUInterrupt::VBlank1); + + if (haveCheats) [[unlikely]] { + cheats.run(); + } + } +} bool Emulator::loadROM(const std::filesystem::path& path) { // Reset the emulator if we've already loaded a ROM From 8e45b8c324a31f7129b39b7d686ca35cf1e20f29 Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 20 Jul 2023 14:01:47 +0300 Subject: [PATCH 4/5] [ActionReplay] Add 16-bit writes --- src/core/action_replay.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp index 2153bd1a..65cc1e96 100644 --- a/src/core/action_replay.cpp +++ b/src/core/action_replay.cpp @@ -42,7 +42,16 @@ void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) { const u32 type = instruction >> 28; switch (type) { - // 8 bit write to [XXXXXXX + offset] + // 16-bit write to [XXXXXXX + offset] + case 0x1: { + const u32 baseAddr = Helpers::getBits<0, 28>(instruction); + const u16 value = u16(cheat[pc++]); + write16(baseAddr + *activeOffset, value); + break; + } + + + // 8-bit write to [XXXXXXX + offset] case 0x2: { const u32 baseAddr = Helpers::getBits<0, 28>(instruction); const u8 value = u8(cheat[pc++]); From 08596c1a24a0a9f2801250eb80dc89fb3942a1db Mon Sep 17 00:00:00 2001 From: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com> Date: Thu, 20 Jul 2023 16:10:32 +0300 Subject: [PATCH 5/5] [AR] More opcodes --- include/action_replay.hpp | 2 +- src/core/action_replay.cpp | 106 +++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/include/action_replay.hpp b/include/action_replay.hpp index 6c6a8ba2..a7cce230 100644 --- a/include/action_replay.hpp +++ b/include/action_replay.hpp @@ -14,7 +14,7 @@ class ActionReplay { // When an instruction does not specify which offset or data register to use, we use the "active" one // Which is by default #1 and may be changed by certain AR operations - u32 *activeOffset, *activeData; + u32 *activeOffset, *activeData, *activeStorage; // Program counter u32 pc = 0; diff --git a/src/core/action_replay.cpp b/src/core/action_replay.cpp index 65cc1e96..cc3f6d20 100644 --- a/src/core/action_replay.cpp +++ b/src/core/action_replay.cpp @@ -6,6 +6,9 @@ void ActionReplay::reset() { // Default value of storage regs is 0 storage1 = 0; storage2 = 0; + + // TODO: Is the active storage persistent or not? + activeStorage = &storage1; } void ActionReplay::runCheat(const Cheat& cheat) { @@ -33,15 +36,41 @@ u8 ActionReplay::read8(u32 addr) { return mem.read8(addr); } u16 ActionReplay::read16(u32 addr) { return mem.read16(addr); } u32 ActionReplay::read32(u32 addr) { return mem.read32(addr); } -void ActionReplay::write8(u32 addr, u8 value) { mem.write8(addr, value); } -void ActionReplay::write16(u32 addr, u16 value) { mem.write16(addr, value); } -void ActionReplay::write32(u32 addr, u32 value) { mem.write32(addr, value); } +// Some AR cheats seem to want to write to unmapped memory or memory that straight up does not exist + +#define MAKE_WRITE_HANDLER(size) \ + void ActionReplay::write##size(u32 addr, u##size value) { \ + auto pointerWrite = mem.getWritePointer(addr); \ + if (pointerWrite) { \ + *(u##size*)pointerWrite = value; \ + } else { \ + auto pointerRead = mem.getReadPointer(addr); \ + if (pointerRead) { \ + *(u##size*)pointerRead = value; \ + } else { \ + Helpers::warn("AR code tried to write to invalid address: %08X\n", addr); \ + } \ + } \ + } + +MAKE_WRITE_HANDLER(8) +MAKE_WRITE_HANDLER(16) +MAKE_WRITE_HANDLER(32) +#undef MAKE_WRITE_HANDLER void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) { // Top nibble determines the instruction type const u32 type = instruction >> 28; switch (type) { + // 32-bit write to [XXXXXXX + offset] + case 0x0: { + const u32 baseAddr = Helpers::getBits<0, 28>(instruction); + const u32 value = cheat[pc++]; + write32(baseAddr + *activeOffset, value); + break; + } + // 16-bit write to [XXXXXXX + offset] case 0x1: { const u32 baseAddr = Helpers::getBits<0, 28>(instruction); @@ -50,7 +79,6 @@ void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) { break; } - // 8-bit write to [XXXXXXX + offset] case 0x2: { const u32 baseAddr = Helpers::getBits<0, 28>(instruction); @@ -74,5 +102,73 @@ void ActionReplay::runInstruction(const Cheat& cheat, u32 instruction) { } void ActionReplay::executeDType(const Cheat& cheat, u32 instruction) { - Helpers::panic("ActionReplay: Unimplemented d-type opcode: %08X", instruction); + switch (instruction) { + case 0xD3000000: offset1 = cheat[pc++]; break; + case 0xD3000001: offset2 = cheat[pc++]; break; + case 0xDC000000: *activeOffset += cheat[pc++]; break; + + // DD000000 XXXXXXXX - if KEYPAD has value XXXXXXXX execute next block + case 0xDD000000: { + const u32 mask = cheat[pc++]; + break; + } + + // Offset register ops + case 0xDF000000: { + const u32 subopcode = cheat[pc++]; + switch (subopcode) { + case 0x00000000: activeOffset = &offset1; break; + case 0x00000001: activeOffset = &offset2; break; + case 0x00010000: offset2 = offset1; break; + case 0x00010001: offset1 = offset2; break; + case 0x00020000: data1 = offset1; break; + case 0x00020001: data2 = offset2; break; + default: + Helpers::warn("Unknown ActionReplay offset operation"); + running = false; + break; + } + break; + } + + // Data register operations + case 0xDF000001: { + const u32 subopcode = cheat[pc++]; + switch (subopcode) { + case 0x00000000: activeData = &data1; break; + case 0x00000001: activeData = &data2; break; + + case 0x00010000: data2 = data1; break; + case 0x00010001: data1 = data2; break; + case 0x00020000: offset1 = data1; break; + case 0x00020001: offset2 = data2; break; + default: + Helpers::warn("Unknown ActionReplay data operation"); + running = false; + break; + } + break; + } + + // Storage register operations + case 0xDF000002: { + const u32 subopcode = cheat[pc++]; + switch (subopcode) { + case 0x00000000: activeStorage = &storage1; break; + case 0x00000001: activeStorage = &storage2; break; + + case 0x00010000: data1 = storage1; break; + case 0x00010001: data2 = storage2; break; + case 0x00020000: storage1 = data1; break; + case 0x00020001: storage2 = data2; break; + default: + Helpers::warn("Unknown ActionReplay data operation"); + running = false; + break; + } + break; + } + + default: Helpers::panic("ActionReplay: Unimplemented d-type opcode: %08X", instruction); break; + } } \ No newline at end of file