diff options
| author | 2019-03-21 21:41:59 -0400 | |
|---|---|---|
| committer | 2019-03-21 21:41:59 -0400 | |
| commit | 639f0c524d3406b7c156dc75af4e934c6b5adcb6 (patch) | |
| tree | e03093771bbffc832c48670331cf9af54aa4a9fc | |
| parent | Merge pull request #2260 from lioncash/sdl (diff) | |
| parent | vm_manager: Remove cheat-specific ranges from VMManager (diff) | |
| download | yuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.tar.gz yuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.tar.xz yuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.zip | |
Merge pull request #1933 from DarkLordZach/cheat-engine
file_sys: Implement parser and interpreter for game memory cheats
Diffstat (limited to '')
| -rw-r--r-- | src/core/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/core/core.cpp | 11 | ||||
| -rw-r--r-- | src/core/core.h | 4 | ||||
| -rw-r--r-- | src/core/file_sys/cheat_engine.cpp | 493 | ||||
| -rw-r--r-- | src/core/file_sys/cheat_engine.h | 227 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.cpp | 54 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.h | 4 | ||||
| -rw-r--r-- | src/core/hle/kernel/vm_manager.h | 3 | ||||
| -rw-r--r-- | src/core/hle/service/hid/hid.h | 3 | ||||
| -rw-r--r-- | src/core/loader/nso.cpp | 12 |
10 files changed, 813 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 16920e2e9..bbbe60896 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -31,6 +31,8 @@ add_library(core STATIC | |||
| 31 | file_sys/bis_factory.h | 31 | file_sys/bis_factory.h |
| 32 | file_sys/card_image.cpp | 32 | file_sys/card_image.cpp |
| 33 | file_sys/card_image.h | 33 | file_sys/card_image.h |
| 34 | file_sys/cheat_engine.cpp | ||
| 35 | file_sys/cheat_engine.h | ||
| 34 | file_sys/content_archive.cpp | 36 | file_sys/content_archive.cpp |
| 35 | file_sys/content_archive.h | 37 | file_sys/content_archive.h |
| 36 | file_sys/control_metadata.cpp | 38 | file_sys/control_metadata.cpp |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 89b3fb418..a88e332be 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -32,6 +32,7 @@ | |||
| 32 | #include "core/perf_stats.h" | 32 | #include "core/perf_stats.h" |
| 33 | #include "core/settings.h" | 33 | #include "core/settings.h" |
| 34 | #include "core/telemetry_session.h" | 34 | #include "core/telemetry_session.h" |
| 35 | #include "file_sys/cheat_engine.h" | ||
| 35 | #include "frontend/applets/profile_select.h" | 36 | #include "frontend/applets/profile_select.h" |
| 36 | #include "frontend/applets/software_keyboard.h" | 37 | #include "frontend/applets/software_keyboard.h" |
| 37 | #include "frontend/applets/web_browser.h" | 38 | #include "frontend/applets/web_browser.h" |
| @@ -205,6 +206,7 @@ struct System::Impl { | |||
| 205 | GDBStub::Shutdown(); | 206 | GDBStub::Shutdown(); |
| 206 | Service::Shutdown(); | 207 | Service::Shutdown(); |
| 207 | service_manager.reset(); | 208 | service_manager.reset(); |
| 209 | cheat_engine.reset(); | ||
| 208 | telemetry_session.reset(); | 210 | telemetry_session.reset(); |
| 209 | gpu_core.reset(); | 211 | gpu_core.reset(); |
| 210 | 212 | ||
| @@ -255,6 +257,8 @@ struct System::Impl { | |||
| 255 | CpuCoreManager cpu_core_manager; | 257 | CpuCoreManager cpu_core_manager; |
| 256 | bool is_powered_on = false; | 258 | bool is_powered_on = false; |
| 257 | 259 | ||
| 260 | std::unique_ptr<FileSys::CheatEngine> cheat_engine; | ||
| 261 | |||
| 258 | /// Frontend applets | 262 | /// Frontend applets |
| 259 | std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; | 263 | std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; |
| 260 | std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; | 264 | std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; |
| @@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { | |||
| 453 | return impl->debug_context.get(); | 457 | return impl->debug_context.get(); |
| 454 | } | 458 | } |
| 455 | 459 | ||
| 460 | void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, | ||
| 461 | const std::string& build_id, VAddr code_region_start, | ||
| 462 | VAddr code_region_end) { | ||
| 463 | impl->cheat_engine = | ||
| 464 | std::make_unique<FileSys::CheatEngine>(list, build_id, code_region_start, code_region_end); | ||
| 465 | } | ||
| 466 | |||
| 456 | void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { | 467 | void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { |
| 457 | impl->virtual_filesystem = std::move(vfs); | 468 | impl->virtual_filesystem = std::move(vfs); |
| 458 | } | 469 | } |
diff --git a/src/core/core.h b/src/core/core.h index ba76a41d8..4d83b93cc 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -20,6 +20,7 @@ class WebBrowserApplet; | |||
| 20 | } // namespace Core::Frontend | 20 | } // namespace Core::Frontend |
| 21 | 21 | ||
| 22 | namespace FileSys { | 22 | namespace FileSys { |
| 23 | class CheatList; | ||
| 23 | class VfsFilesystem; | 24 | class VfsFilesystem; |
| 24 | } // namespace FileSys | 25 | } // namespace FileSys |
| 25 | 26 | ||
| @@ -253,6 +254,9 @@ public: | |||
| 253 | 254 | ||
| 254 | std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; | 255 | std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; |
| 255 | 256 | ||
| 257 | void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, | ||
| 258 | VAddr code_region_start, VAddr code_region_end); | ||
| 259 | |||
| 256 | void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); | 260 | void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); |
| 257 | 261 | ||
| 258 | const Frontend::ProfileSelectApplet& GetProfileSelector() const; | 262 | const Frontend::ProfileSelectApplet& GetProfileSelector() const; |
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp new file mode 100644 index 000000000..09ca9d705 --- /dev/null +++ b/src/core/file_sys/cheat_engine.cpp | |||
| @@ -0,0 +1,493 @@ | |||
| 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/file_sys/cheat_engine.h" | ||
| 13 | #include "core/hle/kernel/process.h" | ||
| 14 | #include "core/hle/service/hid/controllers/controller_base.h" | ||
| 15 | #include "core/hle/service/hid/controllers/npad.h" | ||
| 16 | #include "core/hle/service/hid/hid.h" | ||
| 17 | #include "core/hle/service/sm/sm.h" | ||
| 18 | |||
| 19 | namespace FileSys { | ||
| 20 | |||
| 21 | constexpr u64 CHEAT_ENGINE_TICKS = Core::Timing::BASE_CLOCK_RATE / 60; | ||
| 22 | constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; | ||
| 23 | |||
| 24 | u64 Cheat::Address() const { | ||
| 25 | u64 out; | ||
| 26 | std::memcpy(&out, raw.data(), sizeof(u64)); | ||
| 27 | return Common::swap64(out) & 0xFFFFFFFFFF; | ||
| 28 | } | ||
| 29 | |||
| 30 | u64 Cheat::ValueWidth(u64 offset) const { | ||
| 31 | return Value(offset, width); | ||
| 32 | } | ||
| 33 | |||
| 34 | u64 Cheat::Value(u64 offset, u64 width) const { | ||
| 35 | u64 out; | ||
| 36 | std::memcpy(&out, raw.data() + offset, sizeof(u64)); | ||
| 37 | out = Common::swap64(out); | ||
| 38 | if (width == 8) | ||
| 39 | return out; | ||
| 40 | return out & ((1ull << (width * CHAR_BIT)) - 1); | ||
| 41 | } | ||
| 42 | |||
| 43 | u32 Cheat::KeypadValue() const { | ||
| 44 | u32 out; | ||
| 45 | std::memcpy(&out, raw.data(), sizeof(u32)); | ||
| 46 | return Common::swap32(out) & 0x0FFFFFFF; | ||
| 47 | } | ||
| 48 | |||
| 49 | void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, | ||
| 50 | VAddr heap_end, MemoryWriter writer, MemoryReader reader) { | ||
| 51 | this->main_region_begin = main_begin; | ||
| 52 | this->main_region_end = main_end; | ||
| 53 | this->heap_region_begin = heap_begin; | ||
| 54 | this->heap_region_end = heap_end; | ||
| 55 | this->writer = writer; | ||
| 56 | this->reader = reader; | ||
| 57 | } | ||
| 58 | |||
| 59 | MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); | ||
| 60 | |||
| 61 | void CheatList::Execute() { | ||
| 62 | MICROPROFILE_SCOPE(Cheat_Engine); | ||
| 63 | |||
| 64 | std::fill(scratch.begin(), scratch.end(), 0); | ||
| 65 | in_standard = false; | ||
| 66 | for (std::size_t i = 0; i < master_list.size(); ++i) { | ||
| 67 | LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); | ||
| 68 | current_block = i; | ||
| 69 | ExecuteBlock(master_list[i].second); | ||
| 70 | } | ||
| 71 | |||
| 72 | in_standard = true; | ||
| 73 | for (std::size_t i = 0; i < standard_list.size(); ++i) { | ||
| 74 | LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); | ||
| 75 | current_block = i; | ||
| 76 | ExecuteBlock(standard_list[i].second); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | CheatList::CheatList(ProgramSegment master, ProgramSegment standard) | ||
| 81 | : master_list(master), standard_list(standard) {} | ||
| 82 | |||
| 83 | bool CheatList::EvaluateConditional(const Cheat& cheat) const { | ||
| 84 | using ComparisonFunction = bool (*)(u64, u64); | ||
| 85 | constexpr std::array<ComparisonFunction, 6> comparison_functions{ | ||
| 86 | [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, | ||
| 87 | [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, | ||
| 88 | [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, | ||
| 89 | }; | ||
| 90 | |||
| 91 | if (cheat.type == CodeType::ConditionalInput) { | ||
| 92 | const auto applet_resource = Core::System::GetInstance() | ||
| 93 | .ServiceManager() | ||
| 94 | .GetService<Service::HID::Hid>("hid") | ||
| 95 | ->GetAppletResource(); | ||
| 96 | if (applet_resource == nullptr) { | ||
| 97 | LOG_WARNING( | ||
| 98 | Common_Filesystem, | ||
| 99 | "Attempted to evaluate input conditional, but applet resource is not initialized!"); | ||
| 100 | return false; | ||
| 101 | } | ||
| 102 | |||
| 103 | const auto press_state = | ||
| 104 | applet_resource | ||
| 105 | ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) | ||
| 106 | .GetAndResetPressState(); | ||
| 107 | return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; | ||
| 108 | } | ||
| 109 | |||
| 110 | ASSERT(cheat.type == CodeType::Conditional); | ||
| 111 | |||
| 112 | const auto offset = | ||
| 113 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 114 | ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); | ||
| 115 | auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; | ||
| 116 | const auto addr = cheat.Address() + offset; | ||
| 117 | |||
| 118 | return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); | ||
| 119 | } | ||
| 120 | |||
| 121 | void CheatList::ProcessBlockPairs(const Block& block) { | ||
| 122 | block_pairs.clear(); | ||
| 123 | |||
| 124 | u64 scope = 0; | ||
| 125 | std::map<u64, u64> pairs; | ||
| 126 | |||
| 127 | for (std::size_t i = 0; i < block.size(); ++i) { | ||
| 128 | const auto& cheat = block[i]; | ||
| 129 | |||
| 130 | switch (cheat.type) { | ||
| 131 | case CodeType::Conditional: | ||
| 132 | case CodeType::ConditionalInput: | ||
| 133 | pairs.insert_or_assign(scope, i); | ||
| 134 | ++scope; | ||
| 135 | break; | ||
| 136 | case CodeType::EndConditional: { | ||
| 137 | --scope; | ||
| 138 | const auto idx = pairs.at(scope); | ||
| 139 | block_pairs.insert_or_assign(idx, i); | ||
| 140 | break; | ||
| 141 | } | ||
| 142 | case CodeType::Loop: { | ||
| 143 | if (cheat.end_of_loop) { | ||
| 144 | --scope; | ||
| 145 | const auto idx = pairs.at(scope); | ||
| 146 | block_pairs.insert_or_assign(idx, i); | ||
| 147 | } else { | ||
| 148 | pairs.insert_or_assign(scope, i); | ||
| 149 | ++scope; | ||
| 150 | } | ||
| 151 | break; | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | void CheatList::WriteImmediate(const Cheat& cheat) { | ||
| 158 | const auto offset = | ||
| 159 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 160 | const auto& register_3 = scratch.at(cheat.register_3); | ||
| 161 | |||
| 162 | const auto addr = cheat.Address() + offset + register_3; | ||
| 163 | LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, | ||
| 164 | cheat.Value(8, cheat.width)); | ||
| 165 | writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); | ||
| 166 | } | ||
| 167 | |||
| 168 | void CheatList::BeginConditional(const Cheat& cheat) { | ||
| 169 | if (EvaluateConditional(cheat)) { | ||
| 170 | return; | ||
| 171 | } | ||
| 172 | |||
| 173 | const auto iter = block_pairs.find(current_index); | ||
| 174 | ASSERT(iter != block_pairs.end()); | ||
| 175 | current_index = iter->second - 1; | ||
| 176 | } | ||
| 177 | |||
| 178 | void CheatList::EndConditional(const Cheat& cheat) { | ||
| 179 | LOG_DEBUG(Common_Filesystem, "Ending conditional block."); | ||
| 180 | } | ||
| 181 | |||
| 182 | void CheatList::Loop(const Cheat& cheat) { | ||
| 183 | if (cheat.end_of_loop.Value()) | ||
| 184 | ASSERT(!cheat.end_of_loop.Value()); | ||
| 185 | |||
| 186 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 187 | const auto iter = block_pairs.find(current_index); | ||
| 188 | ASSERT(iter != block_pairs.end()); | ||
| 189 | ASSERT(iter->first < iter->second); | ||
| 190 | |||
| 191 | for (int i = cheat.Value(4, 4); i >= 0; --i) { | ||
| 192 | register_3 = i; | ||
| 193 | for (std::size_t c = iter->first + 1; c < iter->second; ++c) { | ||
| 194 | current_index = c; | ||
| 195 | ExecuteSingleCheat( | ||
| 196 | (in_standard ? standard_list : master_list)[current_block].second[c]); | ||
| 197 | } | ||
| 198 | } | ||
| 199 | |||
| 200 | current_index = iter->second; | ||
| 201 | } | ||
| 202 | |||
| 203 | void CheatList::LoadImmediate(const Cheat& cheat) { | ||
| 204 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 205 | |||
| 206 | LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, | ||
| 207 | cheat.Value(4, 8)); | ||
| 208 | register_3 = cheat.Value(4, 8); | ||
| 209 | } | ||
| 210 | |||
| 211 | void CheatList::LoadIndexed(const Cheat& cheat) { | ||
| 212 | const auto offset = | ||
| 213 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 214 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 215 | |||
| 216 | const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); | ||
| 217 | LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", | ||
| 218 | cheat.register_3, addr); | ||
| 219 | register_3 = reader(cheat.width, SanitizeAddress(addr)); | ||
| 220 | } | ||
| 221 | |||
| 222 | void CheatList::StoreIndexed(const Cheat& cheat) { | ||
| 223 | const auto& register_3 = scratch.at(cheat.register_3); | ||
| 224 | |||
| 225 | const auto addr = | ||
| 226 | register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); | ||
| 227 | LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", | ||
| 228 | cheat.Value(4, cheat.width), addr); | ||
| 229 | writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); | ||
| 230 | } | ||
| 231 | |||
| 232 | void CheatList::RegisterArithmetic(const Cheat& cheat) { | ||
| 233 | using ArithmeticFunction = u64 (*)(u64, u64); | ||
| 234 | constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ | ||
| 235 | [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, | ||
| 236 | [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, | ||
| 237 | [](u64 a, u64 b) { return a >> b; }, | ||
| 238 | }; | ||
| 239 | |||
| 240 | using ArithmeticOverflowCheck = bool (*)(u64, u64); | ||
| 241 | constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ | ||
| 242 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b | ||
| 243 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b | ||
| 244 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b | ||
| 245 | [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b | ||
| 246 | [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b | ||
| 247 | }; | ||
| 248 | |||
| 249 | static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), | ||
| 250 | "Missing or have extra arithmetic overflow checks compared to functions!"); | ||
| 251 | |||
| 252 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 253 | |||
| 254 | ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); | ||
| 255 | auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; | ||
| 256 | auto* overflow_function = | ||
| 257 | arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; | ||
| 258 | LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", | ||
| 259 | cheat.register_3, cheat.ValueWidth(4)); | ||
| 260 | |||
| 261 | if (overflow_function(register_3, cheat.ValueWidth(4))) { | ||
| 262 | LOG_WARNING(Common_Filesystem, | ||
| 263 | "overflow will occur when performing arithmetic operation={:02X} with operands " | ||
| 264 | "a={:016X}, b={:016X}!", | ||
| 265 | static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); | ||
| 266 | } | ||
| 267 | |||
| 268 | register_3 = function(register_3, cheat.ValueWidth(4)); | ||
| 269 | } | ||
| 270 | |||
| 271 | void CheatList::BeginConditionalInput(const Cheat& cheat) { | ||
| 272 | if (EvaluateConditional(cheat)) | ||
| 273 | return; | ||
| 274 | |||
| 275 | const auto iter = block_pairs.find(current_index); | ||
| 276 | ASSERT(iter != block_pairs.end()); | ||
| 277 | current_index = iter->second - 1; | ||
| 278 | } | ||
| 279 | |||
| 280 | VAddr CheatList::SanitizeAddress(VAddr in) const { | ||
| 281 | if ((in < main_region_begin || in >= main_region_end) && | ||
| 282 | (in < heap_region_begin || in >= heap_region_end)) { | ||
| 283 | LOG_ERROR(Common_Filesystem, | ||
| 284 | "Cheat attempting to access memory at invalid address={:016X}, if this persists, " | ||
| 285 | "the cheat may be incorrect. However, this may be normal early in execution if " | ||
| 286 | "the game has not properly set up yet.", | ||
| 287 | in); | ||
| 288 | return 0; ///< Invalid addresses will hard crash | ||
| 289 | } | ||
| 290 | |||
| 291 | return in; | ||
| 292 | } | ||
| 293 | |||
| 294 | void CheatList::ExecuteSingleCheat(const Cheat& cheat) { | ||
| 295 | using CheatOperationFunction = void (CheatList::*)(const Cheat&); | ||
| 296 | constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ | ||
| 297 | &CheatList::WriteImmediate, &CheatList::BeginConditional, | ||
| 298 | &CheatList::EndConditional, &CheatList::Loop, | ||
| 299 | &CheatList::LoadImmediate, &CheatList::LoadIndexed, | ||
| 300 | &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, | ||
| 301 | &CheatList::BeginConditionalInput, | ||
| 302 | }; | ||
| 303 | |||
| 304 | const auto index = static_cast<u8>(cheat.type.Value()); | ||
| 305 | ASSERT(index < sizeof(cheat_operation_functions)); | ||
| 306 | const auto op = cheat_operation_functions[index]; | ||
| 307 | (this->*op)(cheat); | ||
| 308 | } | ||
| 309 | |||
| 310 | void CheatList::ExecuteBlock(const Block& block) { | ||
| 311 | encountered_loops.clear(); | ||
| 312 | |||
| 313 | ProcessBlockPairs(block); | ||
| 314 | for (std::size_t i = 0; i < block.size(); ++i) { | ||
| 315 | current_index = i; | ||
| 316 | ExecuteSingleCheat(block[i]); | ||
| 317 | i = current_index; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | CheatParser::~CheatParser() = default; | ||
| 322 | |||
| 323 | CheatList CheatParser::MakeCheatList(CheatList::ProgramSegment master, | ||
| 324 | CheatList::ProgramSegment standard) const { | ||
| 325 | return {master, standard}; | ||
| 326 | } | ||
| 327 | |||
| 328 | TextCheatParser::~TextCheatParser() = default; | ||
| 329 | |||
| 330 | CheatList TextCheatParser::Parse(const std::vector<u8>& data) const { | ||
| 331 | std::stringstream ss; | ||
| 332 | ss.write(reinterpret_cast<const char*>(data.data()), data.size()); | ||
| 333 | |||
| 334 | std::vector<std::string> lines; | ||
| 335 | std::string stream_line; | ||
| 336 | while (std::getline(ss, stream_line)) { | ||
| 337 | // Remove a trailing \r | ||
| 338 | if (!stream_line.empty() && stream_line.back() == '\r') | ||
| 339 | stream_line.pop_back(); | ||
| 340 | lines.push_back(std::move(stream_line)); | ||
| 341 | } | ||
| 342 | |||
| 343 | CheatList::ProgramSegment master_list; | ||
| 344 | CheatList::ProgramSegment standard_list; | ||
| 345 | |||
| 346 | for (std::size_t i = 0; i < lines.size(); ++i) { | ||
| 347 | auto line = lines[i]; | ||
| 348 | |||
| 349 | if (!line.empty() && (line[0] == '[' || line[0] == '{')) { | ||
| 350 | const auto master = line[0] == '{'; | ||
| 351 | const auto begin = master ? line.find('{') : line.find('['); | ||
| 352 | const auto end = master ? line.rfind('}') : line.rfind(']'); | ||
| 353 | |||
| 354 | ASSERT(begin != std::string::npos && end != std::string::npos); | ||
| 355 | |||
| 356 | const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; | ||
| 357 | CheatList::Block block{}; | ||
| 358 | |||
| 359 | while (i < lines.size() - 1) { | ||
| 360 | line = lines[++i]; | ||
| 361 | if (!line.empty() && (line[0] == '[' || line[0] == '{')) { | ||
| 362 | --i; | ||
| 363 | break; | ||
| 364 | } | ||
| 365 | |||
| 366 | if (line.size() < 8) | ||
| 367 | continue; | ||
| 368 | |||
| 369 | Cheat out{}; | ||
| 370 | out.raw = ParseSingleLineCheat(line); | ||
| 371 | block.push_back(out); | ||
| 372 | } | ||
| 373 | |||
| 374 | (master ? master_list : standard_list).emplace_back(patch_name, block); | ||
| 375 | } | ||
| 376 | } | ||
| 377 | |||
| 378 | return MakeCheatList(master_list, standard_list); | ||
| 379 | } | ||
| 380 | |||
| 381 | std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { | ||
| 382 | std::array<u8, 16> out{}; | ||
| 383 | |||
| 384 | if (line.size() < 8) | ||
| 385 | return out; | ||
| 386 | |||
| 387 | const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); | ||
| 388 | std::memcpy(out.data(), word1.data(), sizeof(u32)); | ||
| 389 | |||
| 390 | if (line.size() < 17 || line[8] != ' ') | ||
| 391 | return out; | ||
| 392 | |||
| 393 | const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); | ||
| 394 | std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); | ||
| 395 | |||
| 396 | if (line.size() < 26 || line[17] != ' ') { | ||
| 397 | // Perform shifting in case value is truncated early. | ||
| 398 | const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); | ||
| 399 | if (type == CodeType::Loop || type == CodeType::LoadImmediate || | ||
| 400 | type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { | ||
| 401 | std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); | ||
| 402 | std::memset(out.data() + 4, 0, sizeof(u32)); | ||
| 403 | } | ||
| 404 | |||
| 405 | return out; | ||
| 406 | } | ||
| 407 | |||
| 408 | const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); | ||
| 409 | std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); | ||
| 410 | |||
| 411 | if (line.size() < 35 || line[26] != ' ') { | ||
| 412 | // Perform shifting in case value is truncated early. | ||
| 413 | const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); | ||
| 414 | if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { | ||
| 415 | std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); | ||
| 416 | std::memset(out.data() + 8, 0, sizeof(u32)); | ||
| 417 | } | ||
| 418 | |||
| 419 | return out; | ||
| 420 | } | ||
| 421 | |||
| 422 | const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); | ||
| 423 | std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); | ||
| 424 | |||
| 425 | return out; | ||
| 426 | } | ||
| 427 | |||
| 428 | u64 MemoryReadImpl(u32 width, VAddr addr) { | ||
| 429 | switch (width) { | ||
| 430 | case 1: | ||
| 431 | return Memory::Read8(addr); | ||
| 432 | case 2: | ||
| 433 | return Memory::Read16(addr); | ||
| 434 | case 4: | ||
| 435 | return Memory::Read32(addr); | ||
| 436 | case 8: | ||
| 437 | return Memory::Read64(addr); | ||
| 438 | default: | ||
| 439 | UNREACHABLE(); | ||
| 440 | return 0; | ||
| 441 | } | ||
| 442 | } | ||
| 443 | |||
| 444 | void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { | ||
| 445 | switch (width) { | ||
| 446 | case 1: | ||
| 447 | Memory::Write8(addr, static_cast<u8>(value)); | ||
| 448 | break; | ||
| 449 | case 2: | ||
| 450 | Memory::Write16(addr, static_cast<u16>(value)); | ||
| 451 | break; | ||
| 452 | case 4: | ||
| 453 | Memory::Write32(addr, static_cast<u32>(value)); | ||
| 454 | break; | ||
| 455 | case 8: | ||
| 456 | Memory::Write64(addr, value); | ||
| 457 | break; | ||
| 458 | default: | ||
| 459 | UNREACHABLE(); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | |||
| 463 | CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id, | ||
| 464 | VAddr code_region_start, VAddr code_region_end) | ||
| 465 | : cheats(std::move(cheats)) { | ||
| 466 | auto& core_timing{Core::System::GetInstance().CoreTiming()}; | ||
| 467 | event = core_timing.RegisterEvent( | ||
| 468 | "CheatEngine::FrameCallback::" + build_id, | ||
| 469 | [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); | ||
| 470 | core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); | ||
| 471 | |||
| 472 | const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager(); | ||
| 473 | for (auto& list : this->cheats) { | ||
| 474 | list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), | ||
| 475 | code_region_end, vm_manager.GetHeapRegionEndAddress(), | ||
| 476 | &MemoryWriteImpl, &MemoryReadImpl); | ||
| 477 | } | ||
| 478 | } | ||
| 479 | |||
| 480 | CheatEngine::~CheatEngine() { | ||
| 481 | auto& core_timing{Core::System::GetInstance().CoreTiming()}; | ||
| 482 | core_timing.UnscheduleEvent(event, 0); | ||
| 483 | } | ||
| 484 | |||
| 485 | void CheatEngine::FrameCallback(u64 userdata, int cycles_late) { | ||
| 486 | for (auto& list : cheats) | ||
| 487 | list.Execute(); | ||
| 488 | |||
| 489 | auto& core_timing{Core::System::GetInstance().CoreTiming()}; | ||
| 490 | core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); | ||
| 491 | } | ||
| 492 | |||
| 493 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h new file mode 100644 index 000000000..7ed69a2c8 --- /dev/null +++ b/src/core/file_sys/cheat_engine.h | |||
| @@ -0,0 +1,227 @@ | |||
| 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 <map> | ||
| 8 | #include <set> | ||
| 9 | #include <vector> | ||
| 10 | #include <queue> | ||
| 11 | #include "common/bit_field.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | |||
| 14 | namespace Core::Timing { | ||
| 15 | struct EventType; | ||
| 16 | } | ||
| 17 | |||
| 18 | namespace FileSys { | ||
| 19 | |||
| 20 | enum class CodeType : u32 { | ||
| 21 | // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY | ||
| 22 | // Writes a T sized value Y to the address A added to the value of register R in memory domain M | ||
| 23 | WriteImmediate = 0, | ||
| 24 | |||
| 25 | // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY | ||
| 26 | // Compares the T sized value Y to the value at address A in memory domain M using the | ||
| 27 | // conditional function C. If success, continues execution. If failure, jumps to the matching | ||
| 28 | // EndConditional statement. | ||
| 29 | Conditional = 1, | ||
| 30 | |||
| 31 | // 20000000 | ||
| 32 | // Terminates a Conditional or ConditionalInput block. | ||
| 33 | EndConditional = 2, | ||
| 34 | |||
| 35 | // 300R0000 VVVVVVVV | ||
| 36 | // Starts looping V times, storing the current count in register R. | ||
| 37 | // Loop block is terminated with a matching 310R0000. | ||
| 38 | Loop = 3, | ||
| 39 | |||
| 40 | // 400R0000 VVVVVVVV VVVVVVVV | ||
| 41 | // Sets the value of register R to the value V. | ||
| 42 | LoadImmediate = 4, | ||
| 43 | |||
| 44 | // 5TMRI0AA AAAAAAAA | ||
| 45 | // Sets the value of register R to the value of width T at address A in memory domain M, with | ||
| 46 | // the current value of R added to the address if I == 1. | ||
| 47 | LoadIndexed = 5, | ||
| 48 | |||
| 49 | // 6T0RIFG0 VVVVVVVV VVVVVVVV | ||
| 50 | // Writes the value V of width T to the memory address stored in register R. Adds the value of | ||
| 51 | // register G to the final calculation if F is nonzero. Increments the value of register R by T | ||
| 52 | // after operation if I is nonzero. | ||
| 53 | StoreIndexed = 6, | ||
| 54 | |||
| 55 | // 7T0RA000 VVVVVVVV | ||
| 56 | // Performs the arithmetic operation A on the value in register R and the value V of width T, | ||
| 57 | // storing the result in register R. | ||
| 58 | RegisterArithmetic = 7, | ||
| 59 | |||
| 60 | // 8KKKKKKK | ||
| 61 | // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, | ||
| 62 | // execution continues. If none are, execution skips to the next EndConditional command. | ||
| 63 | ConditionalInput = 8, | ||
| 64 | }; | ||
| 65 | |||
| 66 | enum class MemoryType : u32 { | ||
| 67 | // Addressed relative to start of main NSO | ||
| 68 | MainNSO = 0, | ||
| 69 | |||
| 70 | // Addressed relative to start of heap | ||
| 71 | Heap = 1, | ||
| 72 | }; | ||
| 73 | |||
| 74 | enum class ArithmeticOp : u32 { | ||
| 75 | Add = 0, | ||
| 76 | Sub = 1, | ||
| 77 | Mult = 2, | ||
| 78 | LShift = 3, | ||
| 79 | RShift = 4, | ||
| 80 | }; | ||
| 81 | |||
| 82 | enum class ComparisonOp : u32 { | ||
| 83 | GreaterThan = 1, | ||
| 84 | GreaterThanEqual = 2, | ||
| 85 | LessThan = 3, | ||
| 86 | LessThanEqual = 4, | ||
| 87 | Equal = 5, | ||
| 88 | Inequal = 6, | ||
| 89 | }; | ||
| 90 | |||
| 91 | union Cheat { | ||
| 92 | std::array<u8, 16> raw; | ||
| 93 | |||
| 94 | BitField<4, 4, CodeType> type; | ||
| 95 | BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. | ||
| 96 | BitField<0, 4, u32> end_of_loop; | ||
| 97 | BitField<12, 4, MemoryType> memory_type; | ||
| 98 | BitField<8, 4, u32> register_3; | ||
| 99 | BitField<8, 4, ComparisonOp> comparison_op; | ||
| 100 | BitField<20, 4, u32> load_from_register; | ||
| 101 | BitField<20, 4, u32> increment_register; | ||
| 102 | BitField<20, 4, ArithmeticOp> arithmetic_op; | ||
| 103 | BitField<16, 4, u32> add_additional_register; | ||
| 104 | BitField<28, 4, u32> register_6; | ||
| 105 | |||
| 106 | u64 Address() const; | ||
| 107 | u64 ValueWidth(u64 offset) const; | ||
| 108 | u64 Value(u64 offset, u64 width) const; | ||
| 109 | u32 KeypadValue() const; | ||
| 110 | }; | ||
| 111 | |||
| 112 | class CheatParser; | ||
| 113 | |||
| 114 | // Represents a full collection of cheats for a game. The Execute function should be called every | ||
| 115 | // interval that all cheats should be executed. Clients should not directly instantiate this class | ||
| 116 | // (hence private constructor), they should instead receive an instance from CheatParser, which | ||
| 117 | // guarantees the list is always in an acceptable state. | ||
| 118 | class CheatList { | ||
| 119 | public: | ||
| 120 | friend class CheatParser; | ||
| 121 | |||
| 122 | using Block = std::vector<Cheat>; | ||
| 123 | using ProgramSegment = std::vector<std::pair<std::string, Block>>; | ||
| 124 | |||
| 125 | // (width in bytes, address, value) | ||
| 126 | using MemoryWriter = void (*)(u32, VAddr, u64); | ||
| 127 | // (width in bytes, address) -> value | ||
| 128 | using MemoryReader = u64 (*)(u32, VAddr); | ||
| 129 | |||
| 130 | void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, | ||
| 131 | MemoryWriter writer, MemoryReader reader); | ||
| 132 | |||
| 133 | void Execute(); | ||
| 134 | |||
| 135 | private: | ||
| 136 | CheatList(ProgramSegment master, ProgramSegment standard); | ||
| 137 | |||
| 138 | void ProcessBlockPairs(const Block& block); | ||
| 139 | void ExecuteSingleCheat(const Cheat& cheat); | ||
| 140 | |||
| 141 | void ExecuteBlock(const Block& block); | ||
| 142 | |||
| 143 | bool EvaluateConditional(const Cheat& cheat) const; | ||
| 144 | |||
| 145 | // Individual cheat operations | ||
| 146 | void WriteImmediate(const Cheat& cheat); | ||
| 147 | void BeginConditional(const Cheat& cheat); | ||
| 148 | void EndConditional(const Cheat& cheat); | ||
| 149 | void Loop(const Cheat& cheat); | ||
| 150 | void LoadImmediate(const Cheat& cheat); | ||
| 151 | void LoadIndexed(const Cheat& cheat); | ||
| 152 | void StoreIndexed(const Cheat& cheat); | ||
| 153 | void RegisterArithmetic(const Cheat& cheat); | ||
| 154 | void BeginConditionalInput(const Cheat& cheat); | ||
| 155 | |||
| 156 | VAddr SanitizeAddress(VAddr in) const; | ||
| 157 | |||
| 158 | // Master Codes are defined as codes that cannot be disabled and are run prior to all | ||
| 159 | // others. | ||
| 160 | ProgramSegment master_list; | ||
| 161 | // All other codes | ||
| 162 | ProgramSegment standard_list; | ||
| 163 | |||
| 164 | bool in_standard = false; | ||
| 165 | |||
| 166 | // 16 (0x0-0xF) scratch registers that can be used by cheats | ||
| 167 | std::array<u64, 16> scratch{}; | ||
| 168 | |||
| 169 | MemoryWriter writer = nullptr; | ||
| 170 | MemoryReader reader = nullptr; | ||
| 171 | |||
| 172 | u64 main_region_begin{}; | ||
| 173 | u64 heap_region_begin{}; | ||
| 174 | u64 main_region_end{}; | ||
| 175 | u64 heap_region_end{}; | ||
| 176 | |||
| 177 | u64 current_block{}; | ||
| 178 | // The current index of the cheat within the current Block | ||
| 179 | u64 current_index{}; | ||
| 180 | |||
| 181 | // The 'stack' of the program. When a conditional or loop statement is encountered, its index is | ||
| 182 | // pushed onto this queue. When a end block is encountered, the condition is checked. | ||
| 183 | std::map<u64, u64> block_pairs; | ||
| 184 | |||
| 185 | std::set<u64> encountered_loops; | ||
| 186 | }; | ||
| 187 | |||
| 188 | // Intermediary class that parses a text file or other disk format for storing cheats into a | ||
| 189 | // CheatList object, that can be used for execution. | ||
| 190 | class CheatParser { | ||
| 191 | public: | ||
| 192 | virtual ~CheatParser(); | ||
| 193 | |||
| 194 | virtual CheatList Parse(const std::vector<u8>& data) const = 0; | ||
| 195 | |||
| 196 | protected: | ||
| 197 | CheatList MakeCheatList(CheatList::ProgramSegment master, | ||
| 198 | CheatList::ProgramSegment standard) const; | ||
| 199 | }; | ||
| 200 | |||
| 201 | // CheatParser implementation that parses text files | ||
| 202 | class TextCheatParser final : public CheatParser { | ||
| 203 | public: | ||
| 204 | ~TextCheatParser() override; | ||
| 205 | |||
| 206 | CheatList Parse(const std::vector<u8>& data) const override; | ||
| 207 | |||
| 208 | private: | ||
| 209 | std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; | ||
| 210 | }; | ||
| 211 | |||
| 212 | // Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming | ||
| 213 | class CheatEngine final { | ||
| 214 | public: | ||
| 215 | CheatEngine(std::vector<CheatList> cheats, const std::string& build_id, VAddr code_region_start, | ||
| 216 | VAddr code_region_end); | ||
| 217 | ~CheatEngine(); | ||
| 218 | |||
| 219 | private: | ||
| 220 | void FrameCallback(u64 userdata, int cycles_late); | ||
| 221 | |||
| 222 | Core::Timing::EventType* event; | ||
| 223 | |||
| 224 | std::vector<CheatList> cheats; | ||
| 225 | }; | ||
| 226 | |||
| 227 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 61706966e..2b09e5d35 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #include <cstddef> | 7 | #include <cstddef> |
| 8 | #include <cstring> | 8 | #include <cstring> |
| 9 | 9 | ||
| 10 | #include "common/file_util.h" | ||
| 10 | #include "common/hex_util.h" | 11 | #include "common/hex_util.h" |
| 11 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 12 | #include "core/file_sys/content_archive.h" | 13 | #include "core/file_sys/content_archive.h" |
| @@ -232,6 +233,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { | |||
| 232 | return !CollectPatches(patch_dirs, build_id).empty(); | 233 | return !CollectPatches(patch_dirs, build_id).empty(); |
| 233 | } | 234 | } |
| 234 | 235 | ||
| 236 | static std::optional<CheatList> ReadCheatFileFromFolder(u64 title_id, | ||
| 237 | const std::array<u8, 0x20>& build_id_, | ||
| 238 | const VirtualDir& base_path, bool upper) { | ||
| 239 | const auto build_id_raw = Common::HexArrayToString(build_id_, upper); | ||
| 240 | const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); | ||
| 241 | const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); | ||
| 242 | |||
| 243 | if (file == nullptr) { | ||
| 244 | LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}", | ||
| 245 | title_id, build_id); | ||
| 246 | return std::nullopt; | ||
| 247 | } | ||
| 248 | |||
| 249 | std::vector<u8> data(file->GetSize()); | ||
| 250 | if (file->Read(data.data(), data.size()) != data.size()) { | ||
| 251 | LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}", | ||
| 252 | title_id, build_id); | ||
| 253 | return std::nullopt; | ||
| 254 | } | ||
| 255 | |||
| 256 | TextCheatParser parser; | ||
| 257 | return parser.Parse(data); | ||
| 258 | } | ||
| 259 | |||
| 260 | std::vector<CheatList> PatchManager::CreateCheatList(const std::array<u8, 32>& build_id_) const { | ||
| 261 | std::vector<CheatList> out; | ||
| 262 | |||
| 263 | const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); | ||
| 264 | auto patch_dirs = load_dir->GetSubdirectories(); | ||
| 265 | std::sort(patch_dirs.begin(), patch_dirs.end(), | ||
| 266 | [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); | ||
| 267 | |||
| 268 | out.reserve(patch_dirs.size()); | ||
| 269 | for (const auto& subdir : patch_dirs) { | ||
| 270 | auto cheats_dir = subdir->GetSubdirectory("cheats"); | ||
| 271 | if (cheats_dir != nullptr) { | ||
| 272 | auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true); | ||
| 273 | if (res.has_value()) { | ||
| 274 | out.push_back(std::move(*res)); | ||
| 275 | continue; | ||
| 276 | } | ||
| 277 | |||
| 278 | res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false); | ||
| 279 | if (res.has_value()) | ||
| 280 | out.push_back(std::move(*res)); | ||
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | return out; | ||
| 285 | } | ||
| 286 | |||
| 235 | static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { | 287 | static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { |
| 236 | const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); | 288 | const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); |
| 237 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || | 289 | if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || |
| @@ -403,6 +455,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam | |||
| 403 | } | 455 | } |
| 404 | if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) | 456 | if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) |
| 405 | AppendCommaIfNotEmpty(types, "LayeredFS"); | 457 | AppendCommaIfNotEmpty(types, "LayeredFS"); |
| 458 | if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats"))) | ||
| 459 | AppendCommaIfNotEmpty(types, "Cheats"); | ||
| 406 | 460 | ||
| 407 | if (types.empty()) | 461 | if (types.empty()) |
| 408 | continue; | 462 | continue; |
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index b8a1652fd..3e3ac6aca 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "core/file_sys/cheat_engine.h" | ||
| 11 | #include "core/file_sys/nca_metadata.h" | 12 | #include "core/file_sys/nca_metadata.h" |
| 12 | #include "core/file_sys/vfs.h" | 13 | #include "core/file_sys/vfs.h" |
| 13 | 14 | ||
| @@ -45,6 +46,9 @@ public: | |||
| 45 | // Used to prevent expensive copies in NSO loader. | 46 | // Used to prevent expensive copies in NSO loader. |
| 46 | bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; | 47 | bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; |
| 47 | 48 | ||
| 49 | // Creates a CheatList object with all | ||
| 50 | std::vector<CheatList> CreateCheatList(const std::array<u8, 0x20>& build_id) const; | ||
| 51 | |||
| 48 | // Currently tracked RomFS patches: | 52 | // Currently tracked RomFS patches: |
| 49 | // - Game Updates | 53 | // - Game Updates |
| 50 | // - LayeredFS | 54 | // - LayeredFS |
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index b96980f8f..13ff4ebb3 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h | |||
| @@ -617,6 +617,9 @@ private: | |||
| 617 | VAddr new_map_region_base = 0; | 617 | VAddr new_map_region_base = 0; |
| 618 | VAddr new_map_region_end = 0; | 618 | VAddr new_map_region_end = 0; |
| 619 | 619 | ||
| 620 | VAddr main_code_region_base = 0; | ||
| 621 | VAddr main_code_region_end = 0; | ||
| 622 | |||
| 620 | VAddr tls_io_region_base = 0; | 623 | VAddr tls_io_region_base = 0; |
| 621 | VAddr tls_io_region_end = 0; | 624 | VAddr tls_io_region_end = 0; |
| 622 | 625 | ||
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index 7cc58db4c..498602de5 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -4,6 +4,9 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include "core/hle/service/hid/controllers/controller_base.h" | ||
| 8 | #include "core/hle/service/service.h" | ||
| 9 | |||
| 7 | #include "controllers/controller_base.h" | 10 | #include "controllers/controller_base.h" |
| 8 | #include "core/hle/service/service.h" | 11 | #include "core/hle/service/service.h" |
| 9 | 12 | ||
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index e1c8908a1..0eb9fd7f7 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -7,8 +7,10 @@ | |||
| 7 | #include <lz4.h> | 7 | #include <lz4.h> |
| 8 | #include "common/common_funcs.h" | 8 | #include "common/common_funcs.h" |
| 9 | #include "common/file_util.h" | 9 | #include "common/file_util.h" |
| 10 | #include "common/hex_util.h" | ||
| 10 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 11 | #include "common/swap.h" | 12 | #include "common/swap.h" |
| 13 | #include "core/core.h" | ||
| 12 | #include "core/file_sys/patch_manager.h" | 14 | #include "core/file_sys/patch_manager.h" |
| 13 | #include "core/gdbstub/gdbstub.h" | 15 | #include "core/gdbstub/gdbstub.h" |
| 14 | #include "core/hle/kernel/code_set.h" | 16 | #include "core/hle/kernel/code_set.h" |
| @@ -165,6 +167,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | |||
| 165 | std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); | 167 | std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); |
| 166 | } | 168 | } |
| 167 | 169 | ||
| 170 | // Apply cheats if they exist and the program has a valid title ID | ||
| 171 | if (pm) { | ||
| 172 | const auto cheats = pm->CreateCheatList(nso_header.build_id); | ||
| 173 | if (!cheats.empty()) { | ||
| 174 | Core::System::GetInstance().RegisterCheatList( | ||
| 175 | cheats, Common::HexArrayToString(nso_header.build_id), load_base, | ||
| 176 | load_base + program_image.size()); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 168 | // Load codeset for current process | 180 | // Load codeset for current process |
| 169 | codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); | 181 | codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); |
| 170 | process.LoadModule(std::move(codeset), load_base); | 182 | process.LoadModule(std::move(codeset), load_base); |