From 573036b38eb963fdbe37896666d771492467354c Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
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 <fstream>
 #include <functional>
 #include <fmt/format.h>
+#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<std::unique_ptr<CheatBase>>& CheatEngine::GetCheats() const {
+std::vector<std::shared_ptr<CheatBase>> CheatEngine::GetCheats() const {
+    std::shared_lock<std::shared_mutex> lock(cheats_list_mutex);
     return cheats_list;
 }
 
+void CheatEngine::AddCheat(const std::shared_ptr<CheatBase>& cheat) {
+    std::unique_lock<std::shared_mutex> lock(cheats_list_mutex);
+    cheats_list.push_back(cheat);
+}
+
+void CheatEngine::RemoveCheat(int index) {
+    std::unique_lock<std::shared_mutex> 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<CheatBase>& new_cheat) {
+    std::unique_lock<std::shared_mutex> 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<std::shared_mutex> 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<std::shared_mutex> 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 <memory>
+#include <shared_mutex>
 #include <vector>
 #include "common/common_types.h"
 
@@ -25,12 +26,17 @@ class CheatEngine {
 public:
     explicit CheatEngine(Core::System& system);
     ~CheatEngine();
-    const std::vector<std::unique_ptr<CheatBase>>& GetCheats() const;
+    std::vector<std::shared_ptr<CheatBase>> GetCheats() const;
+    void AddCheat(const std::shared_ptr<CheatBase>& cheat);
+    void RemoveCheat(int index);
+    void UpdateCheat(int index, const std::shared_ptr<CheatBase>& new_cheat);
+    void SaveCheatFile() const;
 
 private:
     void LoadCheatFile();
     void RunCallback(u64 userdata, int cycles_late);
-    std::vector<std::unique_ptr<CheatBase>> cheats_list;
+    std::vector<std::shared_ptr<CheatBase>> 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<CheatLine> 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<std::string> code_lines;
+    Common::SplitString(code, '\n', code_lines);
+
+    std::vector<CheatLine> 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<std::string> 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<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string
     std::string comments;
     std::vector<CheatLine> cheat_lines;
     std::string name;
+    bool enabled = false;
 
     while (!file.eof()) {
         std::string line;
@@ -452,18 +484,25 @@ std::vector<std::unique_ptr<CheatBase>> GatewayCheat::LoadFile(const std::string
         if (line.length() >= 2 && line.front() == '[') {
             if (!cheat_lines.empty()) {
                 cheats.push_back(std::make_unique<GatewayCheat>(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<GatewayCheat>(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<CheatLine> 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<bool> enabled = false;
     const std::string name;
-    const std::vector<CheatLine> cheat_lines;
+    std::vector<CheatLine> cheat_lines;
     const std::string comments;
 };
 } // namespace Cheats