mirror of
https://github.com/wheremyfoodat/Panda3DS.git
synced 2025-04-08 23:25:40 +12:00
Merge pull request #129 from wheremyfoodat/cheats
Add preliminary cheat support
This commit is contained in:
commit
6dad0f05d1
7 changed files with 310 additions and 16 deletions
|
@ -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 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
|
||||
|
@ -149,7 +149,8 @@ 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
|
||||
include/action_replay.hpp
|
||||
)
|
||||
|
||||
set(THIRD_PARTY_SOURCE_FILES third_party/imgui/imgui.cpp
|
||||
|
|
43
include/action_replay.hpp
Normal file
43
include/action_replay.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
class ActionReplay {
|
||||
using Cheat = std::vector<u32>; // 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.
|
||||
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, *activeStorage;
|
||||
|
||||
// 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(Memory& mem);
|
||||
void runCheat(const Cheat& cheat);
|
||||
void reset();
|
||||
};
|
31
include/cheats.hpp
Normal file
31
include/cheats.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
#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 {
|
||||
ActionReplay, // CTRPF cheats
|
||||
Gateway,
|
||||
};
|
||||
|
||||
struct Cheat {
|
||||
CheatType type;
|
||||
std::vector<u32> instructions;
|
||||
};
|
||||
|
||||
Cheats(Memory& mem);
|
||||
void addCheat(const Cheat& cheat);
|
||||
void reset();
|
||||
void run();
|
||||
|
||||
private:
|
||||
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes
|
||||
std::vector<Cheat> cheats;
|
||||
};
|
|
@ -7,6 +7,7 @@
|
|||
#include <optional>
|
||||
|
||||
#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;
|
||||
Cheats 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
|
||||
|
|
174
src/core/action_replay.cpp
Normal file
174
src/core/action_replay.cpp
Normal file
|
@ -0,0 +1,174 @@
|
|||
#include "action_replay.hpp"
|
||||
|
||||
ActionReplay::ActionReplay(Memory& mem) : mem(mem) { reset(); }
|
||||
|
||||
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) {
|
||||
// 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); }
|
||||
|
||||
// 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);
|
||||
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++]);
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
}
|
28
src/core/cheats.cpp
Normal file
28
src/core/cheats.cpp
Normal file
|
@ -0,0 +1,28 @@
|
|||
#include "cheats.hpp"
|
||||
|
||||
Cheats::Cheats(Memory& mem) : ar(mem) { reset(); }
|
||||
|
||||
void Cheats::reset() {
|
||||
cheats.clear(); // Unload loaded cheats
|
||||
ar.reset(); // Reset ActionReplay
|
||||
}
|
||||
|
||||
void Cheats::addCheat(const Cheat& cheat) { cheats.push_back(cheat); }
|
||||
|
||||
void Cheats::run() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -98,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;
|
||||
|
@ -338,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
|
||||
|
|
Loading…
Add table
Reference in a new issue