Get our first AR code running

This commit is contained in:
wheremyfoodat 2023-07-20 13:50:32 +03:00
parent ae69c8f8c4
commit 97f8ea6cfd
6 changed files with 110 additions and 23 deletions

View file

@ -3,9 +3,10 @@
#include <vector> #include <vector>
#include "helpers.hpp" #include "helpers.hpp"
#include "memory.hpp"
class ActionReplay { class ActionReplay {
using Cheat = std::vector<u32>; // A cheat is really just a bunch of u32 opcodes 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 offset1, offset2; // Memory offset registers. Non-persistent.
u32 data1, data2; // Data 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 // 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 // Which is by default #1 and may be changed by certain AR operations
u32 *activeOffset, *activeData; 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: public:
ActionReplay(); ActionReplay(Memory& mem);
void runCheat(const Cheat& cheat); void runCheat(const Cheat& cheat);
void reset(); void reset();
}; };

View file

@ -5,6 +5,9 @@
#include "action_replay.hpp" #include "action_replay.hpp"
#include "helpers.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 { class Cheats {
public: public:
enum class CheatType { enum class CheatType {
@ -17,10 +20,10 @@ class Cheats {
std::vector<u32> instructions; std::vector<u32> instructions;
}; };
Cheats(); Cheats(Memory& mem);
void addCheat(const Cheat& cheat); void addCheat(const Cheat& cheat);
void runCheats();
void reset(); void reset();
void run();
private: private:
ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes

View file

@ -32,7 +32,7 @@ class Emulator {
Memory memory; Memory memory;
Kernel kernel; Kernel kernel;
Crypto::AESEngine aesEngine; Crypto::AESEngine aesEngine;
ActionReplay cheats; Cheats cheats;
SDL_Window* window; SDL_Window* window;

View file

@ -1,6 +1,6 @@
#include "action_replay.hpp" #include "action_replay.hpp"
ActionReplay::ActionReplay() { reset(); } ActionReplay::ActionReplay(Memory& mem) : mem(mem) { reset(); }
void ActionReplay::reset() { void ActionReplay::reset() {
// Default value of storage regs is 0 // Default value of storage regs is 0
@ -11,4 +11,59 @@ void ActionReplay::reset() {
void ActionReplay::runCheat(const Cheat& cheat) { void ActionReplay::runCheat(const Cheat& cheat) {
// Set offset and data registers to 0 at the start of a cheat // Set offset and data registers to 0 at the start of a cheat
data1 = data2 = offset1 = offset2 = 0; 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);
} }

View file

@ -1,13 +1,15 @@
#include "cheats.hpp" #include "cheats.hpp"
Cheats::Cheats() { reset(); } Cheats::Cheats(Memory& mem) : ar(mem) { reset(); }
void Cheats::reset() { void Cheats::reset() {
cheats.clear(); // Unload loaded cheats 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) { for (const Cheat& cheat : cheats) {
switch (cheat.type) { switch (cheat.type) {
case CheatType::ActionReplay: { case CheatType::ActionReplay: {

View file

@ -16,7 +16,7 @@ __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
Emulator::Emulator() Emulator::Emulator()
: config(std::filesystem::current_path() / "config.toml"), kernel(cpu, memory, gpu), cpu(memory, kernel), gpu(memory, config), : 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) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) {
Helpers::panic("Failed to initialize SDL2"); Helpers::panic("Failed to initialize SDL2");
} }
@ -104,20 +104,9 @@ void Emulator::run() {
#endif #endif
while (running) { while (running) {
runFrame();
ServiceManager& srv = kernel.getServiceManager(); 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; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
namespace Keys = HID::Keys; 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) { bool Emulator::loadROM(const std::filesystem::path& path) {
// Reset the emulator if we've already loaded a ROM // Reset the emulator if we've already loaded a ROM