summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Zach Hilman2019-05-30 19:35:03 -0400
committerGravatar Zach Hilman2019-09-21 21:44:22 -0400
commit7d41c1f52390abb47e67d3fc43310e9d87fbd862 (patch)
tree79a4891115f3efd039b98cc47d63380d811021b1 /src
parentmemory: Port Atmosphere's DmntCheatVm (diff)
downloadyuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.gz
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.xz
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.zip
cheat_engine: Move to memory and strip VM
This is to go with the Atmosphere VM port, now it just contains the callbacks needed for the interface between DmntCheatVm and yuzu, along with the cheat parsers.
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt7
-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/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
5 files changed, 325 insertions, 728 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 877a9e353..a6b56c9c6 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
@@ -477,6 +475,11 @@ add_library(core STATIC
477 loader/nsp.h 475 loader/nsp.h
478 loader/xci.cpp 476 loader/xci.cpp
479 loader/xci.h 477 loader/xci.h
478 memory/cheat_engine.cpp
479 memory/cheat_engine.h
480 memory/dmnt_cheat_types.h
481 memory/dmnt_cheat_vm.cpp
482 memory/dmnt_cheat_vm.h
480 memory.cpp 483 memory.cpp
481 memory.h 484 memory.h
482 memory_setup.h 485 memory_setup.h
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/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
new file mode 100644
index 000000000..ea5c76fc0
--- /dev/null
+++ b/src/core/memory/cheat_engine.cpp
@@ -0,0 +1,234 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/hle/kernel/process.h"
13#include "core/hle/service/hid/controllers/npad.h"
14#include "core/hle/service/hid/hid.h"
15#include "core/hle/service/sm/sm.h"
16#include "core/memory/cheat_engine.h"
17
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 (std::isspace(data[i])) {
106 continue;
107 }
108
109 if (data[i] == '{') {
110 current_entry = 0;
111
112 if (out[*current_entry].definition.num_opcodes > 0) {
113 return {};
114 }
115
116 const auto name = ExtractName<'}'>(data, i + 1);
117 if (name.empty()) {
118 return {};
119 }
120
121 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
122 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
123 name.size()));
124 out[*current_entry]
125 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
126 '\0';
127
128 i += name.length() + 1;
129 } else if (data[i] == '[') {
130 current_entry = out.size();
131 out.emplace_back();
132
133 const auto name = ExtractName<']'>(data, i + 1);
134 if (name.empty()) {
135 return {};
136 }
137
138 std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
139 std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
140 name.size()));
141 out[*current_entry]
142 .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
143 '\0';
144
145 i += name.length() + 1;
146 } else if (std::isxdigit(data[i])) {
147 if (!current_entry || out[*current_entry].definition.num_opcodes >=
148 out[*current_entry].definition.opcodes.size()) {
149 return {};
150 }
151
152 const auto hex = std::string(data.substr(i, 8));
153 if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
154 return {};
155 }
156
157 out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
158 std::stoul(hex, nullptr, 0x10);
159
160 i += 8;
161 } else {
162 return {};
163 }
164 }
165
166 out[0].enabled = out[0].definition.num_opcodes > 0;
167 out[0].cheat_id = 0;
168
169 for (u32 i = 1; i < out.size(); ++i) {
170 out[i].enabled = out[i].definition.num_opcodes > 0;
171 out[i].cheat_id = i;
172 }
173
174 return out;
175}
176
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::HexArrayToString(metadata.main_nso_build_id),
192 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
193 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
194
195 metadata.process_id = system.CurrentProcess()->GetProcessID();
196 metadata.title_id = system.CurrentProcess()->GetTitleID();
197
198 const auto& vm_manager = system.CurrentProcess()->VMManager();
199 metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
200 metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
201 vm_manager.GetAddressSpaceSize()};
202 metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
203
204 is_pending_reload.exchange(true);
205}
206
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