diff options
Diffstat (limited to 'src/core/memory')
| -rw-r--r-- | src/core/memory/cheat_engine.cpp | 234 | ||||
| -rw-r--r-- | src/core/memory/cheat_engine.h | 86 |
2 files changed, 320 insertions, 0 deletions
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp new file mode 100644 index 000000000..ea5c76fc0 --- /dev/null +++ b/src/core/memory/cheat_engine.cpp | |||
| @@ -0,0 +1,234 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <locale> | ||
| 6 | #include "common/hex_util.h" | ||
| 7 | #include "common/microprofile.h" | ||
| 8 | #include "common/swap.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | #include "core/core_timing.h" | ||
| 11 | #include "core/core_timing_util.h" | ||
| 12 | #include "core/hle/kernel/process.h" | ||
| 13 | #include "core/hle/service/hid/controllers/npad.h" | ||
| 14 | #include "core/hle/service/hid/hid.h" | ||
| 15 | #include "core/hle/service/sm/sm.h" | ||
| 16 | #include "core/memory/cheat_engine.h" | ||
| 17 | |||
| 18 | namespace Memory { | ||
| 19 | |||
| 20 | constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12); | ||
| 21 | constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; | ||
| 22 | |||
| 23 | StandardVmCallbacks::StandardVmCallbacks(const Core::System& system, | ||
| 24 | const CheatProcessMetadata& metadata) | ||
| 25 | : system(system), metadata(metadata) {} | ||
| 26 | |||
| 27 | StandardVmCallbacks::~StandardVmCallbacks() = default; | ||
| 28 | |||
| 29 | void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) { | ||
| 30 | ReadBlock(SanitizeAddress(address), data, size); | ||
| 31 | } | ||
| 32 | |||
| 33 | void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) { | ||
| 34 | WriteBlock(SanitizeAddress(address), data, size); | ||
| 35 | } | ||
| 36 | |||
| 37 | u64 StandardVmCallbacks::HidKeysDown() { | ||
| 38 | const auto applet_resource = | ||
| 39 | system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); | ||
| 40 | if (applet_resource == nullptr) { | ||
| 41 | LOG_WARNING(CheatEngine, | ||
| 42 | "Attempted to read input state, but applet resource is not initialized!"); | ||
| 43 | return false; | ||
| 44 | } | ||
| 45 | |||
| 46 | const auto press_state = | ||
| 47 | applet_resource | ||
| 48 | ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) | ||
| 49 | .GetAndResetPressState(); | ||
| 50 | return press_state & KEYPAD_BITMASK; | ||
| 51 | } | ||
| 52 | |||
| 53 | void StandardVmCallbacks::DebugLog(u8 id, u64 value) { | ||
| 54 | LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); | ||
| 55 | } | ||
| 56 | |||
| 57 | void StandardVmCallbacks::CommandLog(std::string_view data) { | ||
| 58 | LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", | ||
| 59 | data.back() == '\n' ? data.substr(0, data.size() - 1) : data); | ||
| 60 | } | ||
| 61 | |||
| 62 | VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const { | ||
| 63 | if ((in < metadata.main_nso_extents.base || | ||
| 64 | in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && | ||
| 65 | (in < metadata.heap_extents.base || | ||
| 66 | in >= metadata.heap_extents.base + metadata.heap_extents.size)) { | ||
| 67 | LOG_ERROR(CheatEngine, | ||
| 68 | "Cheat attempting to access memory at invalid address={:016X}, if this " | ||
| 69 | "persists, " | ||
| 70 | "the cheat may be incorrect. However, this may be normal early in execution if " | ||
| 71 | "the game has not properly set up yet.", | ||
| 72 | in); | ||
| 73 | return 0; ///< Invalid addresses will hard crash | ||
| 74 | } | ||
| 75 | |||
| 76 | return in; | ||
| 77 | } | ||
| 78 | |||
| 79 | CheatParser::~CheatParser() = default; | ||
| 80 | |||
| 81 | TextCheatParser::~TextCheatParser() = default; | ||
| 82 | |||
| 83 | namespace { | ||
| 84 | template <char match> | ||
| 85 | std::string_view ExtractName(std::string_view data, std::size_t start_index) { | ||
| 86 | auto end_index = start_index; | ||
| 87 | while (data[end_index] != match) { | ||
| 88 | ++end_index; | ||
| 89 | if (end_index > data.size() || | ||
| 90 | (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { | ||
| 91 | return {}; | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | return data.substr(start_index, end_index - start_index); | ||
| 96 | } | ||
| 97 | } // Anonymous namespace | ||
| 98 | |||
| 99 | std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system, | ||
| 100 | std::string_view data) const { | ||
| 101 | std::vector<CheatEntry> out(1); | ||
| 102 | std::optional<u64> current_entry = std::nullopt; | ||
| 103 | |||
| 104 | for (std::size_t i = 0; i < data.size(); ++i) { | ||
| 105 | if (std::isspace(data[i])) { | ||
| 106 | continue; | ||
| 107 | } | ||
| 108 | |||
| 109 | if (data[i] == '{') { | ||
| 110 | current_entry = 0; | ||
| 111 | |||
| 112 | if (out[*current_entry].definition.num_opcodes > 0) { | ||
| 113 | return {}; | ||
| 114 | } | ||
| 115 | |||
| 116 | const auto name = ExtractName<'}'>(data, i + 1); | ||
| 117 | if (name.empty()) { | ||
| 118 | return {}; | ||
| 119 | } | ||
| 120 | |||
| 121 | std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), | ||
| 122 | std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), | ||
| 123 | name.size())); | ||
| 124 | out[*current_entry] | ||
| 125 | .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = | ||
| 126 | '\0'; | ||
| 127 | |||
| 128 | i += name.length() + 1; | ||
| 129 | } else if (data[i] == '[') { | ||
| 130 | current_entry = out.size(); | ||
| 131 | out.emplace_back(); | ||
| 132 | |||
| 133 | const auto name = ExtractName<']'>(data, i + 1); | ||
| 134 | if (name.empty()) { | ||
| 135 | return {}; | ||
| 136 | } | ||
| 137 | |||
| 138 | std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), | ||
| 139 | std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), | ||
| 140 | name.size())); | ||
| 141 | out[*current_entry] | ||
| 142 | .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = | ||
| 143 | '\0'; | ||
| 144 | |||
| 145 | i += name.length() + 1; | ||
| 146 | } else if (std::isxdigit(data[i])) { | ||
| 147 | if (!current_entry || out[*current_entry].definition.num_opcodes >= | ||
| 148 | out[*current_entry].definition.opcodes.size()) { | ||
| 149 | return {}; | ||
| 150 | } | ||
| 151 | |||
| 152 | const auto hex = std::string(data.substr(i, 8)); | ||
| 153 | if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { | ||
| 154 | return {}; | ||
| 155 | } | ||
| 156 | |||
| 157 | out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = | ||
| 158 | std::stoul(hex, nullptr, 0x10); | ||
| 159 | |||
| 160 | i += 8; | ||
| 161 | } else { | ||
| 162 | return {}; | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | out[0].enabled = out[0].definition.num_opcodes > 0; | ||
| 167 | out[0].cheat_id = 0; | ||
| 168 | |||
| 169 | for (u32 i = 1; i < out.size(); ++i) { | ||
| 170 | out[i].enabled = out[i].definition.num_opcodes > 0; | ||
| 171 | out[i].cheat_id = i; | ||
| 172 | } | ||
| 173 | |||
| 174 | return out; | ||
| 175 | } | ||
| 176 | |||
| 177 | CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats, | ||
| 178 | const std::array<u8, 0x20>& build_id) | ||
| 179 | : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>( | ||
| 180 | system, metadata)}, | ||
| 181 | cheats(std::move(cheats)) { | ||
| 182 | metadata.main_nso_build_id = build_id; | ||
| 183 | } | ||
| 184 | |||
| 185 | CheatEngine::~CheatEngine() { | ||
| 186 | core_timing.UnscheduleEvent(event, 0); | ||
| 187 | } | ||
| 188 | |||
| 189 | void CheatEngine::Initialize() { | ||
| 190 | event = core_timing.RegisterEvent( | ||
| 191 | "CheatEngine::FrameCallback::" + Common::HexArrayToString(metadata.main_nso_build_id), | ||
| 192 | [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); | ||
| 193 | core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); | ||
| 194 | |||
| 195 | metadata.process_id = system.CurrentProcess()->GetProcessID(); | ||
| 196 | metadata.title_id = system.CurrentProcess()->GetTitleID(); | ||
| 197 | |||
| 198 | const auto& vm_manager = system.CurrentProcess()->VMManager(); | ||
| 199 | metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()}; | ||
| 200 | metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(), | ||
| 201 | vm_manager.GetAddressSpaceSize()}; | ||
| 202 | metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()}; | ||
| 203 | |||
| 204 | is_pending_reload.exchange(true); | ||
| 205 | } | ||
| 206 | |||
| 207 | void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { | ||
| 208 | metadata.main_nso_extents = {main_region_begin, main_region_size}; | ||
| 209 | } | ||
| 210 | |||
| 211 | void CheatEngine::Reload(std::vector<CheatEntry> cheats) { | ||
| 212 | this->cheats = std::move(cheats); | ||
| 213 | is_pending_reload.exchange(true); | ||
| 214 | } | ||
| 215 | |||
| 216 | MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); | ||
| 217 | |||
| 218 | void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { | ||
| 219 | if (is_pending_reload.exchange(false)) { | ||
| 220 | vm.LoadProgram(cheats); | ||
| 221 | } | ||
| 222 | |||
| 223 | if (vm.GetProgramSize() == 0) { | ||
| 224 | return; | ||
| 225 | } | ||
| 226 | |||
| 227 | MICROPROFILE_SCOPE(Cheat_Engine); | ||
| 228 | |||
| 229 | vm.Execute(metadata); | ||
| 230 | |||
| 231 | core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); | ||
| 232 | } | ||
| 233 | |||
| 234 | } // namespace Memory | ||
diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h new file mode 100644 index 000000000..0f012e9b5 --- /dev/null +++ b/src/core/memory/cheat_engine.h | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | // Copyright 2018 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <atomic> | ||
| 8 | #include <vector> | ||
| 9 | #include "common/common_types.h" | ||
| 10 | #include "core/memory/dmnt_cheat_types.h" | ||
| 11 | #include "core/memory/dmnt_cheat_vm.h" | ||
| 12 | |||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } | ||
| 16 | |||
| 17 | namespace Core::Timing { | ||
| 18 | class CoreTiming; | ||
| 19 | struct EventType; | ||
| 20 | } // namespace Core::Timing | ||
| 21 | |||
| 22 | namespace Memory { | ||
| 23 | |||
| 24 | class StandardVmCallbacks : public DmntCheatVm::Callbacks { | ||
| 25 | public: | ||
| 26 | StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata); | ||
| 27 | ~StandardVmCallbacks() override; | ||
| 28 | |||
| 29 | void MemoryRead(VAddr address, void* data, u64 size) override; | ||
| 30 | void MemoryWrite(VAddr address, const void* data, u64 size) override; | ||
| 31 | u64 HidKeysDown() override; | ||
| 32 | void DebugLog(u8 id, u64 value) override; | ||
| 33 | void CommandLog(std::string_view data) override; | ||
| 34 | |||
| 35 | private: | ||
| 36 | VAddr SanitizeAddress(VAddr address) const; | ||
| 37 | |||
| 38 | const CheatProcessMetadata& metadata; | ||
| 39 | const Core::System& system; | ||
| 40 | }; | ||
| 41 | |||
| 42 | // Intermediary class that parses a text file or other disk format for storing cheats into a | ||
| 43 | // CheatList object, that can be used for execution. | ||
| 44 | class CheatParser { | ||
| 45 | public: | ||
| 46 | virtual ~CheatParser(); | ||
| 47 | |||
| 48 | virtual std::vector<CheatEntry> Parse(const Core::System& system, | ||
| 49 | std::string_view data) const = 0; | ||
| 50 | }; | ||
| 51 | |||
| 52 | // CheatParser implementation that parses text files | ||
| 53 | class TextCheatParser final : public CheatParser { | ||
| 54 | public: | ||
| 55 | ~TextCheatParser() override; | ||
| 56 | |||
| 57 | std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override; | ||
| 58 | }; | ||
| 59 | |||
| 60 | // Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming | ||
| 61 | class CheatEngine final { | ||
| 62 | public: | ||
| 63 | CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_, | ||
| 64 | const std::array<u8, 0x20>& build_id); | ||
| 65 | ~CheatEngine(); | ||
| 66 | |||
| 67 | void Initialize(); | ||
| 68 | void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); | ||
| 69 | |||
| 70 | void Reload(std::vector<CheatEntry> cheats); | ||
| 71 | |||
| 72 | private: | ||
| 73 | void FrameCallback(u64 userdata, s64 cycles_late); | ||
| 74 | |||
| 75 | DmntCheatVm vm; | ||
| 76 | CheatProcessMetadata metadata; | ||
| 77 | |||
| 78 | std::vector<CheatEntry> cheats; | ||
| 79 | std::atomic_bool is_pending_reload{false}; | ||
| 80 | |||
| 81 | Core::Timing::EventType* event{}; | ||
| 82 | Core::Timing::CoreTiming& core_timing; | ||
| 83 | Core::System& system; | ||
| 84 | }; | ||
| 85 | |||
| 86 | } // namespace Memory | ||