summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/core.cpp25
-rw-r--r--src/core/core.h10
-rw-r--r--src/core/file_sys/cheat_engine.cpp492
-rw-r--r--src/core/file_sys/cheat_engine.h234
-rw-r--r--src/core/file_sys/patch_manager.cpp32
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/loader/nso.cpp3
-rw-r--r--src/core/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
-rw-r--r--src/core/memory/dmnt_cheat_types.h58
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp1212
-rw-r--r--src/core/memory/dmnt_cheat_vm.h321
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
547void 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
554void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { 552void 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
560void 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
562void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { 567void 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
20namespace FileSys { 20namespace FileSys {
21class CheatList;
22class ContentProvider; 21class ContentProvider;
23class ContentProviderUnion; 22class ContentProviderUnion;
24enum class ContentProviderUnionSlot; 23enum class ContentProviderUnionSlot;
@@ -36,6 +35,10 @@ class AppLoader;
36enum class ResultStatus : u16; 35enum class ResultStatus : u16;
37} // namespace Loader 36} // namespace Loader
38 37
38namespace Memory {
39struct CheatEntry;
40} // namespace Memory
41
39namespace Service { 42namespace Service {
40 43
41namespace AM::Applets { 44namespace 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
18namespace FileSys {
19
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22
23u64 Cheat::Address() const {
24 u64 out;
25 std::memcpy(&out, raw.data(), sizeof(u64));
26 return Common::swap64(out) & 0xFFFFFFFFFF;
27}
28
29u64 Cheat::ValueWidth(u64 offset) const {
30 return Value(offset, width);
31}
32
33u64 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
42u32 Cheat::KeypadValue() const {
43 u32 out;
44 std::memcpy(&out, raw.data(), sizeof(u32));
45 return Common::swap32(out) & 0x0FFFFFFF;
46}
47
48void 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
58MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
59
60void 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
79CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
80 : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
81
82bool 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
118void 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
154void 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
165void 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
175void CheatList::EndConditional(const Cheat& cheat) {
176 LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
177}
178
179void 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
201void 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
209void 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
220void 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
230void 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
269void 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
278VAddr 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
292void 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
308void 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
319CheatParser::~CheatParser() = default;
320
321CheatList 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
326TextCheatParser::~TextCheatParser() = default;
327
328CheatList 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
379std::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
426namespace {
427u64 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
443void 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
463CheatEngine::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
480CheatEngine::~CheatEngine() {
481 core_timing.UnscheduleEvent(event, 0);
482}
483
484void 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
13namespace Core {
14class System;
15}
16
17namespace Core::Timing {
18class CoreTiming;
19struct EventType;
20} // namespace Core::Timing
21
22namespace FileSys {
23
24enum 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
70enum 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
78enum class ArithmeticOp : u32 {
79 Add = 0,
80 Sub = 1,
81 Mult = 2,
82 LShift = 3,
83 RShift = 4,
84};
85
86enum class ComparisonOp : u32 {
87 GreaterThan = 1,
88 GreaterThanEqual = 2,
89 LessThan = 3,
90 LessThanEqual = 4,
91 Equal = 5,
92 Inequal = 6,
93};
94
95union 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
116class 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.
122class CheatList {
123public:
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
139private:
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.
196class CheatParser {
197public:
198 virtual ~CheatParser();
199
200 virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
201
202protected:
203 CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
204 CheatList::ProgramSegment standard) const;
205};
206
207// CheatParser implementation that parses text files
208class TextCheatParser final : public CheatParser {
209public:
210 ~TextCheatParser() override;
211
212 CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
213
214private:
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
219class CheatEngine final {
220public:
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
225private:
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
27namespace FileSys { 28namespace 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
250static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id, 251namespace {
251 const std::array<u8, 0x20>& build_id_, 252std::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
274std::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 = 279std::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
15namespace Core { 15namespace Core {
16class System; 16class 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
18namespace Memory {
19
20constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
21constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
22
23StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
24 const CheatProcessMetadata& metadata)
25 : system(system), metadata(metadata) {}
26
27StandardVmCallbacks::~StandardVmCallbacks() = default;
28
29void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
30 ReadBlock(SanitizeAddress(address), data, size);
31}
32
33void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
34 WriteBlock(SanitizeAddress(address), data, size);
35}
36
37u64 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
53void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
54 LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
55}
56
57void StandardVmCallbacks::CommandLog(std::string_view data) {
58 LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
59 data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
60}
61
62VAddr 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
79CheatParser::~CheatParser() = default;
80
81TextCheatParser::~TextCheatParser() = default;
82
83namespace {
84template <char match>
85std::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
99std::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
177CheatEngine::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
185CheatEngine::~CheatEngine() {
186 core_timing.UnscheduleEvent(event, 0);
187}
188
189void 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
207void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
208 metadata.main_nso_extents = {main_region_begin, main_region_size};
209}
210
211void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
212 this->cheats = std::move(cheats);
213 is_pending_reload.exchange(true);
214}
215
216MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
217
218void 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
13namespace Core {
14class System;
15}
16
17namespace Core::Timing {
18class CoreTiming;
19struct EventType;
20} // namespace Core::Timing
21
22namespace Memory {
23
24class StandardVmCallbacks : public DmntCheatVm::Callbacks {
25public:
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
35private:
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.
44class CheatParser {
45public:
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
53class TextCheatParser final : public CheatParser {
54public:
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
61class CheatEngine final {
62public:
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
72private:
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
29namespace Memory {
30
31struct MemoryRegionExtents {
32 u64 base{};
33 u64 size{};
34};
35
36struct 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
46struct CheatDefinition {
47 std::array<char, 0x40> readable_name{};
48 u32 num_opcodes{};
49 std::array<u32, 0x100> opcodes{};
50};
51
52struct 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
30namespace Memory {
31
32DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
33
34DmntCheatVm::~DmntCheatVm() = default;
35
36void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
37 callbacks->DebugLog(static_cast<u8>(log_id), value);
38}
39
40void 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
232DmntCheatVm::Callbacks::~Callbacks() = default;
233
234bool 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
608void 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
638u64 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
654u64 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
665void 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
674bool 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
695void 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, &registers[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
33namespace Memory {
34
35enum 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
68enum class MemoryAccessType : u32 {
69 MainNso = 0,
70 Heap = 1,
71};
72
73enum class ConditionalComparisonType : u32 {
74 GT = 1,
75 GE = 2,
76 LT = 3,
77 LE = 4,
78 EQ = 5,
79 NE = 6,
80};
81
82enum 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
98enum class StoreRegisterOffsetType : u32 {
99 None = 0,
100 Reg = 1,
101 Imm = 2,
102 MemReg = 3,
103 MemImm = 4,
104 MemImmReg = 5,
105};
106
107enum class CompareRegisterValueType : u32 {
108 MemoryRelAddr = 0,
109 MemoryOfsReg = 1,
110 RegisterRelAddr = 2,
111 RegisterOfsReg = 3,
112 StaticValue = 4,
113 OtherRegister = 5,
114};
115
116enum class SaveRestoreRegisterOpType : u32 {
117 Restore = 0,
118 Save = 1,
119 ClearSaved = 2,
120 ClearRegs = 3,
121};
122
123enum class DebugLogValueType : u32 {
124 MemoryRelAddr = 0,
125 MemoryOfsReg = 1,
126 RegisterRelAddr = 2,
127 RegisterOfsReg = 3,
128 RegisterValue = 4,
129};
130
131union VmInt {
132 u8 bit8;
133 u16 bit16;
134 u32 bit32;
135 u64 bit64;
136};
137
138struct StoreStaticOpcode {
139 u32 bit_width{};
140 MemoryAccessType mem_type{};
141 u32 offset_register{};
142 u64 rel_address{};
143 VmInt value{};
144};
145
146struct BeginConditionalOpcode {
147 u32 bit_width{};
148 MemoryAccessType mem_type{};
149 ConditionalComparisonType cond_type{};
150 u64 rel_address{};
151 VmInt value{};
152};
153
154struct EndConditionalOpcode {};
155
156struct ControlLoopOpcode {
157 bool start_loop{};
158 u32 reg_index{};
159 u32 num_iters{};
160};
161
162struct LoadRegisterStaticOpcode {
163 u32 reg_index{};
164 u64 value{};
165};
166
167struct LoadRegisterMemoryOpcode {
168 u32 bit_width{};
169 MemoryAccessType mem_type{};
170 u32 reg_index{};
171 bool load_from_reg{};
172 u64 rel_address{};
173};
174
175struct 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
184struct PerformArithmeticStaticOpcode {
185 u32 bit_width{};
186 u32 reg_index{};
187 RegisterArithmeticType math_type{};
188 u32 value{};
189};
190
191struct BeginKeypressConditionalOpcode {
192 u32 key_mask{};
193};
194
195struct 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
205struct 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
216struct 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
229struct SaveRestoreRegisterOpcode {
230 u32 dst_index{};
231 u32 src_index{};
232 SaveRestoreRegisterOpType op_type{};
233};
234
235struct SaveRestoreRegisterMaskOpcode {
236 SaveRestoreRegisterOpType op_type{};
237 std::array<bool, 0x10> should_operate{};
238};
239
240struct 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
251struct UnrecognizedInstruction {
252 CheatVmOpcodeType opcode{};
253};
254
255struct 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
266class DmntCheatVm {
267public:
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
295private:
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