diff --git a/include/cheats.hpp b/include/cheats.hpp index c8d7c7630..d995a883c 100644 --- a/include/cheats.hpp +++ b/include/cheats.hpp @@ -12,17 +12,22 @@ class Memory; class Cheats { public: enum class CheatType { + None, ActionReplay, // CTRPF cheats // TODO: Other cheat devices and standards? }; struct Cheat { + bool enabled; CheatType type; std::vector instructions; }; Cheats(Memory& mem, HIDService& hid); - void addCheat(const Cheat& cheat); + uint32_t addCheat(const Cheat& cheat); + void removeCheat(uint32_t id); + void enableCheat(uint32_t id); + void disableCheat(uint32_t id); void reset(); void run(); diff --git a/include/emulator.hpp b/include/emulator.hpp index a5b1d7689..803c8bd71 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -114,6 +114,7 @@ class Emulator { void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } 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 618460c5c..6841cdce6 100644 --- a/src/core/cheats.cpp +++ b/src/core/cheats.cpp @@ -7,9 +7,45 @@ void Cheats::reset() { ar.reset(); // Reset ActionReplay } -void Cheats::addCheat(const Cheat& cheat) { - cheats.push_back(cheat); +uint32_t 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(uint32_t 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(uint32_t id) { + if (id >= cheats.size()) return; + cheats[id].enabled = true; +} + +void Cheats::disableCheat(uint32_t id) { + if (id >= cheats.size()) return; + cheats[id].enabled = false; } void Cheats::clear() { @@ -19,6 +55,8 @@ 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); diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp index 3c809dc34..61feba33c 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(); @@ -30,6 +35,12 @@ class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRend void setPollInputCallback(void (*callback)()) override; void setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) override; + // ICheat + uint32_t addCheat(const uint8_t* data, uint32_t size) override; + void removeCheat(uint32_t id) override; + void enableCheat(uint32_t id) override; + void disableCheat(uint32_t id) override; + std::unique_ptr emulator; RendererGL* renderer; void (*pollInputCallback)() = nullptr; @@ -120,6 +131,28 @@ void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } +uint32_t HydraCore::addCheat(const uint8_t* data, uint32_t size) { + if ((size % 8) != 0) return hydra::BAD_CHEAT; + + Cheats::Cheat cheat; + cheat.enabled = true; + cheat.type = Cheats::CheatType::ActionReplay; + + for (uint32_t i = 0; i < size; i += 8) { + uint32_t first_word = Common::swap32(*reinterpret_cast(data + i)); + uint32_t second_word = Common::swap32(*reinterpret_cast(data + i + 4)); + cheat.instructions.insert(cheat.instructions.end(), {first_word, second_word}); + } + + return emulator->getCheats().addCheat(cheat); +}; + +void HydraCore::removeCheat(uint32_t id) { emulator->getCheats().removeCheat(id); } + +void HydraCore::enableCheat(uint32_t id) { emulator->getCheats().enableCheat(id); } + +void HydraCore::disableCheat(uint32_t id) { emulator->getCheats().disableCheat(id); } + HC_API hydra::IBase* createEmulator() { return new HydraCore; } HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } diff --git a/third_party/hydra_core b/third_party/hydra_core index e4cc6b0fc..0bd9d47fb 160000 --- a/third_party/hydra_core +++ b/third_party/hydra_core @@ -1 +1 @@ -Subproject commit e4cc6b0fc224583e509bc3472a4c11eafb69c041 +Subproject commit 0bd9d47fb7022b35769eda513f979689de1e4193