diff --git a/include/cheats.hpp b/include/cheats.hpp index c8d7c763..2be25827 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -12,17 +12,21 @@ class Memory; class Cheats { public: enum class CheatType { + None, // Cheat has been removed by the frontend or is invalid ActionReplay, // CTRPF cheats - // TODO: Other cheat devices and standards? }; struct Cheat { - CheatType type; + bool enabled = true; + CheatType type = CheatType::ActionReplay; std::vector instructions; }; Cheats(Memory& mem, HIDService& hid); - void addCheat(const Cheat& cheat); + u32 addCheat(const Cheat& cheat); + void removeCheat(u32 id); + void enableCheat(u32 id); + void disableCheat(u32 id); void reset(); void run(); @@ -33,4 +37,4 @@ class Cheats { ActionReplay ar; // An ActionReplay cheat machine for executing CTRPF codes std::vector cheats; bool cheatsLoaded = false; -}; \ No newline at end of file +}; diff --git a/include/emulator.hpp b/include/emulator.hpp index 121c6b8d..df930373 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -115,6 +115,7 @@ class Emulator { void deinitGraphicsContext() { gpu.deinitGraphicsContext(); } EmulatorConfig& getConfig() { return config; } + Cheats& getCheats() { return cheats; } ServiceManager& getServiceManager() { return kernel.getServiceManager(); } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp index 618460c5..83e7cdc4 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -7,9 +7,49 @@ void Cheats::reset() { ar.reset(); // Reset ActionReplay } -void Cheats::addCheat(const Cheat& cheat) { - cheats.push_back(cheat); +u32 Cheats::addCheat(const Cheat& cheat) { cheatsLoaded = true; + + // Find an empty slot if a cheat was previously removed + for (size_t i = 0; i < cheats.size(); i++) { + if (cheats[i].type == CheatType::None) { + cheats[i] = cheat; + return i; + } + } + + // Otherwise, just add a new slot + cheats.push_back(cheat); + return cheats.size() - 1; +} + +void Cheats::removeCheat(u32 id) { + if (id >= cheats.size()) { + return; + } + + // Not using std::erase because we don't want to invalidate cheat IDs + cheats[id].type = CheatType::None; + cheats[id].instructions.clear(); + + // Check if no cheats are loaded + for (const auto& cheat : cheats) { + if (cheat.type != CheatType::None) return; + } + + cheatsLoaded = false; +} + +void Cheats::enableCheat(u32 id) { + if (id < cheats.size()) { + cheats[id].enabled = true; + } +} + +void Cheats::disableCheat(u32 id) { + if (id < cheats.size()) { + cheats[id].enabled = false; + } } void Cheats::clear() { @@ -19,12 +59,15 @@ void Cheats::clear() { void Cheats::run() { for (const Cheat& cheat : cheats) { + if (!cheat.enabled) continue; + switch (cheat.type) { case CheatType::ActionReplay: { ar.runCheat(cheat.instructions); break; } + case CheatType::None: break; default: Helpers::panic("Unknown cheat device!"); } } diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 3ecf7019..d67ffe2f 100644 --- a/src/hydra_core.cpp +++ b/src/hydra_core.cpp @@ -4,8 +4,13 @@ #include #include "hydra_icon.hpp" +#include "swap.hpp" -class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRendered, public hydra::IFrontendDriven, public hydra::IInput { +class HC_GLOBAL HydraCore final : public hydra::IBase, + public hydra::IOpenGlRendered, + public hydra::IFrontendDriven, + public hydra::IInput, + public hydra::ICheat { HYDRA_CLASS public: HydraCore(); @@ -25,11 +30,17 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend // IFrontendDriven void runFrame() override; - uint16_t getFps() override; + u16 getFps() override; // IInput void setPollInputCallback(void (*callback)()) override; - void setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) override; + void setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) override; + + // ICheat + u32 addCheat(const u8* data, u32 size) override; + void removeCheat(u32 id) override; + void enableCheat(u32 id) override; + void disableCheat(u32 id) override; std::unique_ptr emulator; RendererGL* renderer; @@ -93,7 +104,7 @@ void HydraCore::runFrame() { emulator->runFrame(); } -uint16_t HydraCore::getFps() { return 60; } +u16 HydraCore::getFps() { return 60; } void HydraCore::reset() { emulator->reset(Emulator::ReloadOption::Reload); } hydra::Size HydraCore::getNativeSize() { return {400, 480}; } @@ -120,7 +131,33 @@ void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } void HydraCore::setGetProcAddress(void* function) { getProcAddress = function; } void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } -void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } +void HydraCore::setCheckButtonCallback(s32 (*callback)(u32 player, hydra::ButtonType button)) { checkButtonCallback = callback; } + +u32 HydraCore::addCheat(const u8* data, u32 size) { + // Every 3DS cheat is a multiple of 64 bits == 8 bytes + if ((size % 8) != 0) { + return hydra::BAD_CHEAT; + } + + Cheats::Cheat cheat; + cheat.enabled = true; + cheat.type = Cheats::CheatType::ActionReplay; + + for (u32 i = 0; i < size; i += 8) { + auto read32 = [](const u8* ptr) { return (u32(ptr[3]) << 24) | (u32(ptr[2]) << 16) | (u32(ptr[1]) << 8) | u32(ptr[0]); }; + + // Data is passed to us in big endian so we bswap + u32 firstWord = Common::swap32(read32(data + i)); + u32 secondWord = Common::swap32(read32(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {firstWord, secondWord}); + } + + return emulator->getCheats().addCheat(cheat); +}; + +void HydraCore::removeCheat(u32 id) { emulator->getCheats().removeCheat(id); } +void HydraCore::enableCheat(u32 id) { emulator->getCheats().enableCheat(id); } +void HydraCore::disableCheat(u32 id) { emulator->getCheats().disableCheat(id); } HC_API hydra::IBase* createEmulator() { return new HydraCore(); } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; }