diff options
| -rw-r--r-- | src/common/logging/backend.cpp | 1 | ||||
| -rw-r--r-- | src/common/logging/log.h | 1 | ||||
| -rw-r--r-- | src/core/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | src/core/core.cpp | 25 | ||||
| -rw-r--r-- | src/core/core.h | 10 | ||||
| -rw-r--r-- | src/core/file_sys/cheat_engine.cpp | 492 | ||||
| -rw-r--r-- | src/core/file_sys/cheat_engine.h | 234 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.cpp | 32 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.h | 6 | ||||
| -rw-r--r-- | src/core/loader/nso.cpp | 3 | ||||
| -rw-r--r-- | src/core/memory/cheat_engine.cpp | 234 | ||||
| -rw-r--r-- | src/core/memory/cheat_engine.h | 86 | ||||
| -rw-r--r-- | src/core/memory/dmnt_cheat_types.h | 58 | ||||
| -rw-r--r-- | src/core/memory/dmnt_cheat_vm.cpp | 1212 | ||||
| -rw-r--r-- | src/core/memory/dmnt_cheat_vm.h | 321 |
15 files changed, 1962 insertions, 760 deletions
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a03179520..1111cfbad 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
| @@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) { | |||
| 255 | CLS(Input) \ | 255 | CLS(Input) \ |
| 256 | CLS(Network) \ | 256 | CLS(Network) \ |
| 257 | CLS(Loader) \ | 257 | CLS(Loader) \ |
| 258 | CLS(CheatEngine) \ | ||
| 258 | CLS(Crypto) \ | 259 | CLS(Crypto) \ |
| 259 | CLS(WebService) | 260 | CLS(WebService) |
| 260 | 261 | ||
diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 8ed6d5050..259708116 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h | |||
| @@ -117,6 +117,7 @@ enum class Class : ClassType { | |||
| 117 | Audio_DSP, ///< The HLE implementation of the DSP | 117 | Audio_DSP, ///< The HLE implementation of the DSP |
| 118 | Audio_Sink, ///< Emulator audio output backend | 118 | Audio_Sink, ///< Emulator audio output backend |
| 119 | Loader, ///< ROM loader | 119 | Loader, ///< ROM loader |
| 120 | CheatEngine, ///< Memory manipulation and engine VM functions | ||
| 120 | Crypto, ///< Cryptographic engine/functions | 121 | Crypto, ///< Cryptographic engine/functions |
| 121 | Input, ///< Input emulation | 122 | Input, ///< Input emulation |
| 122 | Network, ///< Network emulation | 123 | Network, ///< Network emulation |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7c142c011..2deaf560c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -33,8 +33,6 @@ add_library(core STATIC | |||
| 33 | file_sys/bis_factory.h | 33 | file_sys/bis_factory.h |
| 34 | file_sys/card_image.cpp | 34 | file_sys/card_image.cpp |
| 35 | file_sys/card_image.h | 35 | file_sys/card_image.h |
| 36 | file_sys/cheat_engine.cpp | ||
| 37 | file_sys/cheat_engine.h | ||
| 38 | file_sys/content_archive.cpp | 36 | file_sys/content_archive.cpp |
| 39 | file_sys/content_archive.h | 37 | file_sys/content_archive.h |
| 40 | file_sys/control_metadata.cpp | 38 | file_sys/control_metadata.cpp |
| @@ -491,6 +489,11 @@ add_library(core STATIC | |||
| 491 | loader/nsp.h | 489 | loader/nsp.h |
| 492 | loader/xci.cpp | 490 | loader/xci.cpp |
| 493 | loader/xci.h | 491 | loader/xci.h |
| 492 | memory/cheat_engine.cpp | ||
| 493 | memory/cheat_engine.h | ||
| 494 | memory/dmnt_cheat_types.h | ||
| 495 | memory/dmnt_cheat_vm.cpp | ||
| 496 | memory/dmnt_cheat_vm.h | ||
| 494 | memory.cpp | 497 | memory.cpp |
| 495 | memory.h | 498 | memory.h |
| 496 | memory_setup.h | 499 | memory_setup.h |
diff --git a/src/core/core.cpp b/src/core/core.cpp index f22244cf7..76bb2bae9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -17,6 +17,7 @@ | |||
| 17 | #include "core/file_sys/bis_factory.h" | 17 | #include "core/file_sys/bis_factory.h" |
| 18 | #include "core/file_sys/card_image.h" | 18 | #include "core/file_sys/card_image.h" |
| 19 | #include "core/file_sys/mode.h" | 19 | #include "core/file_sys/mode.h" |
| 20 | #include "core/file_sys/patch_manager.h" | ||
| 20 | #include "core/file_sys/registered_cache.h" | 21 | #include "core/file_sys/registered_cache.h" |
| 21 | #include "core/file_sys/romfs_factory.h" | 22 | #include "core/file_sys/romfs_factory.h" |
| 22 | #include "core/file_sys/savedata_factory.h" | 23 | #include "core/file_sys/savedata_factory.h" |
| @@ -37,13 +38,12 @@ | |||
| 37 | #include "core/hle/service/service.h" | 38 | #include "core/hle/service/service.h" |
| 38 | #include "core/hle/service/sm/sm.h" | 39 | #include "core/hle/service/sm/sm.h" |
| 39 | #include "core/loader/loader.h" | 40 | #include "core/loader/loader.h" |
| 41 | #include "core/memory/cheat_engine.h" | ||
| 40 | #include "core/perf_stats.h" | 42 | #include "core/perf_stats.h" |
| 41 | #include "core/reporter.h" | 43 | #include "core/reporter.h" |
| 42 | #include "core/settings.h" | 44 | #include "core/settings.h" |
| 43 | #include "core/telemetry_session.h" | 45 | #include "core/telemetry_session.h" |
| 44 | #include "core/tools/freezer.h" | 46 | #include "core/tools/freezer.h" |
| 45 | #include "file_sys/cheat_engine.h" | ||
| 46 | #include "file_sys/patch_manager.h" | ||
| 47 | #include "video_core/debug_utils/debug_utils.h" | 47 | #include "video_core/debug_utils/debug_utils.h" |
| 48 | #include "video_core/renderer_base.h" | 48 | #include "video_core/renderer_base.h" |
| 49 | #include "video_core/video_core.h" | 49 | #include "video_core/video_core.h" |
| @@ -204,6 +204,11 @@ struct System::Impl { | |||
| 204 | gpu_core->Start(); | 204 | gpu_core->Start(); |
| 205 | cpu_core_manager.StartThreads(); | 205 | cpu_core_manager.StartThreads(); |
| 206 | 206 | ||
| 207 | // Initialize cheat engine | ||
| 208 | if (cheat_engine) { | ||
| 209 | cheat_engine->Initialize(); | ||
| 210 | } | ||
| 211 | |||
| 207 | // All threads are started, begin main process execution, now that we're in the clear. | 212 | // All threads are started, begin main process execution, now that we're in the clear. |
| 208 | main_process->Run(load_parameters->main_thread_priority, | 213 | main_process->Run(load_parameters->main_thread_priority, |
| 209 | load_parameters->main_thread_stack_size); | 214 | load_parameters->main_thread_stack_size); |
| @@ -329,7 +334,7 @@ struct System::Impl { | |||
| 329 | CpuCoreManager cpu_core_manager; | 334 | CpuCoreManager cpu_core_manager; |
| 330 | bool is_powered_on = false; | 335 | bool is_powered_on = false; |
| 331 | 336 | ||
| 332 | std::unique_ptr<FileSys::CheatEngine> cheat_engine; | 337 | std::unique_ptr<Memory::CheatEngine> cheat_engine; |
| 333 | std::unique_ptr<Tools::Freezer> memory_freezer; | 338 | std::unique_ptr<Tools::Freezer> memory_freezer; |
| 334 | 339 | ||
| 335 | /// Frontend applets | 340 | /// Frontend applets |
| @@ -544,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { | |||
| 544 | return impl->debug_context.get(); | 549 | return impl->debug_context.get(); |
| 545 | } | 550 | } |
| 546 | 551 | ||
| 547 | void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list, | ||
| 548 | const std::string& build_id, VAddr code_region_start, | ||
| 549 | VAddr code_region_end) { | ||
| 550 | impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id, | ||
| 551 | code_region_start, code_region_end); | ||
| 552 | } | ||
| 553 | |||
| 554 | void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { | 552 | void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { |
| 555 | impl->virtual_filesystem = std::move(vfs); | 553 | impl->virtual_filesystem = std::move(vfs); |
| 556 | } | 554 | } |
| @@ -559,6 +557,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const { | |||
| 559 | return impl->virtual_filesystem; | 557 | return impl->virtual_filesystem; |
| 560 | } | 558 | } |
| 561 | 559 | ||
| 560 | void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list, | ||
| 561 | const std::array<u8, 32>& build_id, VAddr main_region_begin, | ||
| 562 | u64 main_region_size) { | ||
| 563 | impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id); | ||
| 564 | impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size); | ||
| 565 | } | ||
| 566 | |||
| 562 | void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { | 567 | void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { |
| 563 | impl->applet_manager.SetAppletFrontendSet(std::move(set)); | 568 | impl->applet_manager.SetAppletFrontendSet(std::move(set)); |
| 564 | } | 569 | } |
diff --git a/src/core/core.h b/src/core/core.h index bb2962fdd..d2a3c82d8 100644 --- a/src/core/core.h +++ b/src/core/core.h | |||
| @@ -18,7 +18,6 @@ class EmuWindow; | |||
| 18 | } // namespace Core::Frontend | 18 | } // namespace Core::Frontend |
| 19 | 19 | ||
| 20 | namespace FileSys { | 20 | namespace FileSys { |
| 21 | class CheatList; | ||
| 22 | class ContentProvider; | 21 | class ContentProvider; |
| 23 | class ContentProviderUnion; | 22 | class ContentProviderUnion; |
| 24 | enum class ContentProviderUnionSlot; | 23 | enum class ContentProviderUnionSlot; |
| @@ -36,6 +35,10 @@ class AppLoader; | |||
| 36 | enum class ResultStatus : u16; | 35 | enum class ResultStatus : u16; |
| 37 | } // namespace Loader | 36 | } // namespace Loader |
| 38 | 37 | ||
| 38 | namespace Memory { | ||
| 39 | struct CheatEntry; | ||
| 40 | } // namespace Memory | ||
| 41 | |||
| 39 | namespace Service { | 42 | namespace Service { |
| 40 | 43 | ||
| 41 | namespace AM::Applets { | 44 | namespace AM::Applets { |
| @@ -286,8 +289,9 @@ public: | |||
| 286 | 289 | ||
| 287 | std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; | 290 | std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; |
| 288 | 291 | ||
| 289 | void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id, | 292 | void RegisterCheatList(const std::vector<Memory::CheatEntry>& list, |
| 290 | VAddr code_region_start, VAddr code_region_end); | 293 | const std::array<u8, 0x20>& build_id, VAddr main_region_begin, |
| 294 | u64 main_region_size); | ||
| 291 | 295 | ||
| 292 | void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); | 296 | void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); |
| 293 | 297 | ||
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp deleted file mode 100644 index b06c2f20a..000000000 --- a/src/core/file_sys/cheat_engine.cpp +++ /dev/null | |||
| @@ -1,492 +0,0 @@ | |||
| 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/npad.h" | ||
| 15 | #include "core/hle/service/hid/hid.h" | ||
| 16 | #include "core/hle/service/sm/sm.h" | ||
| 17 | |||
| 18 | namespace FileSys { | ||
| 19 | |||
| 20 | constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); | ||
| 21 | constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; | ||
| 22 | |||
| 23 | u64 Cheat::Address() const { | ||
| 24 | u64 out; | ||
| 25 | std::memcpy(&out, raw.data(), sizeof(u64)); | ||
| 26 | return Common::swap64(out) & 0xFFFFFFFFFF; | ||
| 27 | } | ||
| 28 | |||
| 29 | u64 Cheat::ValueWidth(u64 offset) const { | ||
| 30 | return Value(offset, width); | ||
| 31 | } | ||
| 32 | |||
| 33 | u64 Cheat::Value(u64 offset, u64 width) const { | ||
| 34 | u64 out; | ||
| 35 | std::memcpy(&out, raw.data() + offset, sizeof(u64)); | ||
| 36 | out = Common::swap64(out); | ||
| 37 | if (width == 8) | ||
| 38 | return out; | ||
| 39 | return out & ((1ull << (width * CHAR_BIT)) - 1); | ||
| 40 | } | ||
| 41 | |||
| 42 | u32 Cheat::KeypadValue() const { | ||
| 43 | u32 out; | ||
| 44 | std::memcpy(&out, raw.data(), sizeof(u32)); | ||
| 45 | return Common::swap32(out) & 0x0FFFFFFF; | ||
| 46 | } | ||
| 47 | |||
| 48 | void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, | ||
| 49 | VAddr heap_end, MemoryWriter writer, MemoryReader reader) { | ||
| 50 | this->main_region_begin = main_begin; | ||
| 51 | this->main_region_end = main_end; | ||
| 52 | this->heap_region_begin = heap_begin; | ||
| 53 | this->heap_region_end = heap_end; | ||
| 54 | this->writer = writer; | ||
| 55 | this->reader = reader; | ||
| 56 | } | ||
| 57 | |||
| 58 | MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); | ||
| 59 | |||
| 60 | void CheatList::Execute() { | ||
| 61 | MICROPROFILE_SCOPE(Cheat_Engine); | ||
| 62 | |||
| 63 | std::fill(scratch.begin(), scratch.end(), 0); | ||
| 64 | in_standard = false; | ||
| 65 | for (std::size_t i = 0; i < master_list.size(); ++i) { | ||
| 66 | LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); | ||
| 67 | current_block = i; | ||
| 68 | ExecuteBlock(master_list[i].second); | ||
| 69 | } | ||
| 70 | |||
| 71 | in_standard = true; | ||
| 72 | for (std::size_t i = 0; i < standard_list.size(); ++i) { | ||
| 73 | LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); | ||
| 74 | current_block = i; | ||
| 75 | ExecuteBlock(standard_list[i].second); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) | ||
| 80 | : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} | ||
| 81 | |||
| 82 | bool CheatList::EvaluateConditional(const Cheat& cheat) const { | ||
| 83 | using ComparisonFunction = bool (*)(u64, u64); | ||
| 84 | constexpr std::array<ComparisonFunction, 6> comparison_functions{ | ||
| 85 | [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, | ||
| 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 | }; | ||
| 89 | |||
| 90 | if (cheat.type == CodeType::ConditionalInput) { | ||
| 91 | const auto applet_resource = | ||
| 92 | system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); | ||
| 93 | if (applet_resource == nullptr) { | ||
| 94 | LOG_WARNING( | ||
| 95 | Common_Filesystem, | ||
| 96 | "Attempted to evaluate input conditional, but applet resource is not initialized!"); | ||
| 97 | return false; | ||
| 98 | } | ||
| 99 | |||
| 100 | const auto press_state = | ||
| 101 | applet_resource | ||
| 102 | ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) | ||
| 103 | .GetAndResetPressState(); | ||
| 104 | return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; | ||
| 105 | } | ||
| 106 | |||
| 107 | ASSERT(cheat.type == CodeType::Conditional); | ||
| 108 | |||
| 109 | const auto offset = | ||
| 110 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 111 | ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); | ||
| 112 | auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; | ||
| 113 | const auto addr = cheat.Address() + offset; | ||
| 114 | |||
| 115 | return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); | ||
| 116 | } | ||
| 117 | |||
| 118 | void CheatList::ProcessBlockPairs(const Block& block) { | ||
| 119 | block_pairs.clear(); | ||
| 120 | |||
| 121 | u64 scope = 0; | ||
| 122 | std::map<u64, u64> pairs; | ||
| 123 | |||
| 124 | for (std::size_t i = 0; i < block.size(); ++i) { | ||
| 125 | const auto& cheat = block[i]; | ||
| 126 | |||
| 127 | switch (cheat.type) { | ||
| 128 | case CodeType::Conditional: | ||
| 129 | case CodeType::ConditionalInput: | ||
| 130 | pairs.insert_or_assign(scope, i); | ||
| 131 | ++scope; | ||
| 132 | break; | ||
| 133 | case CodeType::EndConditional: { | ||
| 134 | --scope; | ||
| 135 | const auto idx = pairs.at(scope); | ||
| 136 | block_pairs.insert_or_assign(idx, i); | ||
| 137 | break; | ||
| 138 | } | ||
| 139 | case CodeType::Loop: { | ||
| 140 | if (cheat.end_of_loop) { | ||
| 141 | --scope; | ||
| 142 | const auto idx = pairs.at(scope); | ||
| 143 | block_pairs.insert_or_assign(idx, i); | ||
| 144 | } else { | ||
| 145 | pairs.insert_or_assign(scope, i); | ||
| 146 | ++scope; | ||
| 147 | } | ||
| 148 | break; | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | } | ||
| 153 | |||
| 154 | void CheatList::WriteImmediate(const Cheat& cheat) { | ||
| 155 | const auto offset = | ||
| 156 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 157 | const auto& register_3 = scratch.at(cheat.register_3); | ||
| 158 | |||
| 159 | const auto addr = cheat.Address() + offset + register_3; | ||
| 160 | LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, | ||
| 161 | cheat.Value(8, cheat.width)); | ||
| 162 | writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); | ||
| 163 | } | ||
| 164 | |||
| 165 | void CheatList::BeginConditional(const Cheat& cheat) { | ||
| 166 | if (EvaluateConditional(cheat)) { | ||
| 167 | return; | ||
| 168 | } | ||
| 169 | |||
| 170 | const auto iter = block_pairs.find(current_index); | ||
| 171 | ASSERT(iter != block_pairs.end()); | ||
| 172 | current_index = iter->second - 1; | ||
| 173 | } | ||
| 174 | |||
| 175 | void CheatList::EndConditional(const Cheat& cheat) { | ||
| 176 | LOG_DEBUG(Common_Filesystem, "Ending conditional block."); | ||
| 177 | } | ||
| 178 | |||
| 179 | void CheatList::Loop(const Cheat& cheat) { | ||
| 180 | if (cheat.end_of_loop.Value()) | ||
| 181 | ASSERT(!cheat.end_of_loop.Value()); | ||
| 182 | |||
| 183 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 184 | const auto iter = block_pairs.find(current_index); | ||
| 185 | ASSERT(iter != block_pairs.end()); | ||
| 186 | ASSERT(iter->first < iter->second); | ||
| 187 | |||
| 188 | const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); | ||
| 189 | for (s32 i = initial_value; i >= 0; --i) { | ||
| 190 | register_3 = static_cast<u64>(i); | ||
| 191 | for (std::size_t c = iter->first + 1; c < iter->second; ++c) { | ||
| 192 | current_index = c; | ||
| 193 | ExecuteSingleCheat( | ||
| 194 | (in_standard ? standard_list : master_list)[current_block].second[c]); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | current_index = iter->second; | ||
| 199 | } | ||
| 200 | |||
| 201 | void CheatList::LoadImmediate(const Cheat& cheat) { | ||
| 202 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 203 | |||
| 204 | LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, | ||
| 205 | cheat.Value(4, 8)); | ||
| 206 | register_3 = cheat.Value(4, 8); | ||
| 207 | } | ||
| 208 | |||
| 209 | void CheatList::LoadIndexed(const Cheat& cheat) { | ||
| 210 | const auto offset = | ||
| 211 | cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; | ||
| 212 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 213 | |||
| 214 | const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); | ||
| 215 | LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", | ||
| 216 | cheat.register_3, addr); | ||
| 217 | register_3 = reader(cheat.width, SanitizeAddress(addr)); | ||
| 218 | } | ||
| 219 | |||
| 220 | void CheatList::StoreIndexed(const Cheat& cheat) { | ||
| 221 | const auto& register_3 = scratch.at(cheat.register_3); | ||
| 222 | |||
| 223 | const auto addr = | ||
| 224 | register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); | ||
| 225 | LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", | ||
| 226 | cheat.Value(4, cheat.width), addr); | ||
| 227 | writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); | ||
| 228 | } | ||
| 229 | |||
| 230 | void CheatList::RegisterArithmetic(const Cheat& cheat) { | ||
| 231 | using ArithmeticFunction = u64 (*)(u64, u64); | ||
| 232 | constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ | ||
| 233 | [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, | ||
| 234 | [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, | ||
| 235 | [](u64 a, u64 b) { return a >> b; }, | ||
| 236 | }; | ||
| 237 | |||
| 238 | using ArithmeticOverflowCheck = bool (*)(u64, u64); | ||
| 239 | constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ | ||
| 240 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b | ||
| 241 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b | ||
| 242 | [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b | ||
| 243 | [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b | ||
| 244 | [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b | ||
| 245 | }; | ||
| 246 | |||
| 247 | static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), | ||
| 248 | "Missing or have extra arithmetic overflow checks compared to functions!"); | ||
| 249 | |||
| 250 | auto& register_3 = scratch.at(cheat.register_3); | ||
| 251 | |||
| 252 | ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); | ||
| 253 | auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; | ||
| 254 | auto* overflow_function = | ||
| 255 | arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; | ||
| 256 | LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", | ||
| 257 | cheat.register_3, cheat.ValueWidth(4)); | ||
| 258 | |||
| 259 | if (overflow_function(register_3, cheat.ValueWidth(4))) { | ||
| 260 | LOG_WARNING(Common_Filesystem, | ||
| 261 | "overflow will occur when performing arithmetic operation={:02X} with operands " | ||
| 262 | "a={:016X}, b={:016X}!", | ||
| 263 | static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); | ||
| 264 | } | ||
| 265 | |||
| 266 | register_3 = function(register_3, cheat.ValueWidth(4)); | ||
| 267 | } | ||
| 268 | |||
| 269 | void CheatList::BeginConditionalInput(const Cheat& cheat) { | ||
| 270 | if (EvaluateConditional(cheat)) | ||
| 271 | return; | ||
| 272 | |||
| 273 | const auto iter = block_pairs.find(current_index); | ||
| 274 | ASSERT(iter != block_pairs.end()); | ||
| 275 | current_index = iter->second - 1; | ||
| 276 | } | ||
| 277 | |||
| 278 | VAddr CheatList::SanitizeAddress(VAddr in) const { | ||
| 279 | if ((in < main_region_begin || in >= main_region_end) && | ||
| 280 | (in < heap_region_begin || in >= heap_region_end)) { | ||
| 281 | LOG_ERROR(Common_Filesystem, | ||
| 282 | "Cheat attempting to access memory at invalid address={:016X}, if this persists, " | ||
| 283 | "the cheat may be incorrect. However, this may be normal early in execution if " | ||
| 284 | "the game has not properly set up yet.", | ||
| 285 | in); | ||
| 286 | return 0; ///< Invalid addresses will hard crash | ||
| 287 | } | ||
| 288 | |||
| 289 | return in; | ||
| 290 | } | ||
| 291 | |||
| 292 | void CheatList::ExecuteSingleCheat(const Cheat& cheat) { | ||
| 293 | using CheatOperationFunction = void (CheatList::*)(const Cheat&); | ||
| 294 | constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ | ||
| 295 | &CheatList::WriteImmediate, &CheatList::BeginConditional, | ||
| 296 | &CheatList::EndConditional, &CheatList::Loop, | ||
| 297 | &CheatList::LoadImmediate, &CheatList::LoadIndexed, | ||
| 298 | &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, | ||
| 299 | &CheatList::BeginConditionalInput, | ||
| 300 | }; | ||
| 301 | |||
| 302 | const auto index = static_cast<u8>(cheat.type.Value()); | ||
| 303 | ASSERT(index < sizeof(cheat_operation_functions)); | ||
| 304 | const auto op = cheat_operation_functions[index]; | ||
| 305 | (this->*op)(cheat); | ||
| 306 | } | ||
| 307 | |||
| 308 | void CheatList::ExecuteBlock(const Block& block) { | ||
| 309 | encountered_loops.clear(); | ||
| 310 | |||
| 311 | ProcessBlockPairs(block); | ||
| 312 | for (std::size_t i = 0; i < block.size(); ++i) { | ||
| 313 | current_index = i; | ||
| 314 | ExecuteSingleCheat(block[i]); | ||
| 315 | i = current_index; | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | CheatParser::~CheatParser() = default; | ||
| 320 | |||
| 321 | CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, | ||
| 322 | CheatList::ProgramSegment standard) const { | ||
| 323 | return {system, std::move(master), std::move(standard)}; | ||
| 324 | } | ||
| 325 | |||
| 326 | TextCheatParser::~TextCheatParser() = default; | ||
| 327 | |||
| 328 | CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { | ||
| 329 | std::stringstream ss; | ||
| 330 | ss.write(reinterpret_cast<const char*>(data.data()), data.size()); | ||
| 331 | |||
| 332 | std::vector<std::string> lines; | ||
| 333 | std::string stream_line; | ||
| 334 | while (std::getline(ss, stream_line)) { | ||
| 335 | // Remove a trailing \r | ||
| 336 | if (!stream_line.empty() && stream_line.back() == '\r') | ||
| 337 | stream_line.pop_back(); | ||
| 338 | lines.push_back(std::move(stream_line)); | ||
| 339 | } | ||
| 340 | |||
| 341 | CheatList::ProgramSegment master_list; | ||
| 342 | CheatList::ProgramSegment standard_list; | ||
| 343 | |||
| 344 | for (std::size_t i = 0; i < lines.size(); ++i) { | ||
| 345 | auto line = lines[i]; | ||
| 346 | |||
| 347 | if (!line.empty() && (line[0] == '[' || line[0] == '{')) { | ||
| 348 | const auto master = line[0] == '{'; | ||
| 349 | const auto begin = master ? line.find('{') : line.find('['); | ||
| 350 | const auto end = master ? line.rfind('}') : line.rfind(']'); | ||
| 351 | |||
| 352 | ASSERT(begin != std::string::npos && end != std::string::npos); | ||
| 353 | |||
| 354 | const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; | ||
| 355 | CheatList::Block block{}; | ||
| 356 | |||
| 357 | while (i < lines.size() - 1) { | ||
| 358 | line = lines[++i]; | ||
| 359 | if (!line.empty() && (line[0] == '[' || line[0] == '{')) { | ||
| 360 | --i; | ||
| 361 | break; | ||
| 362 | } | ||
| 363 | |||
| 364 | if (line.size() < 8) | ||
| 365 | continue; | ||
| 366 | |||
| 367 | Cheat out{}; | ||
| 368 | out.raw = ParseSingleLineCheat(line); | ||
| 369 | block.push_back(out); | ||
| 370 | } | ||
| 371 | |||
| 372 | (master ? master_list : standard_list).emplace_back(patch_name, block); | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | return MakeCheatList(system, master_list, standard_list); | ||
| 377 | } | ||
| 378 | |||
| 379 | std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { | ||
| 380 | std::array<u8, 16> out{}; | ||
| 381 | |||
| 382 | if (line.size() < 8) | ||
| 383 | return out; | ||
| 384 | |||
| 385 | const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); | ||
| 386 | std::memcpy(out.data(), word1.data(), sizeof(u32)); | ||
| 387 | |||
| 388 | if (line.size() < 17 || line[8] != ' ') | ||
| 389 | return out; | ||
| 390 | |||
| 391 | const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); | ||
| 392 | std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); | ||
| 393 | |||
| 394 | if (line.size() < 26 || line[17] != ' ') { | ||
| 395 | // Perform shifting in case value is truncated early. | ||
| 396 | const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); | ||
| 397 | if (type == CodeType::Loop || type == CodeType::LoadImmediate || | ||
| 398 | type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { | ||
| 399 | std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); | ||
| 400 | std::memset(out.data() + 4, 0, sizeof(u32)); | ||
| 401 | } | ||
| 402 | |||
| 403 | return out; | ||
| 404 | } | ||
| 405 | |||
| 406 | const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); | ||
| 407 | std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); | ||
| 408 | |||
| 409 | if (line.size() < 35 || line[26] != ' ') { | ||
| 410 | // Perform shifting in case value is truncated early. | ||
| 411 | const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); | ||
| 412 | if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { | ||
| 413 | std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); | ||
| 414 | std::memset(out.data() + 8, 0, sizeof(u32)); | ||
| 415 | } | ||
| 416 | |||
| 417 | return out; | ||
| 418 | } | ||
| 419 | |||
| 420 | const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); | ||
| 421 | std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); | ||
| 422 | |||
| 423 | return out; | ||
| 424 | } | ||
| 425 | |||
| 426 | namespace { | ||
| 427 | u64 MemoryReadImpl(u32 width, VAddr addr) { | ||
| 428 | switch (width) { | ||
| 429 | case 1: | ||
| 430 | return Memory::Read8(addr); | ||
| 431 | case 2: | ||
| 432 | return Memory::Read16(addr); | ||
| 433 | case 4: | ||
| 434 | return Memory::Read32(addr); | ||
| 435 | case 8: | ||
| 436 | return Memory::Read64(addr); | ||
| 437 | default: | ||
| 438 | UNREACHABLE(); | ||
| 439 | return 0; | ||
| 440 | } | ||
| 441 | } | ||
| 442 | |||
| 443 | void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { | ||
| 444 | switch (width) { | ||
| 445 | case 1: | ||
| 446 | Memory::Write8(addr, static_cast<u8>(value)); | ||
| 447 | break; | ||
| 448 | case 2: | ||
| 449 | Memory::Write16(addr, static_cast<u16>(value)); | ||
| 450 | break; | ||
| 451 | case 4: | ||
| 452 | Memory::Write32(addr, static_cast<u32>(value)); | ||
| 453 | break; | ||
| 454 | case 8: | ||
| 455 | Memory::Write64(addr, value); | ||
| 456 | break; | ||
| 457 | default: | ||
| 458 | UNREACHABLE(); | ||
| 459 | } | ||
| 460 | } | ||
| 461 | } // Anonymous namespace | ||
| 462 | |||
| 463 | CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, | ||
| 464 | const std::string& build_id, VAddr code_region_start, | ||
| 465 | VAddr code_region_end) | ||
| 466 | : cheats{std::move(cheats_)}, core_timing{system.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 = system.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 | core_timing.UnscheduleEvent(event, 0); | ||
| 482 | } | ||
| 483 | |||
| 484 | void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { | ||
| 485 | for (auto& list : cheats) { | ||
| 486 | list.Execute(); | ||
| 487 | } | ||
| 488 | |||
| 489 | core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); | ||
| 490 | } | ||
| 491 | |||
| 492 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h deleted file mode 100644 index ac22a82cb..000000000 --- a/src/core/file_sys/cheat_engine.h +++ /dev/null | |||
| @@ -1,234 +0,0 @@ | |||
| 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 "common/bit_field.h" | ||
| 11 | #include "common/common_types.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 FileSys { | ||
| 23 | |||
| 24 | enum class CodeType : u32 { | ||
| 25 | // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY | ||
| 26 | // Writes a T sized value Y to the address A added to the value of register R in memory domain M | ||
| 27 | WriteImmediate = 0, | ||
| 28 | |||
| 29 | // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY | ||
| 30 | // Compares the T sized value Y to the value at address A in memory domain M using the | ||
| 31 | // conditional function C. If success, continues execution. If failure, jumps to the matching | ||
| 32 | // EndConditional statement. | ||
| 33 | Conditional = 1, | ||
| 34 | |||
| 35 | // 20000000 | ||
| 36 | // Terminates a Conditional or ConditionalInput block. | ||
| 37 | EndConditional = 2, | ||
| 38 | |||
| 39 | // 300R0000 VVVVVVVV | ||
| 40 | // Starts looping V times, storing the current count in register R. | ||
| 41 | // Loop block is terminated with a matching 310R0000. | ||
| 42 | Loop = 3, | ||
| 43 | |||
| 44 | // 400R0000 VVVVVVVV VVVVVVVV | ||
| 45 | // Sets the value of register R to the value V. | ||
| 46 | LoadImmediate = 4, | ||
| 47 | |||
| 48 | // 5TMRI0AA AAAAAAAA | ||
| 49 | // Sets the value of register R to the value of width T at address A in memory domain M, with | ||
| 50 | // the current value of R added to the address if I == 1. | ||
| 51 | LoadIndexed = 5, | ||
| 52 | |||
| 53 | // 6T0RIFG0 VVVVVVVV VVVVVVVV | ||
| 54 | // Writes the value V of width T to the memory address stored in register R. Adds the value of | ||
| 55 | // register G to the final calculation if F is nonzero. Increments the value of register R by T | ||
| 56 | // after operation if I is nonzero. | ||
| 57 | StoreIndexed = 6, | ||
| 58 | |||
| 59 | // 7T0RA000 VVVVVVVV | ||
| 60 | // Performs the arithmetic operation A on the value in register R and the value V of width T, | ||
| 61 | // storing the result in register R. | ||
| 62 | RegisterArithmetic = 7, | ||
| 63 | |||
| 64 | // 8KKKKKKK | ||
| 65 | // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, | ||
| 66 | // execution continues. If none are, execution skips to the next EndConditional command. | ||
| 67 | ConditionalInput = 8, | ||
| 68 | }; | ||
| 69 | |||
| 70 | enum class MemoryType : u32 { | ||
| 71 | // Addressed relative to start of main NSO | ||
| 72 | MainNSO = 0, | ||
| 73 | |||
| 74 | // Addressed relative to start of heap | ||
| 75 | Heap = 1, | ||
| 76 | }; | ||
| 77 | |||
| 78 | enum class ArithmeticOp : u32 { | ||
| 79 | Add = 0, | ||
| 80 | Sub = 1, | ||
| 81 | Mult = 2, | ||
| 82 | LShift = 3, | ||
| 83 | RShift = 4, | ||
| 84 | }; | ||
| 85 | |||
| 86 | enum class ComparisonOp : u32 { | ||
| 87 | GreaterThan = 1, | ||
| 88 | GreaterThanEqual = 2, | ||
| 89 | LessThan = 3, | ||
| 90 | LessThanEqual = 4, | ||
| 91 | Equal = 5, | ||
| 92 | Inequal = 6, | ||
| 93 | }; | ||
| 94 | |||
| 95 | union Cheat { | ||
| 96 | std::array<u8, 16> raw; | ||
| 97 | |||
| 98 | BitField<4, 4, CodeType> type; | ||
| 99 | BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. | ||
| 100 | BitField<0, 4, u32> end_of_loop; | ||
| 101 | BitField<12, 4, MemoryType> memory_type; | ||
| 102 | BitField<8, 4, u32> register_3; | ||
| 103 | BitField<8, 4, ComparisonOp> comparison_op; | ||
| 104 | BitField<20, 4, u32> load_from_register; | ||
| 105 | BitField<20, 4, u32> increment_register; | ||
| 106 | BitField<20, 4, ArithmeticOp> arithmetic_op; | ||
| 107 | BitField<16, 4, u32> add_additional_register; | ||
| 108 | BitField<28, 4, u32> register_6; | ||
| 109 | |||
| 110 | u64 Address() const; | ||
| 111 | u64 ValueWidth(u64 offset) const; | ||
| 112 | u64 Value(u64 offset, u64 width) const; | ||
| 113 | u32 KeypadValue() const; | ||
| 114 | }; | ||
| 115 | |||
| 116 | class CheatParser; | ||
| 117 | |||
| 118 | // Represents a full collection of cheats for a game. The Execute function should be called every | ||
| 119 | // interval that all cheats should be executed. Clients should not directly instantiate this class | ||
| 120 | // (hence private constructor), they should instead receive an instance from CheatParser, which | ||
| 121 | // guarantees the list is always in an acceptable state. | ||
| 122 | class CheatList { | ||
| 123 | public: | ||
| 124 | friend class CheatParser; | ||
| 125 | |||
| 126 | using Block = std::vector<Cheat>; | ||
| 127 | using ProgramSegment = std::vector<std::pair<std::string, Block>>; | ||
| 128 | |||
| 129 | // (width in bytes, address, value) | ||
| 130 | using MemoryWriter = void (*)(u32, VAddr, u64); | ||
| 131 | // (width in bytes, address) -> value | ||
| 132 | using MemoryReader = u64 (*)(u32, VAddr); | ||
| 133 | |||
| 134 | void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, | ||
| 135 | MemoryWriter writer, MemoryReader reader); | ||
| 136 | |||
| 137 | void Execute(); | ||
| 138 | |||
| 139 | private: | ||
| 140 | CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); | ||
| 141 | |||
| 142 | void ProcessBlockPairs(const Block& block); | ||
| 143 | void ExecuteSingleCheat(const Cheat& cheat); | ||
| 144 | |||
| 145 | void ExecuteBlock(const Block& block); | ||
| 146 | |||
| 147 | bool EvaluateConditional(const Cheat& cheat) const; | ||
| 148 | |||
| 149 | // Individual cheat operations | ||
| 150 | void WriteImmediate(const Cheat& cheat); | ||
| 151 | void BeginConditional(const Cheat& cheat); | ||
| 152 | void EndConditional(const Cheat& cheat); | ||
| 153 | void Loop(const Cheat& cheat); | ||
| 154 | void LoadImmediate(const Cheat& cheat); | ||
| 155 | void LoadIndexed(const Cheat& cheat); | ||
| 156 | void StoreIndexed(const Cheat& cheat); | ||
| 157 | void RegisterArithmetic(const Cheat& cheat); | ||
| 158 | void BeginConditionalInput(const Cheat& cheat); | ||
| 159 | |||
| 160 | VAddr SanitizeAddress(VAddr in) const; | ||
| 161 | |||
| 162 | // Master Codes are defined as codes that cannot be disabled and are run prior to all | ||
| 163 | // others. | ||
| 164 | ProgramSegment master_list; | ||
| 165 | // All other codes | ||
| 166 | ProgramSegment standard_list; | ||
| 167 | |||
| 168 | bool in_standard = false; | ||
| 169 | |||
| 170 | // 16 (0x0-0xF) scratch registers that can be used by cheats | ||
| 171 | std::array<u64, 16> scratch{}; | ||
| 172 | |||
| 173 | MemoryWriter writer = nullptr; | ||
| 174 | MemoryReader reader = nullptr; | ||
| 175 | |||
| 176 | u64 main_region_begin{}; | ||
| 177 | u64 heap_region_begin{}; | ||
| 178 | u64 main_region_end{}; | ||
| 179 | u64 heap_region_end{}; | ||
| 180 | |||
| 181 | u64 current_block{}; | ||
| 182 | // The current index of the cheat within the current Block | ||
| 183 | u64 current_index{}; | ||
| 184 | |||
| 185 | // The 'stack' of the program. When a conditional or loop statement is encountered, its index is | ||
| 186 | // pushed onto this queue. When a end block is encountered, the condition is checked. | ||
| 187 | std::map<u64, u64> block_pairs; | ||
| 188 | |||
| 189 | std::set<u64> encountered_loops; | ||
| 190 | |||
| 191 | const Core::System* system; | ||
| 192 | }; | ||
| 193 | |||
| 194 | // Intermediary class that parses a text file or other disk format for storing cheats into a | ||
| 195 | // CheatList object, that can be used for execution. | ||
| 196 | class CheatParser { | ||
| 197 | public: | ||
| 198 | virtual ~CheatParser(); | ||
| 199 | |||
| 200 | virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; | ||
| 201 | |||
| 202 | protected: | ||
| 203 | CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, | ||
| 204 | CheatList::ProgramSegment standard) const; | ||
| 205 | }; | ||
| 206 | |||
| 207 | // CheatParser implementation that parses text files | ||
| 208 | class TextCheatParser final : public CheatParser { | ||
| 209 | public: | ||
| 210 | ~TextCheatParser() override; | ||
| 211 | |||
| 212 | CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; | ||
| 213 | |||
| 214 | private: | ||
| 215 | std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; | ||
| 216 | }; | ||
| 217 | |||
| 218 | // Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming | ||
| 219 | class CheatEngine final { | ||
| 220 | public: | ||
| 221 | CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, | ||
| 222 | VAddr code_region_start, VAddr code_region_end); | ||
| 223 | ~CheatEngine(); | ||
| 224 | |||
| 225 | private: | ||
| 226 | void FrameCallback(u64 userdata, s64 cycles_late); | ||
| 227 | |||
| 228 | std::vector<CheatList> cheats; | ||
| 229 | |||
| 230 | Core::Timing::EventType* event; | ||
| 231 | Core::Timing::CoreTiming& core_timing; | ||
| 232 | }; | ||
| 233 | |||
| 234 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index c1dd0c6d7..df0ecb15c 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp | |||
| @@ -22,6 +22,7 @@ | |||
| 22 | #include "core/hle/service/filesystem/filesystem.h" | 22 | #include "core/hle/service/filesystem/filesystem.h" |
| 23 | #include "core/loader/loader.h" | 23 | #include "core/loader/loader.h" |
| 24 | #include "core/loader/nso.h" | 24 | #include "core/loader/nso.h" |
| 25 | #include "core/memory/cheat_engine.h" | ||
| 25 | #include "core/settings.h" | 26 | #include "core/settings.h" |
| 26 | 27 | ||
| 27 | namespace FileSys { | 28 | namespace FileSys { |
| @@ -247,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { | |||
| 247 | return !CollectPatches(patch_dirs, build_id).empty(); | 248 | return !CollectPatches(patch_dirs, build_id).empty(); |
| 248 | } | 249 | } |
| 249 | 250 | ||
| 250 | static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, | 251 | namespace { |
| 251 | const std::array<u8, 0x20>& build_id_, | 252 | std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder( |
| 252 | const VirtualDir& base_path, bool upper) { | 253 | const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_, |
| 254 | const VirtualDir& base_path, bool upper) { | ||
| 253 | const auto build_id_raw = Common::HexToString(build_id_, upper); | 255 | const auto build_id_raw = Common::HexToString(build_id_, upper); |
| 254 | const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); | 256 | const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); |
| 255 | const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); | 257 | const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); |
| @@ -267,14 +269,16 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst | |||
| 267 | return std::nullopt; | 269 | return std::nullopt; |
| 268 | } | 270 | } |
| 269 | 271 | ||
| 270 | TextCheatParser parser; | 272 | Memory::TextCheatParser parser; |
| 271 | return parser.Parse(system, data); | 273 | return parser.Parse( |
| 274 | system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size())); | ||
| 272 | } | 275 | } |
| 273 | 276 | ||
| 274 | std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, | 277 | } // Anonymous namespace |
| 275 | const std::array<u8, 32>& build_id_) const { | 278 | |
| 276 | const auto load_dir = | 279 | std::vector<Memory::CheatEntry> PatchManager::CreateCheatList( |
| 277 | Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); | 280 | const Core::System& system, const std::array<u8, 32>& build_id_) const { |
| 281 | const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); | ||
| 278 | if (load_dir == nullptr) { | 282 | if (load_dir == nullptr) { |
| 279 | LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); | 283 | LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); |
| 280 | return {}; | 284 | return {}; |
| @@ -284,20 +288,20 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system, | |||
| 284 | std::sort(patch_dirs.begin(), patch_dirs.end(), | 288 | std::sort(patch_dirs.begin(), patch_dirs.end(), |
| 285 | [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); | 289 | [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); |
| 286 | 290 | ||
| 287 | std::vector<CheatList> out; | 291 | std::vector<Memory::CheatEntry> out; |
| 288 | out.reserve(patch_dirs.size()); | ||
| 289 | for (const auto& subdir : patch_dirs) { | 292 | for (const auto& subdir : patch_dirs) { |
| 290 | auto cheats_dir = subdir->GetSubdirectory("cheats"); | 293 | auto cheats_dir = subdir->GetSubdirectory("cheats"); |
| 291 | if (cheats_dir != nullptr) { | 294 | if (cheats_dir != nullptr) { |
| 292 | auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); | 295 | auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); |
| 293 | if (res.has_value()) { | 296 | if (res.has_value()) { |
| 294 | out.push_back(std::move(*res)); | 297 | std::copy(res->begin(), res->end(), std::back_inserter(out)); |
| 295 | continue; | 298 | continue; |
| 296 | } | 299 | } |
| 297 | 300 | ||
| 298 | res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); | 301 | res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); |
| 299 | if (res.has_value()) | 302 | if (res.has_value()) { |
| 300 | out.push_back(std::move(*res)); | 303 | std::copy(res->begin(), res->end(), std::back_inserter(out)); |
| 304 | } | ||
| 301 | } | 305 | } |
| 302 | } | 306 | } |
| 303 | 307 | ||
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index a363c6577..e857e6e82 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h | |||
| @@ -8,9 +8,9 @@ | |||
| 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" | ||
| 12 | #include "core/file_sys/nca_metadata.h" | 11 | #include "core/file_sys/nca_metadata.h" |
| 13 | #include "core/file_sys/vfs.h" | 12 | #include "core/file_sys/vfs.h" |
| 13 | #include "core/memory/dmnt_cheat_types.h" | ||
| 14 | 14 | ||
| 15 | namespace Core { | 15 | namespace Core { |
| 16 | class System; | 16 | class System; |
| @@ -51,8 +51,8 @@ public: | |||
| 51 | bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; | 51 | bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; |
| 52 | 52 | ||
| 53 | // Creates a CheatList object with all | 53 | // Creates a CheatList object with all |
| 54 | std::vector<CheatList> CreateCheatList(const Core::System& system, | 54 | std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system, |
| 55 | const std::array<u8, 0x20>& build_id) const; | 55 | const std::array<u8, 0x20>& build_id) const; |
| 56 | 56 | ||
| 57 | // Currently tracked RomFS patches: | 57 | // Currently tracked RomFS patches: |
| 58 | // - Game Updates | 58 | // - Game Updates |
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 70c90109f..e75c700ad 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp | |||
| @@ -152,8 +152,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process, | |||
| 152 | auto& system = Core::System::GetInstance(); | 152 | auto& system = Core::System::GetInstance(); |
| 153 | const auto cheats = pm->CreateCheatList(system, nso_header.build_id); | 153 | const auto cheats = pm->CreateCheatList(system, nso_header.build_id); |
| 154 | if (!cheats.empty()) { | 154 | if (!cheats.empty()) { |
| 155 | system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base, | 155 | system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); |
| 156 | load_base + program_image.size()); | ||
| 157 | } | 156 | } |
| 158 | } | 157 | } |
| 159 | 158 | ||
diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp new file mode 100644 index 000000000..b56cb0627 --- /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 (::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 (::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::HexToString(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 | ||
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h new file mode 100644 index 000000000..bf68fa0fe --- /dev/null +++ b/src/core/memory/dmnt_cheat_types.h | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2018-2019 Atmosphère-NX | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify it | ||
| 5 | * under the terms and conditions of the GNU General Public License, | ||
| 6 | * version 2, as published by the Free Software Foundation. | ||
| 7 | * | ||
| 8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
| 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 11 | * more details. | ||
| 12 | * | ||
| 13 | * You should have received a copy of the GNU General Public License | ||
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 15 | */ | ||
| 16 | |||
| 17 | /* | ||
| 18 | * Adapted by DarkLordZach for use/interaction with yuzu | ||
| 19 | * | ||
| 20 | * Modifications Copyright 2019 yuzu emulator team | ||
| 21 | * Licensed under GPLv2 or any later version | ||
| 22 | * Refer to the license.txt file included. | ||
| 23 | */ | ||
| 24 | |||
| 25 | #pragma once | ||
| 26 | |||
| 27 | #include "common/common_types.h" | ||
| 28 | |||
| 29 | namespace Memory { | ||
| 30 | |||
| 31 | struct MemoryRegionExtents { | ||
| 32 | u64 base{}; | ||
| 33 | u64 size{}; | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct CheatProcessMetadata { | ||
| 37 | u64 process_id{}; | ||
| 38 | u64 title_id{}; | ||
| 39 | MemoryRegionExtents main_nso_extents{}; | ||
| 40 | MemoryRegionExtents heap_extents{}; | ||
| 41 | MemoryRegionExtents alias_extents{}; | ||
| 42 | MemoryRegionExtents address_space_extents{}; | ||
| 43 | std::array<u8, 0x20> main_nso_build_id{}; | ||
| 44 | }; | ||
| 45 | |||
| 46 | struct CheatDefinition { | ||
| 47 | std::array<char, 0x40> readable_name{}; | ||
| 48 | u32 num_opcodes{}; | ||
| 49 | std::array<u32, 0x100> opcodes{}; | ||
| 50 | }; | ||
| 51 | |||
| 52 | struct CheatEntry { | ||
| 53 | bool enabled{}; | ||
| 54 | u32 cheat_id{}; | ||
| 55 | CheatDefinition definition{}; | ||
| 56 | }; | ||
| 57 | |||
| 58 | } // namespace Memory | ||
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp new file mode 100644 index 000000000..cc16d15a4 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.cpp | |||
| @@ -0,0 +1,1212 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2018-2019 Atmosphère-NX | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify it | ||
| 5 | * under the terms and conditions of the GNU General Public License, | ||
| 6 | * version 2, as published by the Free Software Foundation. | ||
| 7 | * | ||
| 8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
| 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 11 | * more details. | ||
| 12 | * | ||
| 13 | * You should have received a copy of the GNU General Public License | ||
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 15 | */ | ||
| 16 | |||
| 17 | /* | ||
| 18 | * Adapted by DarkLordZach for use/interaction with yuzu | ||
| 19 | * | ||
| 20 | * Modifications Copyright 2019 yuzu emulator team | ||
| 21 | * Licensed under GPLv2 or any later version | ||
| 22 | * Refer to the license.txt file included. | ||
| 23 | */ | ||
| 24 | |||
| 25 | #include "common/assert.h" | ||
| 26 | #include "common/scope_exit.h" | ||
| 27 | #include "core/memory/dmnt_cheat_types.h" | ||
| 28 | #include "core/memory/dmnt_cheat_vm.h" | ||
| 29 | |||
| 30 | namespace Memory { | ||
| 31 | |||
| 32 | DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {} | ||
| 33 | |||
| 34 | DmntCheatVm::~DmntCheatVm() = default; | ||
| 35 | |||
| 36 | void DmntCheatVm::DebugLog(u32 log_id, u64 value) { | ||
| 37 | callbacks->DebugLog(static_cast<u8>(log_id), value); | ||
| 38 | } | ||
| 39 | |||
| 40 | void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { | ||
| 41 | if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) { | ||
| 42 | callbacks->CommandLog("Opcode: Store Static"); | ||
| 43 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); | ||
| 44 | callbacks->CommandLog( | ||
| 45 | fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type))); | ||
| 46 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); | ||
| 47 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); | ||
| 48 | callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); | ||
| 49 | } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) { | ||
| 50 | callbacks->CommandLog("Opcode: Begin Conditional"); | ||
| 51 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); | ||
| 52 | callbacks->CommandLog( | ||
| 53 | fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type))); | ||
| 54 | callbacks->CommandLog( | ||
| 55 | fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type))); | ||
| 56 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); | ||
| 57 | callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); | ||
| 58 | } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) { | ||
| 59 | callbacks->CommandLog("Opcode: End Conditional"); | ||
| 60 | } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) { | ||
| 61 | if (ctrl_loop->start_loop) { | ||
| 62 | callbacks->CommandLog("Opcode: Start Loop"); | ||
| 63 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); | ||
| 64 | callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); | ||
| 65 | } else { | ||
| 66 | callbacks->CommandLog("Opcode: End Loop"); | ||
| 67 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); | ||
| 68 | } | ||
| 69 | } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) { | ||
| 70 | callbacks->CommandLog("Opcode: Load Register Static"); | ||
| 71 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); | ||
| 72 | callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value)); | ||
| 73 | } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) { | ||
| 74 | callbacks->CommandLog("Opcode: Load Register Memory"); | ||
| 75 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); | ||
| 76 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); | ||
| 77 | callbacks->CommandLog( | ||
| 78 | fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type))); | ||
| 79 | callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); | ||
| 80 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); | ||
| 81 | } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) { | ||
| 82 | callbacks->CommandLog("Opcode: Store Static to Address"); | ||
| 83 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); | ||
| 84 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); | ||
| 85 | if (str_static->add_offset_reg) { | ||
| 86 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index)); | ||
| 87 | } | ||
| 88 | callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); | ||
| 89 | callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value)); | ||
| 90 | } else if (auto perform_math_static = | ||
| 91 | std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) { | ||
| 92 | callbacks->CommandLog("Opcode: Perform Static Arithmetic"); | ||
| 93 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); | ||
| 94 | callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); | ||
| 95 | callbacks->CommandLog( | ||
| 96 | fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type))); | ||
| 97 | callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); | ||
| 98 | } else if (auto begin_keypress_cond = | ||
| 99 | std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) { | ||
| 100 | callbacks->CommandLog("Opcode: Begin Keypress Conditional"); | ||
| 101 | callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); | ||
| 102 | } else if (auto perform_math_reg = | ||
| 103 | std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) { | ||
| 104 | callbacks->CommandLog("Opcode: Perform Register Arithmetic"); | ||
| 105 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); | ||
| 106 | callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); | ||
| 107 | callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); | ||
| 108 | if (perform_math_reg->has_immediate) { | ||
| 109 | callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); | ||
| 110 | } else { | ||
| 111 | callbacks->CommandLog( | ||
| 112 | fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index)); | ||
| 113 | } | ||
| 114 | } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) { | ||
| 115 | callbacks->CommandLog("Opcode: Store Register to Address"); | ||
| 116 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); | ||
| 117 | callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); | ||
| 118 | callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); | ||
| 119 | callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); | ||
| 120 | switch (str_register->ofs_type) { | ||
| 121 | case StoreRegisterOffsetType::None: | ||
| 122 | break; | ||
| 123 | case StoreRegisterOffsetType::Reg: | ||
| 124 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); | ||
| 125 | break; | ||
| 126 | case StoreRegisterOffsetType::Imm: | ||
| 127 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); | ||
| 128 | break; | ||
| 129 | case StoreRegisterOffsetType::MemReg: | ||
| 130 | callbacks->CommandLog( | ||
| 131 | fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type))); | ||
| 132 | break; | ||
| 133 | case StoreRegisterOffsetType::MemImm: | ||
| 134 | case StoreRegisterOffsetType::MemImmReg: | ||
| 135 | callbacks->CommandLog( | ||
| 136 | fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type))); | ||
| 137 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); | ||
| 138 | break; | ||
| 139 | } | ||
| 140 | } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) { | ||
| 141 | callbacks->CommandLog("Opcode: Begin Register Conditional"); | ||
| 142 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); | ||
| 143 | callbacks->CommandLog( | ||
| 144 | fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type))); | ||
| 145 | callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); | ||
| 146 | switch (begin_reg_cond->comp_type) { | ||
| 147 | case CompareRegisterValueType::StaticValue: | ||
| 148 | callbacks->CommandLog("Comp Type: Static Value"); | ||
| 149 | callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64)); | ||
| 150 | break; | ||
| 151 | case CompareRegisterValueType::OtherRegister: | ||
| 152 | callbacks->CommandLog("Comp Type: Other Register"); | ||
| 153 | callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index)); | ||
| 154 | break; | ||
| 155 | case CompareRegisterValueType::MemoryRelAddr: | ||
| 156 | callbacks->CommandLog("Comp Type: Memory Relative Address"); | ||
| 157 | callbacks->CommandLog( | ||
| 158 | fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type))); | ||
| 159 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); | ||
| 160 | break; | ||
| 161 | case CompareRegisterValueType::MemoryOfsReg: | ||
| 162 | callbacks->CommandLog("Comp Type: Memory Offset Register"); | ||
| 163 | callbacks->CommandLog( | ||
| 164 | fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type))); | ||
| 165 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); | ||
| 166 | break; | ||
| 167 | case CompareRegisterValueType::RegisterRelAddr: | ||
| 168 | callbacks->CommandLog("Comp Type: Register Relative Address"); | ||
| 169 | callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); | ||
| 170 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); | ||
| 171 | break; | ||
| 172 | case CompareRegisterValueType::RegisterOfsReg: | ||
| 173 | callbacks->CommandLog("Comp Type: Register Offset Register"); | ||
| 174 | callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); | ||
| 175 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); | ||
| 176 | break; | ||
| 177 | } | ||
| 178 | } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) { | ||
| 179 | callbacks->CommandLog("Opcode: Save or Restore Register"); | ||
| 180 | callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); | ||
| 181 | callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); | ||
| 182 | callbacks->CommandLog( | ||
| 183 | fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type))); | ||
| 184 | } else if (auto save_restore_regmask = | ||
| 185 | std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) { | ||
| 186 | callbacks->CommandLog("Opcode: Save or Restore Register Mask"); | ||
| 187 | callbacks->CommandLog( | ||
| 188 | fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type))); | ||
| 189 | for (std::size_t i = 0; i < NumRegisters; i++) { | ||
| 190 | callbacks->CommandLog( | ||
| 191 | fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i])); | ||
| 192 | } | ||
| 193 | } else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) { | ||
| 194 | callbacks->CommandLog("Opcode: Debug Log"); | ||
| 195 | callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); | ||
| 196 | callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); | ||
| 197 | callbacks->CommandLog( | ||
| 198 | fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type))); | ||
| 199 | switch (debug_log->val_type) { | ||
| 200 | case DebugLogValueType::RegisterValue: | ||
| 201 | callbacks->CommandLog("Val Type: Register Value"); | ||
| 202 | callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index)); | ||
| 203 | break; | ||
| 204 | case DebugLogValueType::MemoryRelAddr: | ||
| 205 | callbacks->CommandLog("Val Type: Memory Relative Address"); | ||
| 206 | callbacks->CommandLog( | ||
| 207 | fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type))); | ||
| 208 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); | ||
| 209 | break; | ||
| 210 | case DebugLogValueType::MemoryOfsReg: | ||
| 211 | callbacks->CommandLog("Val Type: Memory Offset Register"); | ||
| 212 | callbacks->CommandLog( | ||
| 213 | fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type))); | ||
| 214 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); | ||
| 215 | break; | ||
| 216 | case DebugLogValueType::RegisterRelAddr: | ||
| 217 | callbacks->CommandLog("Val Type: Register Relative Address"); | ||
| 218 | callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); | ||
| 219 | callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); | ||
| 220 | break; | ||
| 221 | case DebugLogValueType::RegisterOfsReg: | ||
| 222 | callbacks->CommandLog("Val Type: Register Offset Register"); | ||
| 223 | callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); | ||
| 224 | callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); | ||
| 225 | break; | ||
| 226 | } | ||
| 227 | } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) { | ||
| 228 | callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode))); | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | DmntCheatVm::Callbacks::~Callbacks() = default; | ||
| 233 | |||
| 234 | bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { | ||
| 235 | // If we've ever seen a decode failure, return false. | ||
| 236 | bool valid = decode_success; | ||
| 237 | CheatVmOpcode opcode = {}; | ||
| 238 | SCOPE_EXIT({ | ||
| 239 | decode_success &= valid; | ||
| 240 | if (valid) { | ||
| 241 | out = opcode; | ||
| 242 | } | ||
| 243 | }); | ||
| 244 | |||
| 245 | // Helper function for getting instruction dwords. | ||
| 246 | const auto GetNextDword = [&] { | ||
| 247 | if (instruction_ptr >= num_opcodes) { | ||
| 248 | valid = false; | ||
| 249 | return static_cast<u32>(0); | ||
| 250 | } | ||
| 251 | return program[instruction_ptr++]; | ||
| 252 | }; | ||
| 253 | |||
| 254 | // Helper function for parsing a VmInt. | ||
| 255 | const auto GetNextVmInt = [&](const u32 bit_width) { | ||
| 256 | VmInt val{}; | ||
| 257 | |||
| 258 | const u32 first_dword = GetNextDword(); | ||
| 259 | switch (bit_width) { | ||
| 260 | case 1: | ||
| 261 | val.bit8 = static_cast<u8>(first_dword); | ||
| 262 | break; | ||
| 263 | case 2: | ||
| 264 | val.bit16 = static_cast<u16>(first_dword); | ||
| 265 | break; | ||
| 266 | case 4: | ||
| 267 | val.bit32 = first_dword; | ||
| 268 | break; | ||
| 269 | case 8: | ||
| 270 | val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword()); | ||
| 271 | break; | ||
| 272 | } | ||
| 273 | |||
| 274 | return val; | ||
| 275 | }; | ||
| 276 | |||
| 277 | // Read opcode. | ||
| 278 | const u32 first_dword = GetNextDword(); | ||
| 279 | if (!valid) { | ||
| 280 | return valid; | ||
| 281 | } | ||
| 282 | |||
| 283 | auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF)); | ||
| 284 | if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { | ||
| 285 | opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) | | ||
| 286 | ((first_dword >> 24) & 0xF)); | ||
| 287 | } | ||
| 288 | if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { | ||
| 289 | opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) | | ||
| 290 | ((first_dword >> 20) & 0xF)); | ||
| 291 | } | ||
| 292 | |||
| 293 | // detect condition start. | ||
| 294 | switch (opcode_type) { | ||
| 295 | case CheatVmOpcodeType::BeginConditionalBlock: | ||
| 296 | case CheatVmOpcodeType::BeginKeypressConditionalBlock: | ||
| 297 | case CheatVmOpcodeType::BeginRegisterConditionalBlock: | ||
| 298 | opcode.begin_conditional_block = true; | ||
| 299 | break; | ||
| 300 | default: | ||
| 301 | opcode.begin_conditional_block = false; | ||
| 302 | break; | ||
| 303 | } | ||
| 304 | |||
| 305 | switch (opcode_type) { | ||
| 306 | case CheatVmOpcodeType::StoreStatic: { | ||
| 307 | StoreStaticOpcode store_static{}; | ||
| 308 | // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) | ||
| 309 | // Read additional words. | ||
| 310 | const u32 second_dword = GetNextDword(); | ||
| 311 | store_static.bit_width = (first_dword >> 24) & 0xF; | ||
| 312 | store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); | ||
| 313 | store_static.offset_register = ((first_dword >> 16) & 0xF); | ||
| 314 | store_static.rel_address = | ||
| 315 | (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); | ||
| 316 | store_static.value = GetNextVmInt(store_static.bit_width); | ||
| 317 | opcode.opcode = store_static; | ||
| 318 | } break; | ||
| 319 | case CheatVmOpcodeType::BeginConditionalBlock: { | ||
| 320 | BeginConditionalOpcode begin_cond{}; | ||
| 321 | // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) | ||
| 322 | // Read additional words. | ||
| 323 | const u32 second_dword = GetNextDword(); | ||
| 324 | begin_cond.bit_width = (first_dword >> 24) & 0xF; | ||
| 325 | begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); | ||
| 326 | begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); | ||
| 327 | begin_cond.rel_address = | ||
| 328 | (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); | ||
| 329 | begin_cond.value = GetNextVmInt(begin_cond.bit_width); | ||
| 330 | opcode.opcode = begin_cond; | ||
| 331 | } break; | ||
| 332 | case CheatVmOpcodeType::EndConditionalBlock: { | ||
| 333 | // 20000000 | ||
| 334 | // There's actually nothing left to process here! | ||
| 335 | opcode.opcode = EndConditionalOpcode{}; | ||
| 336 | } break; | ||
| 337 | case CheatVmOpcodeType::ControlLoop: { | ||
| 338 | ControlLoopOpcode ctrl_loop{}; | ||
| 339 | // 300R0000 VVVVVVVV | ||
| 340 | // 310R0000 | ||
| 341 | // Parse register, whether loop start or loop end. | ||
| 342 | ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; | ||
| 343 | ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); | ||
| 344 | |||
| 345 | // Read number of iters if loop start. | ||
| 346 | if (ctrl_loop.start_loop) { | ||
| 347 | ctrl_loop.num_iters = GetNextDword(); | ||
| 348 | } | ||
| 349 | opcode.opcode = ctrl_loop; | ||
| 350 | } break; | ||
| 351 | case CheatVmOpcodeType::LoadRegisterStatic: { | ||
| 352 | LoadRegisterStaticOpcode ldr_static{}; | ||
| 353 | // 400R0000 VVVVVVVV VVVVVVVV | ||
| 354 | // Read additional words. | ||
| 355 | ldr_static.reg_index = ((first_dword >> 16) & 0xF); | ||
| 356 | ldr_static.value = | ||
| 357 | (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); | ||
| 358 | opcode.opcode = ldr_static; | ||
| 359 | } break; | ||
| 360 | case CheatVmOpcodeType::LoadRegisterMemory: { | ||
| 361 | LoadRegisterMemoryOpcode ldr_memory{}; | ||
| 362 | // 5TMRI0AA AAAAAAAA | ||
| 363 | // Read additional words. | ||
| 364 | const u32 second_dword = GetNextDword(); | ||
| 365 | ldr_memory.bit_width = (first_dword >> 24) & 0xF; | ||
| 366 | ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF); | ||
| 367 | ldr_memory.reg_index = ((first_dword >> 16) & 0xF); | ||
| 368 | ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; | ||
| 369 | ldr_memory.rel_address = | ||
| 370 | (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword); | ||
| 371 | opcode.opcode = ldr_memory; | ||
| 372 | } break; | ||
| 373 | case CheatVmOpcodeType::StoreStaticToAddress: { | ||
| 374 | StoreStaticToAddressOpcode str_static{}; | ||
| 375 | // 6T0RIor0 VVVVVVVV VVVVVVVV | ||
| 376 | // Read additional words. | ||
| 377 | str_static.bit_width = (first_dword >> 24) & 0xF; | ||
| 378 | str_static.reg_index = ((first_dword >> 16) & 0xF); | ||
| 379 | str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; | ||
| 380 | str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; | ||
| 381 | str_static.offset_reg_index = ((first_dword >> 4) & 0xF); | ||
| 382 | str_static.value = | ||
| 383 | (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword()); | ||
| 384 | opcode.opcode = str_static; | ||
| 385 | } break; | ||
| 386 | case CheatVmOpcodeType::PerformArithmeticStatic: { | ||
| 387 | PerformArithmeticStaticOpcode perform_math_static{}; | ||
| 388 | // 7T0RC000 VVVVVVVV | ||
| 389 | // Read additional words. | ||
| 390 | perform_math_static.bit_width = (first_dword >> 24) & 0xF; | ||
| 391 | perform_math_static.reg_index = ((first_dword >> 16) & 0xF); | ||
| 392 | perform_math_static.math_type = | ||
| 393 | static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF); | ||
| 394 | perform_math_static.value = GetNextDword(); | ||
| 395 | opcode.opcode = perform_math_static; | ||
| 396 | } break; | ||
| 397 | case CheatVmOpcodeType::BeginKeypressConditionalBlock: { | ||
| 398 | BeginKeypressConditionalOpcode begin_keypress_cond{}; | ||
| 399 | // 8kkkkkkk | ||
| 400 | // Just parse the mask. | ||
| 401 | begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; | ||
| 402 | } break; | ||
| 403 | case CheatVmOpcodeType::PerformArithmeticRegister: { | ||
| 404 | PerformArithmeticRegisterOpcode perform_math_reg{}; | ||
| 405 | // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) | ||
| 406 | perform_math_reg.bit_width = (first_dword >> 24) & 0xF; | ||
| 407 | perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF); | ||
| 408 | perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); | ||
| 409 | perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); | ||
| 410 | perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; | ||
| 411 | if (perform_math_reg.has_immediate) { | ||
| 412 | perform_math_reg.src_reg_2_index = 0; | ||
| 413 | perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); | ||
| 414 | } else { | ||
| 415 | perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); | ||
| 416 | } | ||
| 417 | opcode.opcode = perform_math_reg; | ||
| 418 | } break; | ||
| 419 | case CheatVmOpcodeType::StoreRegisterToAddress: { | ||
| 420 | StoreRegisterToAddressOpcode str_register{}; | ||
| 421 | // ATSRIOxa (aaaaaaaa) | ||
| 422 | // A = opcode 10 | ||
| 423 | // T = bit width | ||
| 424 | // S = src register index | ||
| 425 | // R = address register index | ||
| 426 | // I = 1 if increment address register, 0 if not increment address register | ||
| 427 | // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, | ||
| 428 | // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + | ||
| 429 | // Relative Address | ||
| 430 | // x = offset register (for offset type 1), memory type (for offset type 3) | ||
| 431 | // a = relative address (for offset type 2+3) | ||
| 432 | str_register.bit_width = (first_dword >> 24) & 0xF; | ||
| 433 | str_register.str_reg_index = ((first_dword >> 20) & 0xF); | ||
| 434 | str_register.addr_reg_index = ((first_dword >> 16) & 0xF); | ||
| 435 | str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; | ||
| 436 | str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)); | ||
| 437 | str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); | ||
| 438 | switch (str_register.ofs_type) { | ||
| 439 | case StoreRegisterOffsetType::None: | ||
| 440 | case StoreRegisterOffsetType::Reg: | ||
| 441 | // Nothing more to do | ||
| 442 | break; | ||
| 443 | case StoreRegisterOffsetType::Imm: | ||
| 444 | str_register.rel_address = | ||
| 445 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 446 | break; | ||
| 447 | case StoreRegisterOffsetType::MemReg: | ||
| 448 | str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 449 | break; | ||
| 450 | case StoreRegisterOffsetType::MemImm: | ||
| 451 | case StoreRegisterOffsetType::MemImmReg: | ||
| 452 | str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 453 | str_register.rel_address = | ||
| 454 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 455 | break; | ||
| 456 | default: | ||
| 457 | str_register.ofs_type = StoreRegisterOffsetType::None; | ||
| 458 | break; | ||
| 459 | } | ||
| 460 | opcode.opcode = str_register; | ||
| 461 | } break; | ||
| 462 | case CheatVmOpcodeType::BeginRegisterConditionalBlock: { | ||
| 463 | BeginRegisterConditionalOpcode begin_reg_cond{}; | ||
| 464 | // C0TcSX## | ||
| 465 | // C0TcS0Ma aaaaaaaa | ||
| 466 | // C0TcS1Mr | ||
| 467 | // C0TcS2Ra aaaaaaaa | ||
| 468 | // C0TcS3Rr | ||
| 469 | // C0TcS400 VVVVVVVV (VVVVVVVV) | ||
| 470 | // C0TcS5X0 | ||
| 471 | // C0 = opcode 0xC0 | ||
| 472 | // T = bit width | ||
| 473 | // c = condition type. | ||
| 474 | // S = source register. | ||
| 475 | // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset | ||
| 476 | // register, | ||
| 477 | // 2 = register with relative offset, 3 = register with offset register, 4 = static | ||
| 478 | // value, 5 = other register. | ||
| 479 | // M = memory type. | ||
| 480 | // R = address register. | ||
| 481 | // a = relative address. | ||
| 482 | // r = offset register. | ||
| 483 | // X = other register. | ||
| 484 | // V = value. | ||
| 485 | begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; | ||
| 486 | begin_reg_cond.cond_type = | ||
| 487 | static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF); | ||
| 488 | begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); | ||
| 489 | begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF); | ||
| 490 | |||
| 491 | switch (begin_reg_cond.comp_type) { | ||
| 492 | case CompareRegisterValueType::StaticValue: | ||
| 493 | begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); | ||
| 494 | break; | ||
| 495 | case CompareRegisterValueType::OtherRegister: | ||
| 496 | begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); | ||
| 497 | break; | ||
| 498 | case CompareRegisterValueType::MemoryRelAddr: | ||
| 499 | begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 500 | begin_reg_cond.rel_address = | ||
| 501 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 502 | break; | ||
| 503 | case CompareRegisterValueType::MemoryOfsReg: | ||
| 504 | begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 505 | begin_reg_cond.ofs_reg_index = (first_dword & 0xF); | ||
| 506 | break; | ||
| 507 | case CompareRegisterValueType::RegisterRelAddr: | ||
| 508 | begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); | ||
| 509 | begin_reg_cond.rel_address = | ||
| 510 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 511 | break; | ||
| 512 | case CompareRegisterValueType::RegisterOfsReg: | ||
| 513 | begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); | ||
| 514 | begin_reg_cond.ofs_reg_index = (first_dword & 0xF); | ||
| 515 | break; | ||
| 516 | } | ||
| 517 | opcode.opcode = begin_reg_cond; | ||
| 518 | } break; | ||
| 519 | case CheatVmOpcodeType::SaveRestoreRegister: { | ||
| 520 | SaveRestoreRegisterOpcode save_restore_reg{}; | ||
| 521 | // C10D0Sx0 | ||
| 522 | // C1 = opcode 0xC1 | ||
| 523 | // D = destination index. | ||
| 524 | // S = source index. | ||
| 525 | // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring | ||
| 526 | // a register. | ||
| 527 | // NOTE: If we add more save slots later, current encoding is backwards compatible. | ||
| 528 | save_restore_reg.dst_index = (first_dword >> 16) & 0xF; | ||
| 529 | save_restore_reg.src_index = (first_dword >> 8) & 0xF; | ||
| 530 | save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF); | ||
| 531 | opcode.opcode = save_restore_reg; | ||
| 532 | } break; | ||
| 533 | case CheatVmOpcodeType::SaveRestoreRegisterMask: { | ||
| 534 | SaveRestoreRegisterMaskOpcode save_restore_regmask{}; | ||
| 535 | // C2x0XXXX | ||
| 536 | // C2 = opcode 0xC2 | ||
| 537 | // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. | ||
| 538 | // X = 16-bit bitmask, bit i --> save or restore register i. | ||
| 539 | save_restore_regmask.op_type = | ||
| 540 | static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF); | ||
| 541 | for (std::size_t i = 0; i < NumRegisters; i++) { | ||
| 542 | save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; | ||
| 543 | } | ||
| 544 | opcode.opcode = save_restore_regmask; | ||
| 545 | } break; | ||
| 546 | case CheatVmOpcodeType::DebugLog: { | ||
| 547 | DebugLogOpcode debug_log{}; | ||
| 548 | // FFFTIX## | ||
| 549 | // FFFTI0Ma aaaaaaaa | ||
| 550 | // FFFTI1Mr | ||
| 551 | // FFFTI2Ra aaaaaaaa | ||
| 552 | // FFFTI3Rr | ||
| 553 | // FFFTI4X0 | ||
| 554 | // FFF = opcode 0xFFF | ||
| 555 | // T = bit width. | ||
| 556 | // I = log id. | ||
| 557 | // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset | ||
| 558 | // register, | ||
| 559 | // 2 = register with relative offset, 3 = register with offset register, 4 = register | ||
| 560 | // value. | ||
| 561 | // M = memory type. | ||
| 562 | // R = address register. | ||
| 563 | // a = relative address. | ||
| 564 | // r = offset register. | ||
| 565 | // X = value register. | ||
| 566 | debug_log.bit_width = (first_dword >> 16) & 0xF; | ||
| 567 | debug_log.log_id = ((first_dword >> 12) & 0xF); | ||
| 568 | debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF); | ||
| 569 | |||
| 570 | switch (debug_log.val_type) { | ||
| 571 | case DebugLogValueType::RegisterValue: | ||
| 572 | debug_log.val_reg_index = ((first_dword >> 4) & 0xF); | ||
| 573 | break; | ||
| 574 | case DebugLogValueType::MemoryRelAddr: | ||
| 575 | debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 576 | debug_log.rel_address = | ||
| 577 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 578 | break; | ||
| 579 | case DebugLogValueType::MemoryOfsReg: | ||
| 580 | debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF); | ||
| 581 | debug_log.ofs_reg_index = (first_dword & 0xF); | ||
| 582 | break; | ||
| 583 | case DebugLogValueType::RegisterRelAddr: | ||
| 584 | debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); | ||
| 585 | debug_log.rel_address = | ||
| 586 | ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword())); | ||
| 587 | break; | ||
| 588 | case DebugLogValueType::RegisterOfsReg: | ||
| 589 | debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); | ||
| 590 | debug_log.ofs_reg_index = (first_dword & 0xF); | ||
| 591 | break; | ||
| 592 | } | ||
| 593 | opcode.opcode = debug_log; | ||
| 594 | } break; | ||
| 595 | case CheatVmOpcodeType::ExtendedWidth: | ||
| 596 | case CheatVmOpcodeType::DoubleExtendedWidth: | ||
| 597 | default: | ||
| 598 | // Unrecognized instruction cannot be decoded. | ||
| 599 | valid = false; | ||
| 600 | opcode.opcode = UnrecognizedInstruction{opcode_type}; | ||
| 601 | break; | ||
| 602 | } | ||
| 603 | |||
| 604 | // End decoding. | ||
| 605 | return valid; | ||
| 606 | } | ||
| 607 | |||
| 608 | void DmntCheatVm::SkipConditionalBlock() { | ||
| 609 | if (condition_depth > 0) { | ||
| 610 | // We want to continue until we're out of the current block. | ||
| 611 | const std::size_t desired_depth = condition_depth - 1; | ||
| 612 | |||
| 613 | CheatVmOpcode skip_opcode{}; | ||
| 614 | while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) { | ||
| 615 | // Decode instructions until we see end of the current conditional block. | ||
| 616 | // NOTE: This is broken in gateway's implementation. | ||
| 617 | // Gateway currently checks for "0x2" instead of "0x20000000" | ||
| 618 | // In addition, they do a linear scan instead of correctly decoding opcodes. | ||
| 619 | // This causes issues if "0x2" appears as an immediate in the conditional block... | ||
| 620 | |||
| 621 | // We also support nesting of conditional blocks, and Gateway does not. | ||
| 622 | if (skip_opcode.begin_conditional_block) { | ||
| 623 | condition_depth++; | ||
| 624 | } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) { | ||
| 625 | condition_depth--; | ||
| 626 | } | ||
| 627 | } | ||
| 628 | } else { | ||
| 629 | // Skipping, but condition_depth = 0. | ||
| 630 | // This is an error condition. | ||
| 631 | // However, I don't actually believe it is possible for this to happen. | ||
| 632 | // I guess we'll throw a fatal error here, so as to encourage me to fix the VM | ||
| 633 | // in the event that someone triggers it? I don't know how you'd do that. | ||
| 634 | UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); | ||
| 635 | } | ||
| 636 | } | ||
| 637 | |||
| 638 | u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { | ||
| 639 | switch (bit_width) { | ||
| 640 | case 1: | ||
| 641 | return value.bit8; | ||
| 642 | case 2: | ||
| 643 | return value.bit16; | ||
| 644 | case 4: | ||
| 645 | return value.bit32; | ||
| 646 | case 8: | ||
| 647 | return value.bit64; | ||
| 648 | default: | ||
| 649 | // Invalid bit width -> return 0. | ||
| 650 | return 0; | ||
| 651 | } | ||
| 652 | } | ||
| 653 | |||
| 654 | u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, | ||
| 655 | MemoryAccessType mem_type, u64 rel_address) { | ||
| 656 | switch (mem_type) { | ||
| 657 | case MemoryAccessType::MainNso: | ||
| 658 | default: | ||
| 659 | return metadata.main_nso_extents.base + rel_address; | ||
| 660 | case MemoryAccessType::Heap: | ||
| 661 | return metadata.heap_extents.base + rel_address; | ||
| 662 | } | ||
| 663 | } | ||
| 664 | |||
| 665 | void DmntCheatVm::ResetState() { | ||
| 666 | registers.fill(0); | ||
| 667 | saved_values.fill(0); | ||
| 668 | loop_tops.fill(0); | ||
| 669 | instruction_ptr = 0; | ||
| 670 | condition_depth = 0; | ||
| 671 | decode_success = true; | ||
| 672 | } | ||
| 673 | |||
| 674 | bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) { | ||
| 675 | // Reset opcode count. | ||
| 676 | num_opcodes = 0; | ||
| 677 | |||
| 678 | for (std::size_t i = 0; i < entries.size(); i++) { | ||
| 679 | if (entries[i].enabled) { | ||
| 680 | // Bounds check. | ||
| 681 | if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { | ||
| 682 | num_opcodes = 0; | ||
| 683 | return false; | ||
| 684 | } | ||
| 685 | |||
| 686 | for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { | ||
| 687 | program[num_opcodes++] = entries[i].definition.opcodes[n]; | ||
| 688 | } | ||
| 689 | } | ||
| 690 | } | ||
| 691 | |||
| 692 | return true; | ||
| 693 | } | ||
| 694 | |||
| 695 | void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { | ||
| 696 | CheatVmOpcode cur_opcode{}; | ||
| 697 | |||
| 698 | // Get Keys down. | ||
| 699 | u64 kDown = callbacks->HidKeysDown(); | ||
| 700 | |||
| 701 | callbacks->CommandLog("Started VM execution."); | ||
| 702 | callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); | ||
| 703 | callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); | ||
| 704 | callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF))); | ||
| 705 | |||
| 706 | // Clear VM state. | ||
| 707 | ResetState(); | ||
| 708 | |||
| 709 | // Loop until program finishes. | ||
| 710 | while (DecodeNextOpcode(cur_opcode)) { | ||
| 711 | callbacks->CommandLog( | ||
| 712 | fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr))); | ||
| 713 | |||
| 714 | for (std::size_t i = 0; i < NumRegisters; i++) { | ||
| 715 | callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); | ||
| 716 | } | ||
| 717 | |||
| 718 | for (std::size_t i = 0; i < NumRegisters; i++) { | ||
| 719 | callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); | ||
| 720 | } | ||
| 721 | LogOpcode(cur_opcode); | ||
| 722 | |||
| 723 | // Increment conditional depth, if relevant. | ||
| 724 | if (cur_opcode.begin_conditional_block) { | ||
| 725 | condition_depth++; | ||
| 726 | } | ||
| 727 | |||
| 728 | if (auto store_static = std::get_if<StoreStaticOpcode>(&cur_opcode.opcode)) { | ||
| 729 | // Calculate address, write value to memory. | ||
| 730 | u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, | ||
| 731 | store_static->rel_address + | ||
| 732 | registers[store_static->offset_register]); | ||
| 733 | u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); | ||
| 734 | switch (store_static->bit_width) { | ||
| 735 | case 1: | ||
| 736 | case 2: | ||
| 737 | case 4: | ||
| 738 | case 8: | ||
| 739 | callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width); | ||
| 740 | break; | ||
| 741 | } | ||
| 742 | } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) { | ||
| 743 | // Read value from memory. | ||
| 744 | u64 src_address = | ||
| 745 | GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); | ||
| 746 | u64 src_value = 0; | ||
| 747 | switch (store_static->bit_width) { | ||
| 748 | case 1: | ||
| 749 | case 2: | ||
| 750 | case 4: | ||
| 751 | case 8: | ||
| 752 | callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width); | ||
| 753 | break; | ||
| 754 | } | ||
| 755 | // Check against condition. | ||
| 756 | u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); | ||
| 757 | bool cond_met = false; | ||
| 758 | switch (begin_cond->cond_type) { | ||
| 759 | case ConditionalComparisonType::GT: | ||
| 760 | cond_met = src_value > cond_value; | ||
| 761 | break; | ||
| 762 | case ConditionalComparisonType::GE: | ||
| 763 | cond_met = src_value >= cond_value; | ||
| 764 | break; | ||
| 765 | case ConditionalComparisonType::LT: | ||
| 766 | cond_met = src_value < cond_value; | ||
| 767 | break; | ||
| 768 | case ConditionalComparisonType::LE: | ||
| 769 | cond_met = src_value <= cond_value; | ||
| 770 | break; | ||
| 771 | case ConditionalComparisonType::EQ: | ||
| 772 | cond_met = src_value == cond_value; | ||
| 773 | break; | ||
| 774 | case ConditionalComparisonType::NE: | ||
| 775 | cond_met = src_value != cond_value; | ||
| 776 | break; | ||
| 777 | } | ||
| 778 | // Skip conditional block if condition not met. | ||
| 779 | if (!cond_met) { | ||
| 780 | SkipConditionalBlock(); | ||
| 781 | } | ||
| 782 | } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) { | ||
| 783 | // Decrement the condition depth. | ||
| 784 | // We will assume, graciously, that mismatched conditional block ends are a nop. | ||
| 785 | if (condition_depth > 0) { | ||
| 786 | condition_depth--; | ||
| 787 | } | ||
| 788 | } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) { | ||
| 789 | if (ctrl_loop->start_loop) { | ||
| 790 | // Start a loop. | ||
| 791 | registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; | ||
| 792 | loop_tops[ctrl_loop->reg_index] = instruction_ptr; | ||
| 793 | } else { | ||
| 794 | // End a loop. | ||
| 795 | registers[ctrl_loop->reg_index]--; | ||
| 796 | if (registers[ctrl_loop->reg_index] != 0) { | ||
| 797 | instruction_ptr = loop_tops[ctrl_loop->reg_index]; | ||
| 798 | } | ||
| 799 | } | ||
| 800 | } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&cur_opcode.opcode)) { | ||
| 801 | // Set a register to a static value. | ||
| 802 | registers[ldr_static->reg_index] = ldr_static->value; | ||
| 803 | } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) { | ||
| 804 | // Choose source address. | ||
| 805 | u64 src_address; | ||
| 806 | if (ldr_memory->load_from_reg) { | ||
| 807 | src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; | ||
| 808 | } else { | ||
| 809 | src_address = | ||
| 810 | GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); | ||
| 811 | } | ||
| 812 | // Read into register. Gateway only reads on valid bitwidth. | ||
| 813 | switch (ldr_memory->bit_width) { | ||
| 814 | case 1: | ||
| 815 | case 2: | ||
| 816 | case 4: | ||
| 817 | case 8: | ||
| 818 | callbacks->MemoryRead(src_address, ®isters[ldr_memory->reg_index], | ||
| 819 | ldr_memory->bit_width); | ||
| 820 | break; | ||
| 821 | } | ||
| 822 | } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) { | ||
| 823 | // Calculate address. | ||
| 824 | u64 dst_address = registers[str_static->reg_index]; | ||
| 825 | u64 dst_value = str_static->value; | ||
| 826 | if (str_static->add_offset_reg) { | ||
| 827 | dst_address += registers[str_static->offset_reg_index]; | ||
| 828 | } | ||
| 829 | // Write value to memory. Gateway only writes on valid bitwidth. | ||
| 830 | switch (str_static->bit_width) { | ||
| 831 | case 1: | ||
| 832 | case 2: | ||
| 833 | case 4: | ||
| 834 | case 8: | ||
| 835 | callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width); | ||
| 836 | break; | ||
| 837 | } | ||
| 838 | // Increment register if relevant. | ||
| 839 | if (str_static->increment_reg) { | ||
| 840 | registers[str_static->reg_index] += str_static->bit_width; | ||
| 841 | } | ||
| 842 | } else if (auto perform_math_static = | ||
| 843 | std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) { | ||
| 844 | // Do requested math. | ||
| 845 | switch (perform_math_static->math_type) { | ||
| 846 | case RegisterArithmeticType::Addition: | ||
| 847 | registers[perform_math_static->reg_index] += | ||
| 848 | static_cast<u64>(perform_math_static->value); | ||
| 849 | break; | ||
| 850 | case RegisterArithmeticType::Subtraction: | ||
| 851 | registers[perform_math_static->reg_index] -= | ||
| 852 | static_cast<u64>(perform_math_static->value); | ||
| 853 | break; | ||
| 854 | case RegisterArithmeticType::Multiplication: | ||
| 855 | registers[perform_math_static->reg_index] *= | ||
| 856 | static_cast<u64>(perform_math_static->value); | ||
| 857 | break; | ||
| 858 | case RegisterArithmeticType::LeftShift: | ||
| 859 | registers[perform_math_static->reg_index] <<= | ||
| 860 | static_cast<u64>(perform_math_static->value); | ||
| 861 | break; | ||
| 862 | case RegisterArithmeticType::RightShift: | ||
| 863 | registers[perform_math_static->reg_index] >>= | ||
| 864 | static_cast<u64>(perform_math_static->value); | ||
| 865 | break; | ||
| 866 | default: | ||
| 867 | // Do not handle extensions here. | ||
| 868 | break; | ||
| 869 | } | ||
| 870 | // Apply bit width. | ||
| 871 | switch (perform_math_static->bit_width) { | ||
| 872 | case 1: | ||
| 873 | registers[perform_math_static->reg_index] = | ||
| 874 | static_cast<u8>(registers[perform_math_static->reg_index]); | ||
| 875 | break; | ||
| 876 | case 2: | ||
| 877 | registers[perform_math_static->reg_index] = | ||
| 878 | static_cast<u16>(registers[perform_math_static->reg_index]); | ||
| 879 | break; | ||
| 880 | case 4: | ||
| 881 | registers[perform_math_static->reg_index] = | ||
| 882 | static_cast<u32>(registers[perform_math_static->reg_index]); | ||
| 883 | break; | ||
| 884 | case 8: | ||
| 885 | registers[perform_math_static->reg_index] = | ||
| 886 | static_cast<u64>(registers[perform_math_static->reg_index]); | ||
| 887 | break; | ||
| 888 | } | ||
| 889 | } else if (auto begin_keypress_cond = | ||
| 890 | std::get_if<BeginKeypressConditionalOpcode>(&cur_opcode.opcode)) { | ||
| 891 | // Check for keypress. | ||
| 892 | if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { | ||
| 893 | // Keys not pressed. Skip conditional block. | ||
| 894 | SkipConditionalBlock(); | ||
| 895 | } | ||
| 896 | } else if (auto perform_math_reg = | ||
| 897 | std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) { | ||
| 898 | const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index]; | ||
| 899 | const u64 operand_2_value = | ||
| 900 | perform_math_reg->has_immediate | ||
| 901 | ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) | ||
| 902 | : registers[perform_math_reg->src_reg_2_index]; | ||
| 903 | |||
| 904 | u64 res_val = 0; | ||
| 905 | // Do requested math. | ||
| 906 | switch (perform_math_reg->math_type) { | ||
| 907 | case RegisterArithmeticType::Addition: | ||
| 908 | res_val = operand_1_value + operand_2_value; | ||
| 909 | break; | ||
| 910 | case RegisterArithmeticType::Subtraction: | ||
| 911 | res_val = operand_1_value - operand_2_value; | ||
| 912 | break; | ||
| 913 | case RegisterArithmeticType::Multiplication: | ||
| 914 | res_val = operand_1_value * operand_2_value; | ||
| 915 | break; | ||
| 916 | case RegisterArithmeticType::LeftShift: | ||
| 917 | res_val = operand_1_value << operand_2_value; | ||
| 918 | break; | ||
| 919 | case RegisterArithmeticType::RightShift: | ||
| 920 | res_val = operand_1_value >> operand_2_value; | ||
| 921 | break; | ||
| 922 | case RegisterArithmeticType::LogicalAnd: | ||
| 923 | res_val = operand_1_value & operand_2_value; | ||
| 924 | break; | ||
| 925 | case RegisterArithmeticType::LogicalOr: | ||
| 926 | res_val = operand_1_value | operand_2_value; | ||
| 927 | break; | ||
| 928 | case RegisterArithmeticType::LogicalNot: | ||
| 929 | res_val = ~operand_1_value; | ||
| 930 | break; | ||
| 931 | case RegisterArithmeticType::LogicalXor: | ||
| 932 | res_val = operand_1_value ^ operand_2_value; | ||
| 933 | break; | ||
| 934 | case RegisterArithmeticType::None: | ||
| 935 | res_val = operand_1_value; | ||
| 936 | break; | ||
| 937 | } | ||
| 938 | |||
| 939 | // Apply bit width. | ||
| 940 | switch (perform_math_reg->bit_width) { | ||
| 941 | case 1: | ||
| 942 | res_val = static_cast<u8>(res_val); | ||
| 943 | break; | ||
| 944 | case 2: | ||
| 945 | res_val = static_cast<u16>(res_val); | ||
| 946 | break; | ||
| 947 | case 4: | ||
| 948 | res_val = static_cast<u32>(res_val); | ||
| 949 | break; | ||
| 950 | case 8: | ||
| 951 | res_val = static_cast<u64>(res_val); | ||
| 952 | break; | ||
| 953 | } | ||
| 954 | |||
| 955 | // Save to register. | ||
| 956 | registers[perform_math_reg->dst_reg_index] = res_val; | ||
| 957 | } else if (auto str_register = | ||
| 958 | std::get_if<StoreRegisterToAddressOpcode>(&cur_opcode.opcode)) { | ||
| 959 | // Calculate address. | ||
| 960 | u64 dst_value = registers[str_register->str_reg_index]; | ||
| 961 | u64 dst_address = registers[str_register->addr_reg_index]; | ||
| 962 | switch (str_register->ofs_type) { | ||
| 963 | case StoreRegisterOffsetType::None: | ||
| 964 | // Nothing more to do | ||
| 965 | break; | ||
| 966 | case StoreRegisterOffsetType::Reg: | ||
| 967 | dst_address += registers[str_register->ofs_reg_index]; | ||
| 968 | break; | ||
| 969 | case StoreRegisterOffsetType::Imm: | ||
| 970 | dst_address += str_register->rel_address; | ||
| 971 | break; | ||
| 972 | case StoreRegisterOffsetType::MemReg: | ||
| 973 | dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, | ||
| 974 | registers[str_register->addr_reg_index]); | ||
| 975 | break; | ||
| 976 | case StoreRegisterOffsetType::MemImm: | ||
| 977 | dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, | ||
| 978 | str_register->rel_address); | ||
| 979 | break; | ||
| 980 | case StoreRegisterOffsetType::MemImmReg: | ||
| 981 | dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, | ||
| 982 | registers[str_register->addr_reg_index] + | ||
| 983 | str_register->rel_address); | ||
| 984 | break; | ||
| 985 | } | ||
| 986 | |||
| 987 | // Write value to memory. Write only on valid bitwidth. | ||
| 988 | switch (str_register->bit_width) { | ||
| 989 | case 1: | ||
| 990 | case 2: | ||
| 991 | case 4: | ||
| 992 | case 8: | ||
| 993 | callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width); | ||
| 994 | break; | ||
| 995 | } | ||
| 996 | |||
| 997 | // Increment register if relevant. | ||
| 998 | if (str_register->increment_reg) { | ||
| 999 | registers[str_register->addr_reg_index] += str_register->bit_width; | ||
| 1000 | } | ||
| 1001 | } else if (auto begin_reg_cond = | ||
| 1002 | std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) { | ||
| 1003 | // Get value from register. | ||
| 1004 | u64 src_value = 0; | ||
| 1005 | switch (begin_reg_cond->bit_width) { | ||
| 1006 | case 1: | ||
| 1007 | src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul); | ||
| 1008 | break; | ||
| 1009 | case 2: | ||
| 1010 | src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul); | ||
| 1011 | break; | ||
| 1012 | case 4: | ||
| 1013 | src_value = | ||
| 1014 | static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul); | ||
| 1015 | break; | ||
| 1016 | case 8: | ||
| 1017 | src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] & | ||
| 1018 | 0xFFFFFFFFFFFFFFFFul); | ||
| 1019 | break; | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | // Read value from memory. | ||
| 1023 | u64 cond_value = 0; | ||
| 1024 | if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { | ||
| 1025 | cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); | ||
| 1026 | } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { | ||
| 1027 | switch (begin_reg_cond->bit_width) { | ||
| 1028 | case 1: | ||
| 1029 | cond_value = | ||
| 1030 | static_cast<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul); | ||
| 1031 | break; | ||
| 1032 | case 2: | ||
| 1033 | cond_value = | ||
| 1034 | static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul); | ||
| 1035 | break; | ||
| 1036 | case 4: | ||
| 1037 | cond_value = | ||
| 1038 | static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul); | ||
| 1039 | break; | ||
| 1040 | case 8: | ||
| 1041 | cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] & | ||
| 1042 | 0xFFFFFFFFFFFFFFFFul); | ||
| 1043 | break; | ||
| 1044 | } | ||
| 1045 | } else { | ||
| 1046 | u64 cond_address = 0; | ||
| 1047 | switch (begin_reg_cond->comp_type) { | ||
| 1048 | case CompareRegisterValueType::MemoryRelAddr: | ||
| 1049 | cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, | ||
| 1050 | begin_reg_cond->rel_address); | ||
| 1051 | break; | ||
| 1052 | case CompareRegisterValueType::MemoryOfsReg: | ||
| 1053 | cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, | ||
| 1054 | registers[begin_reg_cond->ofs_reg_index]); | ||
| 1055 | break; | ||
| 1056 | case CompareRegisterValueType::RegisterRelAddr: | ||
| 1057 | cond_address = | ||
| 1058 | registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address; | ||
| 1059 | break; | ||
| 1060 | case CompareRegisterValueType::RegisterOfsReg: | ||
| 1061 | cond_address = registers[begin_reg_cond->addr_reg_index] + | ||
| 1062 | registers[begin_reg_cond->ofs_reg_index]; | ||
| 1063 | break; | ||
| 1064 | default: | ||
| 1065 | break; | ||
| 1066 | } | ||
| 1067 | switch (begin_reg_cond->bit_width) { | ||
| 1068 | case 1: | ||
| 1069 | case 2: | ||
| 1070 | case 4: | ||
| 1071 | case 8: | ||
| 1072 | callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width); | ||
| 1073 | break; | ||
| 1074 | } | ||
| 1075 | } | ||
| 1076 | |||
| 1077 | // Check against condition. | ||
| 1078 | bool cond_met = false; | ||
| 1079 | switch (begin_reg_cond->cond_type) { | ||
| 1080 | case ConditionalComparisonType::GT: | ||
| 1081 | cond_met = src_value > cond_value; | ||
| 1082 | break; | ||
| 1083 | case ConditionalComparisonType::GE: | ||
| 1084 | cond_met = src_value >= cond_value; | ||
| 1085 | break; | ||
| 1086 | case ConditionalComparisonType::LT: | ||
| 1087 | cond_met = src_value < cond_value; | ||
| 1088 | break; | ||
| 1089 | case ConditionalComparisonType::LE: | ||
| 1090 | cond_met = src_value <= cond_value; | ||
| 1091 | break; | ||
| 1092 | case ConditionalComparisonType::EQ: | ||
| 1093 | cond_met = src_value == cond_value; | ||
| 1094 | break; | ||
| 1095 | case ConditionalComparisonType::NE: | ||
| 1096 | cond_met = src_value != cond_value; | ||
| 1097 | break; | ||
| 1098 | } | ||
| 1099 | |||
| 1100 | // Skip conditional block if condition not met. | ||
| 1101 | if (!cond_met) { | ||
| 1102 | SkipConditionalBlock(); | ||
| 1103 | } | ||
| 1104 | } else if (auto save_restore_reg = | ||
| 1105 | std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) { | ||
| 1106 | // Save or restore a register. | ||
| 1107 | switch (save_restore_reg->op_type) { | ||
| 1108 | case SaveRestoreRegisterOpType::ClearRegs: | ||
| 1109 | registers[save_restore_reg->dst_index] = 0ul; | ||
| 1110 | break; | ||
| 1111 | case SaveRestoreRegisterOpType::ClearSaved: | ||
| 1112 | saved_values[save_restore_reg->dst_index] = 0ul; | ||
| 1113 | break; | ||
| 1114 | case SaveRestoreRegisterOpType::Save: | ||
| 1115 | saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; | ||
| 1116 | break; | ||
| 1117 | case SaveRestoreRegisterOpType::Restore: | ||
| 1118 | default: | ||
| 1119 | registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; | ||
| 1120 | break; | ||
| 1121 | } | ||
| 1122 | } else if (auto save_restore_regmask = | ||
| 1123 | std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) { | ||
| 1124 | // Save or restore register mask. | ||
| 1125 | u64* src; | ||
| 1126 | u64* dst; | ||
| 1127 | switch (save_restore_regmask->op_type) { | ||
| 1128 | case SaveRestoreRegisterOpType::ClearSaved: | ||
| 1129 | case SaveRestoreRegisterOpType::Save: | ||
| 1130 | src = registers.data(); | ||
| 1131 | dst = saved_values.data(); | ||
| 1132 | break; | ||
| 1133 | case SaveRestoreRegisterOpType::ClearRegs: | ||
| 1134 | case SaveRestoreRegisterOpType::Restore: | ||
| 1135 | default: | ||
| 1136 | src = registers.data(); | ||
| 1137 | dst = saved_values.data(); | ||
| 1138 | break; | ||
| 1139 | } | ||
| 1140 | for (std::size_t i = 0; i < NumRegisters; i++) { | ||
| 1141 | if (save_restore_regmask->should_operate[i]) { | ||
| 1142 | switch (save_restore_regmask->op_type) { | ||
| 1143 | case SaveRestoreRegisterOpType::ClearSaved: | ||
| 1144 | case SaveRestoreRegisterOpType::ClearRegs: | ||
| 1145 | dst[i] = 0ul; | ||
| 1146 | break; | ||
| 1147 | case SaveRestoreRegisterOpType::Save: | ||
| 1148 | case SaveRestoreRegisterOpType::Restore: | ||
| 1149 | default: | ||
| 1150 | dst[i] = src[i]; | ||
| 1151 | break; | ||
| 1152 | } | ||
| 1153 | } | ||
| 1154 | } | ||
| 1155 | } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) { | ||
| 1156 | // Read value from memory. | ||
| 1157 | u64 log_value = 0; | ||
| 1158 | if (debug_log->val_type == DebugLogValueType::RegisterValue) { | ||
| 1159 | switch (debug_log->bit_width) { | ||
| 1160 | case 1: | ||
| 1161 | log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul); | ||
| 1162 | break; | ||
| 1163 | case 2: | ||
| 1164 | log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul); | ||
| 1165 | break; | ||
| 1166 | case 4: | ||
| 1167 | log_value = | ||
| 1168 | static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); | ||
| 1169 | break; | ||
| 1170 | case 8: | ||
| 1171 | log_value = static_cast<u64>(registers[debug_log->val_reg_index] & | ||
| 1172 | 0xFFFFFFFFFFFFFFFFul); | ||
| 1173 | break; | ||
| 1174 | } | ||
| 1175 | } else { | ||
| 1176 | u64 val_address = 0; | ||
| 1177 | switch (debug_log->val_type) { | ||
| 1178 | case DebugLogValueType::MemoryRelAddr: | ||
| 1179 | val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, | ||
| 1180 | debug_log->rel_address); | ||
| 1181 | break; | ||
| 1182 | case DebugLogValueType::MemoryOfsReg: | ||
| 1183 | val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, | ||
| 1184 | registers[debug_log->ofs_reg_index]); | ||
| 1185 | break; | ||
| 1186 | case DebugLogValueType::RegisterRelAddr: | ||
| 1187 | val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; | ||
| 1188 | break; | ||
| 1189 | case DebugLogValueType::RegisterOfsReg: | ||
| 1190 | val_address = | ||
| 1191 | registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; | ||
| 1192 | break; | ||
| 1193 | default: | ||
| 1194 | break; | ||
| 1195 | } | ||
| 1196 | switch (debug_log->bit_width) { | ||
| 1197 | case 1: | ||
| 1198 | case 2: | ||
| 1199 | case 4: | ||
| 1200 | case 8: | ||
| 1201 | callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width); | ||
| 1202 | break; | ||
| 1203 | } | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | // Log value. | ||
| 1207 | DebugLog(debug_log->log_id, log_value); | ||
| 1208 | } | ||
| 1209 | } | ||
| 1210 | } | ||
| 1211 | |||
| 1212 | } // namespace Memory | ||
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h new file mode 100644 index 000000000..c36212cf1 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.h | |||
| @@ -0,0 +1,321 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 2018-2019 Atmosphère-NX | ||
| 3 | * | ||
| 4 | * This program is free software; you can redistribute it and/or modify it | ||
| 5 | * under the terms and conditions of the GNU General Public License, | ||
| 6 | * version 2, as published by the Free Software Foundation. | ||
| 7 | * | ||
| 8 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
| 9 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 10 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 11 | * more details. | ||
| 12 | * | ||
| 13 | * You should have received a copy of the GNU General Public License | ||
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 15 | */ | ||
| 16 | |||
| 17 | /* | ||
| 18 | * Adapted by DarkLordZach for use/interaction with yuzu | ||
| 19 | * | ||
| 20 | * Modifications Copyright 2019 yuzu emulator team | ||
| 21 | * Licensed under GPLv2 or any later version | ||
| 22 | * Refer to the license.txt file included. | ||
| 23 | */ | ||
| 24 | |||
| 25 | #pragma once | ||
| 26 | |||
| 27 | #include <variant> | ||
| 28 | #include <vector> | ||
| 29 | #include <fmt/printf.h> | ||
| 30 | #include "common/common_types.h" | ||
| 31 | #include "core/memory/dmnt_cheat_types.h" | ||
| 32 | |||
| 33 | namespace Memory { | ||
| 34 | |||
| 35 | enum class CheatVmOpcodeType : u32 { | ||
| 36 | StoreStatic = 0, | ||
| 37 | BeginConditionalBlock = 1, | ||
| 38 | EndConditionalBlock = 2, | ||
| 39 | ControlLoop = 3, | ||
| 40 | LoadRegisterStatic = 4, | ||
| 41 | LoadRegisterMemory = 5, | ||
| 42 | StoreStaticToAddress = 6, | ||
| 43 | PerformArithmeticStatic = 7, | ||
| 44 | BeginKeypressConditionalBlock = 8, | ||
| 45 | |||
| 46 | // These are not implemented by Gateway's VM. | ||
| 47 | PerformArithmeticRegister = 9, | ||
| 48 | StoreRegisterToAddress = 10, | ||
| 49 | Reserved11 = 11, | ||
| 50 | |||
| 51 | // This is a meta entry, and not a real opcode. | ||
| 52 | // This is to facilitate multi-nybble instruction decoding. | ||
| 53 | ExtendedWidth = 12, | ||
| 54 | |||
| 55 | // Extended width opcodes. | ||
| 56 | BeginRegisterConditionalBlock = 0xC0, | ||
| 57 | SaveRestoreRegister = 0xC1, | ||
| 58 | SaveRestoreRegisterMask = 0xC2, | ||
| 59 | |||
| 60 | // This is a meta entry, and not a real opcode. | ||
| 61 | // This is to facilitate multi-nybble instruction decoding. | ||
| 62 | DoubleExtendedWidth = 0xF0, | ||
| 63 | |||
| 64 | // Double-extended width opcodes. | ||
| 65 | DebugLog = 0xFFF, | ||
| 66 | }; | ||
| 67 | |||
| 68 | enum class MemoryAccessType : u32 { | ||
| 69 | MainNso = 0, | ||
| 70 | Heap = 1, | ||
| 71 | }; | ||
| 72 | |||
| 73 | enum class ConditionalComparisonType : u32 { | ||
| 74 | GT = 1, | ||
| 75 | GE = 2, | ||
| 76 | LT = 3, | ||
| 77 | LE = 4, | ||
| 78 | EQ = 5, | ||
| 79 | NE = 6, | ||
| 80 | }; | ||
| 81 | |||
| 82 | enum class RegisterArithmeticType : u32 { | ||
| 83 | Addition = 0, | ||
| 84 | Subtraction = 1, | ||
| 85 | Multiplication = 2, | ||
| 86 | LeftShift = 3, | ||
| 87 | RightShift = 4, | ||
| 88 | |||
| 89 | // These are not supported by Gateway's VM. | ||
| 90 | LogicalAnd = 5, | ||
| 91 | LogicalOr = 6, | ||
| 92 | LogicalNot = 7, | ||
| 93 | LogicalXor = 8, | ||
| 94 | |||
| 95 | None = 9, | ||
| 96 | }; | ||
| 97 | |||
| 98 | enum class StoreRegisterOffsetType : u32 { | ||
| 99 | None = 0, | ||
| 100 | Reg = 1, | ||
| 101 | Imm = 2, | ||
| 102 | MemReg = 3, | ||
| 103 | MemImm = 4, | ||
| 104 | MemImmReg = 5, | ||
| 105 | }; | ||
| 106 | |||
| 107 | enum class CompareRegisterValueType : u32 { | ||
| 108 | MemoryRelAddr = 0, | ||
| 109 | MemoryOfsReg = 1, | ||
| 110 | RegisterRelAddr = 2, | ||
| 111 | RegisterOfsReg = 3, | ||
| 112 | StaticValue = 4, | ||
| 113 | OtherRegister = 5, | ||
| 114 | }; | ||
| 115 | |||
| 116 | enum class SaveRestoreRegisterOpType : u32 { | ||
| 117 | Restore = 0, | ||
| 118 | Save = 1, | ||
| 119 | ClearSaved = 2, | ||
| 120 | ClearRegs = 3, | ||
| 121 | }; | ||
| 122 | |||
| 123 | enum class DebugLogValueType : u32 { | ||
| 124 | MemoryRelAddr = 0, | ||
| 125 | MemoryOfsReg = 1, | ||
| 126 | RegisterRelAddr = 2, | ||
| 127 | RegisterOfsReg = 3, | ||
| 128 | RegisterValue = 4, | ||
| 129 | }; | ||
| 130 | |||
| 131 | union VmInt { | ||
| 132 | u8 bit8; | ||
| 133 | u16 bit16; | ||
| 134 | u32 bit32; | ||
| 135 | u64 bit64; | ||
| 136 | }; | ||
| 137 | |||
| 138 | struct StoreStaticOpcode { | ||
| 139 | u32 bit_width{}; | ||
| 140 | MemoryAccessType mem_type{}; | ||
| 141 | u32 offset_register{}; | ||
| 142 | u64 rel_address{}; | ||
| 143 | VmInt value{}; | ||
| 144 | }; | ||
| 145 | |||
| 146 | struct BeginConditionalOpcode { | ||
| 147 | u32 bit_width{}; | ||
| 148 | MemoryAccessType mem_type{}; | ||
| 149 | ConditionalComparisonType cond_type{}; | ||
| 150 | u64 rel_address{}; | ||
| 151 | VmInt value{}; | ||
| 152 | }; | ||
| 153 | |||
| 154 | struct EndConditionalOpcode {}; | ||
| 155 | |||
| 156 | struct ControlLoopOpcode { | ||
| 157 | bool start_loop{}; | ||
| 158 | u32 reg_index{}; | ||
| 159 | u32 num_iters{}; | ||
| 160 | }; | ||
| 161 | |||
| 162 | struct LoadRegisterStaticOpcode { | ||
| 163 | u32 reg_index{}; | ||
| 164 | u64 value{}; | ||
| 165 | }; | ||
| 166 | |||
| 167 | struct LoadRegisterMemoryOpcode { | ||
| 168 | u32 bit_width{}; | ||
| 169 | MemoryAccessType mem_type{}; | ||
| 170 | u32 reg_index{}; | ||
| 171 | bool load_from_reg{}; | ||
| 172 | u64 rel_address{}; | ||
| 173 | }; | ||
| 174 | |||
| 175 | struct StoreStaticToAddressOpcode { | ||
| 176 | u32 bit_width{}; | ||
| 177 | u32 reg_index{}; | ||
| 178 | bool increment_reg{}; | ||
| 179 | bool add_offset_reg{}; | ||
| 180 | u32 offset_reg_index{}; | ||
| 181 | u64 value{}; | ||
| 182 | }; | ||
| 183 | |||
| 184 | struct PerformArithmeticStaticOpcode { | ||
| 185 | u32 bit_width{}; | ||
| 186 | u32 reg_index{}; | ||
| 187 | RegisterArithmeticType math_type{}; | ||
| 188 | u32 value{}; | ||
| 189 | }; | ||
| 190 | |||
| 191 | struct BeginKeypressConditionalOpcode { | ||
| 192 | u32 key_mask{}; | ||
| 193 | }; | ||
| 194 | |||
| 195 | struct PerformArithmeticRegisterOpcode { | ||
| 196 | u32 bit_width{}; | ||
| 197 | RegisterArithmeticType math_type{}; | ||
| 198 | u32 dst_reg_index{}; | ||
| 199 | u32 src_reg_1_index{}; | ||
| 200 | u32 src_reg_2_index{}; | ||
| 201 | bool has_immediate{}; | ||
| 202 | VmInt value{}; | ||
| 203 | }; | ||
| 204 | |||
| 205 | struct StoreRegisterToAddressOpcode { | ||
| 206 | u32 bit_width{}; | ||
| 207 | u32 str_reg_index{}; | ||
| 208 | u32 addr_reg_index{}; | ||
| 209 | bool increment_reg{}; | ||
| 210 | StoreRegisterOffsetType ofs_type{}; | ||
| 211 | MemoryAccessType mem_type{}; | ||
| 212 | u32 ofs_reg_index{}; | ||
| 213 | u64 rel_address{}; | ||
| 214 | }; | ||
| 215 | |||
| 216 | struct BeginRegisterConditionalOpcode { | ||
| 217 | u32 bit_width{}; | ||
| 218 | ConditionalComparisonType cond_type{}; | ||
| 219 | u32 val_reg_index{}; | ||
| 220 | CompareRegisterValueType comp_type{}; | ||
| 221 | MemoryAccessType mem_type{}; | ||
| 222 | u32 addr_reg_index{}; | ||
| 223 | u32 other_reg_index{}; | ||
| 224 | u32 ofs_reg_index{}; | ||
| 225 | u64 rel_address{}; | ||
| 226 | VmInt value{}; | ||
| 227 | }; | ||
| 228 | |||
| 229 | struct SaveRestoreRegisterOpcode { | ||
| 230 | u32 dst_index{}; | ||
| 231 | u32 src_index{}; | ||
| 232 | SaveRestoreRegisterOpType op_type{}; | ||
| 233 | }; | ||
| 234 | |||
| 235 | struct SaveRestoreRegisterMaskOpcode { | ||
| 236 | SaveRestoreRegisterOpType op_type{}; | ||
| 237 | std::array<bool, 0x10> should_operate{}; | ||
| 238 | }; | ||
| 239 | |||
| 240 | struct DebugLogOpcode { | ||
| 241 | u32 bit_width{}; | ||
| 242 | u32 log_id{}; | ||
| 243 | DebugLogValueType val_type{}; | ||
| 244 | MemoryAccessType mem_type{}; | ||
| 245 | u32 addr_reg_index{}; | ||
| 246 | u32 val_reg_index{}; | ||
| 247 | u32 ofs_reg_index{}; | ||
| 248 | u64 rel_address{}; | ||
| 249 | }; | ||
| 250 | |||
| 251 | struct UnrecognizedInstruction { | ||
| 252 | CheatVmOpcodeType opcode{}; | ||
| 253 | }; | ||
| 254 | |||
| 255 | struct CheatVmOpcode { | ||
| 256 | bool begin_conditional_block{}; | ||
| 257 | std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, | ||
| 258 | LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, | ||
| 259 | PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, | ||
| 260 | PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, | ||
| 261 | BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, | ||
| 262 | SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction> | ||
| 263 | opcode{}; | ||
| 264 | }; | ||
| 265 | |||
| 266 | class DmntCheatVm { | ||
| 267 | public: | ||
| 268 | /// Helper Type for DmntCheatVm <=> yuzu Interface | ||
| 269 | class Callbacks { | ||
| 270 | public: | ||
| 271 | virtual ~Callbacks(); | ||
| 272 | |||
| 273 | virtual void MemoryRead(VAddr address, void* data, u64 size) = 0; | ||
| 274 | virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0; | ||
| 275 | |||
| 276 | virtual u64 HidKeysDown() = 0; | ||
| 277 | |||
| 278 | virtual void DebugLog(u8 id, u64 value) = 0; | ||
| 279 | virtual void CommandLog(std::string_view data) = 0; | ||
| 280 | }; | ||
| 281 | |||
| 282 | static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; | ||
| 283 | static constexpr std::size_t NumRegisters = 0x10; | ||
| 284 | |||
| 285 | explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks); | ||
| 286 | ~DmntCheatVm(); | ||
| 287 | |||
| 288 | std::size_t GetProgramSize() const { | ||
| 289 | return this->num_opcodes; | ||
| 290 | } | ||
| 291 | |||
| 292 | bool LoadProgram(const std::vector<CheatEntry>& cheats); | ||
| 293 | void Execute(const CheatProcessMetadata& metadata); | ||
| 294 | |||
| 295 | private: | ||
| 296 | std::unique_ptr<Callbacks> callbacks; | ||
| 297 | |||
| 298 | std::size_t num_opcodes = 0; | ||
| 299 | std::size_t instruction_ptr = 0; | ||
| 300 | std::size_t condition_depth = 0; | ||
| 301 | bool decode_success = false; | ||
| 302 | std::array<u32, MaximumProgramOpcodeCount> program{}; | ||
| 303 | std::array<u64, NumRegisters> registers{}; | ||
| 304 | std::array<u64, NumRegisters> saved_values{}; | ||
| 305 | std::array<std::size_t, NumRegisters> loop_tops{}; | ||
| 306 | |||
| 307 | bool DecodeNextOpcode(CheatVmOpcode& out); | ||
| 308 | void SkipConditionalBlock(); | ||
| 309 | void ResetState(); | ||
| 310 | |||
| 311 | // For implementing the DebugLog opcode. | ||
| 312 | void DebugLog(u32 log_id, u64 value); | ||
| 313 | |||
| 314 | void LogOpcode(const CheatVmOpcode& opcode); | ||
| 315 | |||
| 316 | static u64 GetVmInt(VmInt value, u32 bit_width); | ||
| 317 | static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, | ||
| 318 | MemoryAccessType mem_type, u64 rel_address); | ||
| 319 | }; | ||
| 320 | |||
| 321 | }; // namespace Memory | ||