Merge pull request #129 from wheremyfoodat/cheats

Add preliminary cheat support
This commit is contained in:
wheremyfoodat 2023-07-20 16:28:22 +03:00 committed by GitHub
commit 6dad0f05d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 310 additions and 16 deletions

174
src/core/action_replay.cpp Normal file
View 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
View 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");
}
}
}

View file

@ -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