From 573036b38eb963fdbe37896666d771492467354c Mon Sep 17 00:00:00 2001 From: zhupengfei Date: Wed, 30 Jan 2019 23:03:44 +0800 Subject: [PATCH] core/cheats: Add and change a few functions Added a few interfaces for adding/deleting/replacing/saving cheats. The cheats list is guarded by a std::shared_mutex, and would only need a exclusive lock when it's being updated. I marked the `Execute` function as `const` to avoid accidentally changing the internal state of the cheat on execution, so that execution can be considered a "read" operation which only needs a shared lock. Whether a cheat is enabled or not is now saved by a special comment line `*citra_enabled`. --- src/core/cheats/cheat_base.h | 3 +- src/core/cheats/cheats.cpp | 62 ++++++++++++++++++++++++++++--- src/core/cheats/cheats.h | 10 ++++- src/core/cheats/gateway_cheat.cpp | 51 ++++++++++++++++++++++--- src/core/cheats/gateway_cheat.h | 7 +++- 5 files changed, 117 insertions(+), 16 deletions(-) diff --git a/src/core/cheats/cheat_base.h b/src/core/cheats/cheat_base.h index ee64a047f..d8a5924cd 100644 --- a/src/core/cheats/cheat_base.h +++ b/src/core/cheats/cheat_base.h @@ -14,7 +14,7 @@ namespace Cheats { class CheatBase { public: virtual ~CheatBase(); - virtual void Execute(Core::System& system) = 0; + virtual void Execute(Core::System& system) const = 0; virtual bool IsEnabled() const = 0; virtual void SetEnabled(bool enabled) = 0; @@ -22,6 +22,7 @@ public: virtual std::string GetComments() const = 0; virtual std::string GetName() const = 0; virtual std::string GetType() const = 0; + virtual std::string GetCode() const = 0; virtual std::string ToString() const = 0; }; diff --git a/src/core/cheats/cheats.cpp b/src/core/cheats/cheats.cpp index 612d1b5d9..8b4a30ca6 100644 --- a/src/core/cheats/cheats.cpp +++ b/src/core/cheats/cheats.cpp @@ -2,8 +2,10 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include #include +#include "common/file_util.h" #include "core/cheats/cheats.h" #include "core/cheats/gateway_cheat.h" #include "core/core.h" @@ -26,10 +28,54 @@ CheatEngine::~CheatEngine() { system.CoreTiming().UnscheduleEvent(event, 0); } -const std::vector>& CheatEngine::GetCheats() const { +std::vector> CheatEngine::GetCheats() const { + std::shared_lock lock(cheats_list_mutex); return cheats_list; } +void CheatEngine::AddCheat(const std::shared_ptr& cheat) { + std::unique_lock lock(cheats_list_mutex); + cheats_list.push_back(cheat); +} + +void CheatEngine::RemoveCheat(int index) { + std::unique_lock lock(cheats_list_mutex); + if (index < 0 || index >= cheats_list.size()) { + LOG_ERROR(Core_Cheats, "Invalid index {}", index); + return; + } + cheats_list.erase(cheats_list.begin() + index); +} + +void CheatEngine::UpdateCheat(int index, const std::shared_ptr& new_cheat) { + std::unique_lock lock(cheats_list_mutex); + if (index < 0 || index >= cheats_list.size()) { + LOG_ERROR(Core_Cheats, "Invalid index {}", index); + return; + } + cheats_list[index] = new_cheat; +} + +void CheatEngine::SaveCheatFile() const { + const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); + const std::string filepath = fmt::format( + "{}{:016X}.txt", cheat_dir, system.Kernel().GetCurrentProcess()->codeset->program_id); + + if (!FileUtil::IsDirectory(cheat_dir)) { + FileUtil::CreateDir(cheat_dir); + } + + std::ofstream file; + OpenFStream(file, filepath, std::ios_base::out); + + auto cheats = GetCheats(); + for (const auto& cheat : cheats) { + file << cheat->ToString(); + } + + file.flush(); +} + void CheatEngine::LoadCheatFile() { const std::string cheat_dir = FileUtil::GetUserPath(FileUtil::UserPath::CheatsDir); const std::string filepath = fmt::format( @@ -43,13 +89,19 @@ void CheatEngine::LoadCheatFile() { return; auto gateway_cheats = GatewayCheat::LoadFile(filepath); - std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); + { + std::unique_lock lock(cheats_list_mutex); + std::move(gateway_cheats.begin(), gateway_cheats.end(), std::back_inserter(cheats_list)); + } } void CheatEngine::RunCallback([[maybe_unused]] u64 userdata, int cycles_late) { - for (auto& cheat : cheats_list) { - if (cheat->IsEnabled()) { - cheat->Execute(system); + { + std::shared_lock lock(cheats_list_mutex); + for (auto& cheat : cheats_list) { + if (cheat->IsEnabled()) { + cheat->Execute(system); + } } } system.CoreTiming().ScheduleEvent(run_interval_ticks - cycles_late, event); diff --git a/src/core/cheats/cheats.h b/src/core/cheats/cheats.h index 7e838de29..a8d373038 100644 --- a/src/core/cheats/cheats.h +++ b/src/core/cheats/cheats.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include "common/common_types.h" @@ -25,12 +26,17 @@ class CheatEngine { public: explicit CheatEngine(Core::System& system); ~CheatEngine(); - const std::vector>& GetCheats() const; + std::vector> GetCheats() const; + void AddCheat(const std::shared_ptr& cheat); + void RemoveCheat(int index); + void UpdateCheat(int index, const std::shared_ptr& new_cheat); + void SaveCheatFile() const; private: void LoadCheatFile(); void RunCallback(u64 userdata, int cycles_late); - std::vector> cheats_list; + std::vector> cheats_list; + mutable std::shared_mutex cheats_list_mutex; Core::TimingEventType* event; Core::System& system; }; diff --git a/src/core/cheats/gateway_cheat.cpp b/src/core/cheats/gateway_cheat.cpp index af7e0f8cf..15a0e8ca9 100644 --- a/src/core/cheats/gateway_cheat.cpp +++ b/src/core/cheats/gateway_cheat.cpp @@ -176,6 +176,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { type = CheatType::Null; cheat_line = line; LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); + valid = false; return; } try { @@ -193,6 +194,7 @@ GatewayCheat::CheatLine::CheatLine(const std::string& line) { type = CheatType::Null; cheat_line = line; LOG_ERROR(Core_Cheats, "Cheat contains invalid line: {}", line); + valid = false; } } @@ -201,9 +203,23 @@ GatewayCheat::GatewayCheat(std::string name_, std::vector cheat_lines : name(std::move(name_)), cheat_lines(std::move(cheat_lines_)), comments(std::move(comments_)) { } +GatewayCheat::GatewayCheat(std::string name_, std::string code, std::string comments_) + : name(std::move(name_)), comments(std::move(comments_)) { + + std::vector code_lines; + Common::SplitString(code, '\n', code_lines); + + std::vector temp_cheat_lines; + for (std::size_t i = 0; i < code_lines.size(); ++i) { + if (!code_lines[i].empty()) + temp_cheat_lines.emplace_back(code_lines[i]); + } + cheat_lines = std::move(temp_cheat_lines); +} + GatewayCheat::~GatewayCheat() = default; -void GatewayCheat::Execute(Core::System& system) { +void GatewayCheat::Execute(Core::System& system) const { State state; Memory::MemorySystem& memory = system.Memory(); @@ -421,13 +437,28 @@ std::string GatewayCheat::GetType() const { return "Gateway"; } +std::string GatewayCheat::GetCode() const { + std::string result; + for (const auto& line : cheat_lines) + result += line.cheat_line + '\n'; + return result; +} + +/// A special marker used to keep track of enabled cheats +static constexpr char EnabledText[] = "*citra_enabled"; + std::string GatewayCheat::ToString() const { std::string result; result += '[' + name + "]\n"; - result += comments + '\n'; - for (const auto& line : cheat_lines) - result += line.cheat_line + '\n'; - result += '\n'; + if (enabled) { + result += EnabledText; + result += '\n'; + } + std::vector comment_lines; + Common::SplitString(comments, '\n', comment_lines); + for (const auto& comment_line : comment_lines) + result += "*" + comment_line + '\n'; + result += GetCode() + '\n'; return result; } @@ -443,6 +474,7 @@ std::vector> GatewayCheat::LoadFile(const std::string std::string comments; std::vector cheat_lines; std::string name; + bool enabled = false; while (!file.eof()) { std::string line; @@ -452,18 +484,25 @@ std::vector> GatewayCheat::LoadFile(const std::string if (line.length() >= 2 && line.front() == '[') { if (!cheat_lines.empty()) { cheats.push_back(std::make_unique(name, cheat_lines, comments)); + cheats.back()->SetEnabled(enabled); + enabled = false; } name = line.substr(1, line.length() - 2); cheat_lines.clear(); comments.erase(); } else if (!line.empty() && line.front() == '*') { - comments += line.substr(1, line.length() - 1) + '\n'; + if (line == EnabledText) { + enabled = true; + } else { + comments += line.substr(1, line.length() - 1) + '\n'; + } } else if (!line.empty()) { cheat_lines.emplace_back(std::move(line)); } } if (!cheat_lines.empty()) { cheats.push_back(std::make_unique(name, cheat_lines, comments)); + cheats.back()->SetEnabled(enabled); } return cheats; } diff --git a/src/core/cheats/gateway_cheat.h b/src/core/cheats/gateway_cheat.h index 5d7a8fcf9..46512fb96 100644 --- a/src/core/cheats/gateway_cheat.h +++ b/src/core/cheats/gateway_cheat.h @@ -50,12 +50,14 @@ public: u32 value; u32 first; std::string cheat_line; + bool valid = true; }; GatewayCheat(std::string name, std::vector cheat_lines, std::string comments); + GatewayCheat(std::string name, std::string code, std::string comments); ~GatewayCheat(); - void Execute(Core::System& system) override; + void Execute(Core::System& system) const override; bool IsEnabled() const override; void SetEnabled(bool enabled) override; @@ -63,6 +65,7 @@ public: std::string GetComments() const override; std::string GetName() const override; std::string GetType() const override; + std::string GetCode() const override; std::string ToString() const override; /// Gateway cheats look like: @@ -77,7 +80,7 @@ public: private: std::atomic enabled = false; const std::string name; - const std::vector cheat_lines; + std::vector cheat_lines; const std::string comments; }; } // namespace Cheats