summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Zach Hilman2018-12-22 21:31:38 -0500
committerGravatar Zach Hilman2019-03-04 18:39:58 -0500
commit769b3466823d988d262b13325c6eb926770d0868 (patch)
tree3e242ea8a6de37b5f3a1354b5055ce930cce419b /src
parentloader/nso: Set main code region in VMManager (diff)
downloadyuzu-769b3466823d988d262b13325c6eb926770d0868.tar.gz
yuzu-769b3466823d988d262b13325c6eb926770d0868.tar.xz
yuzu-769b3466823d988d262b13325c6eb926770d0868.zip
cheat_engine: Add parser and interpreter for game cheats
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/file_sys/cheat_engine.cpp487
-rw-r--r--src/core/file_sys/cheat_engine.h226
3 files changed, 715 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 988356c65..199a0c2e3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -31,6 +31,8 @@ add_library(core STATIC
31 file_sys/bis_factory.h 31 file_sys/bis_factory.h
32 file_sys/card_image.cpp 32 file_sys/card_image.cpp
33 file_sys/card_image.h 33 file_sys/card_image.h
34 file_sys/cheat_engine.cpp
35 file_sys/cheat_engine.h
34 file_sys/content_archive.cpp 36 file_sys/content_archive.cpp
35 file_sys/content_archive.h 37 file_sys/content_archive.h
36 file_sys/control_metadata.cpp 38 file_sys/control_metadata.cpp
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp
new file mode 100644
index 000000000..e4383d8c1
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.cpp
@@ -0,0 +1,487 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/file_sys/cheat_engine.h"
13#include "core/hle/kernel/process.h"
14#include "core/hle/service/hid/controllers/controller_base.h"
15#include "core/hle/service/hid/controllers/npad.h"
16#include "core/hle/service/hid/hid.h"
17#include "core/hle/service/sm/sm.h"
18
19namespace FileSys {
20
21constexpr u64 CHEAT_ENGINE_TICKS = CoreTiming::BASE_CLOCK_RATE / 60;
22constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
23
24u64 Cheat::Address() const {
25 u64 out;
26 std::memcpy(&out, raw.data(), sizeof(u64));
27 return Common::swap64(out) & 0xFFFFFFFFFF;
28}
29
30u64 Cheat::ValueWidth(u64 offset) const {
31 return Value(offset, width);
32}
33
34u64 Cheat::Value(u64 offset, u64 width) const {
35 u64 out;
36 std::memcpy(&out, raw.data() + offset, sizeof(u64));
37 out = Common::swap64(out);
38 if (width == 8)
39 return out;
40 return out & ((1ull << (width * CHAR_BIT)) - 1);
41}
42
43u32 Cheat::KeypadValue() const {
44 u32 out;
45 std::memcpy(&out, raw.data(), sizeof(u32));
46 return Common::swap32(out) & 0x0FFFFFFF;
47}
48
49void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
50 VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
51 this->main_region_begin = main_begin;
52 this->main_region_end = main_end;
53 this->heap_region_begin = heap_begin;
54 this->heap_region_end = heap_end;
55 this->writer = writer;
56 this->reader = reader;
57}
58
59MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
60
61void CheatList::Execute() {
62 MICROPROFILE_SCOPE(Cheat_Engine);
63
64 std::fill(scratch.begin(), scratch.end(), 0);
65 in_standard = false;
66 for (std::size_t i = 0; i < master_list.size(); ++i) {
67 LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
68 current_block = i;
69 ExecuteBlock(master_list[i].second);
70 }
71
72 in_standard = true;
73 for (std::size_t i = 0; i < standard_list.size(); ++i) {
74 LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
75 current_block = i;
76 ExecuteBlock(standard_list[i].second);
77 }
78}
79
80CheatList::CheatList(ProgramSegment master, ProgramSegment standard)
81 : master_list(master), standard_list(standard) {}
82
83bool CheatList::EvaluateConditional(const Cheat& cheat) const {
84 using ComparisonFunction = bool (*)(u64, u64);
85 constexpr ComparisonFunction comparison_functions[] = {
86 [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
87 [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
88 [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
89 };
90
91 if (cheat.type == CodeType::ConditionalInput) {
92 const auto applet_resource = Core::System::GetInstance()
93 .ServiceManager()
94 .GetService<Service::HID::Hid>("hid")
95 ->GetAppletResource();
96 if (applet_resource == nullptr) {
97 LOG_WARNING(
98 Common_Filesystem,
99 "Attempted to evaluate input conditional, but applet resource is not initialized!");
100 return false;
101 }
102
103 const auto press_state =
104 applet_resource
105 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
106 .GetPressState();
107 return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
108 }
109
110 ASSERT(cheat.type == CodeType::Conditional);
111
112 const auto offset =
113 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
114 ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
115 const auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
116 const auto addr = cheat.Address() + offset;
117
118 return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
119}
120
121void CheatList::ProcessBlockPairs(const Block& block) {
122 block_pairs.clear();
123
124 u64 scope = 0;
125 std::map<u64, u64> pairs;
126
127 for (std::size_t i = 0; i < block.size(); ++i) {
128 const auto& cheat = block[i];
129
130 switch (cheat.type) {
131 case CodeType::Conditional:
132 case CodeType::ConditionalInput:
133 pairs.insert_or_assign(scope, i);
134 ++scope;
135 break;
136 case CodeType::EndConditional: {
137 --scope;
138 const auto idx = pairs.at(scope);
139 block_pairs.insert_or_assign(idx, i);
140 break;
141 }
142 case CodeType::Loop: {
143 if (cheat.end_of_loop) {
144 --scope;
145 const auto idx = pairs.at(scope);
146 block_pairs.insert_or_assign(idx, i);
147 } else {
148 pairs.insert_or_assign(scope, i);
149 ++scope;
150 }
151 break;
152 }
153 }
154 }
155}
156
157void CheatList::WriteImmediate(const Cheat& cheat) {
158 const auto offset =
159 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
160 auto& register_3 = scratch.at(cheat.register_3);
161
162 const auto addr = cheat.Address() + offset + register_3;
163 LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
164 cheat.Value(8, cheat.width));
165 writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
166}
167
168void CheatList::BeginConditional(const Cheat& cheat) {
169 if (!EvaluateConditional(cheat)) {
170 const auto iter = block_pairs.find(current_index);
171 ASSERT(iter != block_pairs.end());
172 current_index = iter->second - 1;
173 }
174}
175
176void CheatList::EndConditional(const Cheat& cheat) {
177 LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
178}
179
180void CheatList::Loop(const Cheat& cheat) {
181 if (cheat.end_of_loop.Value())
182 ASSERT(!cheat.end_of_loop.Value());
183
184 auto& register_3 = scratch.at(cheat.register_3);
185 const auto iter = block_pairs.find(current_index);
186 ASSERT(iter != block_pairs.end());
187 ASSERT(iter->first < iter->second);
188
189 for (int i = cheat.Value(4, 4); i >= 0; --i) {
190 register_3 = 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 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 ArithmeticFunction 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 ArithmeticOverflowCheck 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 const auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
254 const 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 const auto iter = block_pairs.find(current_index);
272 ASSERT(iter != block_pairs.end());
273 current_index = iter->second - 1;
274 }
275}
276
277VAddr CheatList::SanitizeAddress(VAddr in) const {
278 if ((in < main_region_begin || in >= main_region_end) &&
279 (in < heap_region_begin || in >= heap_region_end)) {
280 LOG_ERROR(Common_Filesystem,
281 "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
282 "the cheat may be incorrect. However, this may be normal early in execution if "
283 "the game has not properly set up yet.",
284 in);
285 return 0; ///< Invalid addresses will hard crash
286 }
287
288 return in;
289}
290
291void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
292 using CheatOperationFunction = void (CheatList::*)(const Cheat&);
293 constexpr CheatOperationFunction cheat_operation_functions[] = {
294 &CheatList::WriteImmediate, &CheatList::BeginConditional,
295 &CheatList::EndConditional, &CheatList::Loop,
296 &CheatList::LoadImmediate, &CheatList::LoadIndexed,
297 &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
298 &CheatList::BeginConditionalInput,
299 };
300
301 const auto index = static_cast<u8>(cheat.type.Value());
302 ASSERT(index < sizeof(cheat_operation_functions));
303 const auto op = cheat_operation_functions[index];
304 (this->*op)(cheat);
305}
306
307void CheatList::ExecuteBlock(const Block& block) {
308 encountered_loops.clear();
309
310 ProcessBlockPairs(block);
311 for (std::size_t i = 0; i < block.size(); ++i) {
312 current_index = i;
313 ExecuteSingleCheat(block[i]);
314 i = current_index;
315 }
316}
317
318CheatParser::~CheatParser() = default;
319
320CheatList CheatParser::MakeCheatList(CheatList::ProgramSegment master,
321 CheatList::ProgramSegment standard) const {
322 return {master, standard};
323}
324
325TextCheatParser::~TextCheatParser() = default;
326
327CheatList TextCheatParser::Parse(const std::vector<u8>& data) const {
328 std::stringstream ss;
329 ss.write(reinterpret_cast<const char*>(data.data()), data.size());
330
331 std::vector<std::string> lines;
332 std::string stream_line;
333 while (std::getline(ss, stream_line)) {
334 // Remove a trailing \r
335 if (!stream_line.empty() && stream_line.back() == '\r')
336 stream_line.pop_back();
337 lines.push_back(std::move(stream_line));
338 }
339
340 CheatList::ProgramSegment master_list;
341 CheatList::ProgramSegment standard_list;
342
343 for (std::size_t i = 0; i < lines.size(); ++i) {
344 auto line = lines[i];
345
346 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
347 const auto master = line[0] == '{';
348 const auto begin = master ? line.find('{') : line.find('[');
349 const auto end = master ? line.find_last_of('}') : line.find_last_of(']');
350
351 ASSERT(begin != std::string::npos && end != std::string::npos);
352
353 const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
354 CheatList::Block block{};
355
356 while (i < lines.size() - 1) {
357 line = lines[++i];
358 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
359 --i;
360 break;
361 }
362
363 if (line.size() < 8)
364 continue;
365
366 Cheat out{};
367 out.raw = ParseSingleLineCheat(line);
368 block.push_back(out);
369 }
370
371 (master ? master_list : standard_list).emplace_back(patch_name, block);
372 }
373 }
374
375 return MakeCheatList(master_list, standard_list);
376}
377
378std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
379 std::array<u8, 16> out{};
380
381 if (line.size() < 8)
382 return out;
383
384 const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
385 std::memcpy(out.data(), word1.data(), sizeof(u32));
386
387 if (line.size() < 17 || line[8] != ' ')
388 return out;
389
390 const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
391 std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
392
393 if (line.size() < 26 || line[17] != ' ') {
394 // Perform shifting in case value is truncated early.
395 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
396 if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
397 type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
398 std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
399 std::memset(out.data() + 4, 0, sizeof(u32));
400 }
401
402 return out;
403 }
404
405 const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
406 std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
407
408 if (line.size() < 35 || line[26] != ' ') {
409 // Perform shifting in case value is truncated early.
410 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
411 if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
412 std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
413 std::memset(out.data() + 8, 0, sizeof(u32));
414 }
415
416 return out;
417 }
418
419 const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
420 std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
421
422 return out;
423}
424
425u64 MemoryReadImpl(u8 width, VAddr addr) {
426 switch (width) {
427 case 1:
428 return Memory::Read8(addr);
429 case 2:
430 return Memory::Read16(addr);
431 case 4:
432 return Memory::Read32(addr);
433 case 8:
434 return Memory::Read64(addr);
435 default:
436 UNREACHABLE();
437 return 0;
438 }
439}
440
441void MemoryWriteImpl(u8 width, VAddr addr, u64 value) {
442 switch (width) {
443 case 1:
444 Memory::Write8(addr, static_cast<u8>(value));
445 break;
446 case 2:
447 Memory::Write16(addr, static_cast<u16>(value));
448 break;
449 case 4:
450 Memory::Write32(addr, static_cast<u32>(value));
451 break;
452 case 8:
453 Memory::Write64(addr, value);
454 break;
455 default:
456 UNREACHABLE();
457 }
458}
459
460CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id)
461 : cheats(std::move(cheats)) {
462 event = CoreTiming::RegisterEvent(
463 "CheatEngine::FrameCallback::" + build_id,
464 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
465 CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS, event);
466
467 const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager();
468 for (auto& list : this->cheats) {
469 list.SetMemoryParameters(
470 vm_manager.GetMainCodeRegionBaseAddress(), vm_manager.GetHeapRegionBaseAddress(),
471 vm_manager.GetMainCodeRegionEndAddress(), vm_manager.GetHeapRegionEndAddress(),
472 &MemoryWriteImpl, &MemoryReadImpl);
473 }
474}
475
476CheatEngine::~CheatEngine() {
477 CoreTiming::UnscheduleEvent(event, 0);
478}
479
480void CheatEngine::FrameCallback(u64 userdata, int cycles_late) {
481 for (auto& list : cheats)
482 list.Execute();
483
484 CoreTiming::ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
485}
486
487} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h
new file mode 100644
index 000000000..d7a8654b7
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.h
@@ -0,0 +1,226 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <map>
8#include <set>
9#include <vector>
10#include <queue>
11#include "common/bit_field.h"
12#include "common/common_types.h"
13
14namespace CoreTiming {
15struct EventType;
16}
17
18namespace FileSys {
19
20enum class CodeType : u32 {
21 // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
22 // Writes a T sized value Y to the address A added to the value of register R in memory domain M
23 WriteImmediate = 0,
24
25 // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
26 // Compares the T sized value Y to the value at address A in memory domain M using the
27 // conditional function C. If success, continues execution. If failure, jumps to the matching
28 // EndConditional statement.
29 Conditional = 1,
30
31 // 20000000
32 // Terminates a Conditional or ConditionalInput block.
33 EndConditional = 2,
34
35 // 300R0000 VVVVVVVV
36 // Starts looping V times, storing the current count in register R.
37 // Loop block is terminated with a matching 310R0000.
38 Loop = 3,
39
40 // 400R0000 VVVVVVVV VVVVVVVV
41 // Sets the value of register R to the value V.
42 LoadImmediate = 4,
43
44 // 5TMRI0AA AAAAAAAA
45 // Sets the value of register R to the value of width T at address A in memory domain M, with
46 // the current value of R added to the address if I == 1.
47 LoadIndexed = 5,
48
49 // 6T0RIFG0 VVVVVVVV VVVVVVVV
50 // Writes the value V of width T to the memory address stored in register R. Adds the value of
51 // register G to the final calculation if F is nonzero. Increments the value of register R by T
52 // after operation if I is nonzero.
53 StoreIndexed = 6,
54
55 // 7T0RA000 VVVVVVVV
56 // Performs the arithmetic operation A on the value in register R and the value V of width T,
57 // storing the result in register R.
58 RegisterArithmetic = 7,
59
60 // 8KKKKKKK
61 // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
62 // execution continues. If none are, execution skips to the next EndConditional command.
63 ConditionalInput = 8,
64};
65
66enum class MemoryType : u32 {
67 // Addressed relative to start of main NSO
68 MainNSO = 0,
69
70 // Addressed relative to start of heap
71 Heap = 1,
72};
73
74enum class ArithmeticOp : u32 {
75 Add = 0,
76 Sub = 1,
77 Mult = 2,
78 LShift = 3,
79 RShift = 4,
80};
81
82enum class ComparisonOp : u32 {
83 GreaterThan = 1,
84 GreaterThanEqual = 2,
85 LessThan = 3,
86 LessThanEqual = 4,
87 Equal = 5,
88 Inequal = 6,
89};
90
91union Cheat {
92 std::array<u8, 16> raw;
93
94 BitField<4, 4, CodeType> type;
95 BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
96 BitField<0, 4, u32> end_of_loop;
97 BitField<12, 4, MemoryType> memory_type;
98 BitField<8, 4, u32> register_3;
99 BitField<8, 4, ComparisonOp> comparison_op;
100 BitField<20, 4, u32> load_from_register;
101 BitField<20, 4, u32> increment_register;
102 BitField<20, 4, ArithmeticOp> arithmetic_op;
103 BitField<16, 4, u32> add_additional_register;
104 BitField<28, 4, u32> register_6;
105
106 u64 Address() const;
107 u64 ValueWidth(u64 offset) const;
108 u64 Value(u64 offset, u64 width) const;
109 u32 KeypadValue() const;
110};
111
112class CheatParser;
113
114// Represents a full collection of cheats for a game. The Execute function should be called every
115// interval that all cheats should be executed. Clients should not directly instantiate this class
116// (hence private constructor), they should instead receive an instance from CheatParser, which
117// guarantees the list is always in an acceptable state.
118class CheatList {
119public:
120 friend class CheatParser;
121
122 using Block = std::vector<Cheat>;
123 using ProgramSegment = std::vector<std::pair<std::string, Block>>;
124
125 // (width in bytes, address, value)
126 using MemoryWriter = void (*)(u8, VAddr, u64);
127 // (width in bytes, address) -> value
128 using MemoryReader = u64 (*)(u8, VAddr);
129
130 void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
131 MemoryWriter writer, MemoryReader reader);
132
133 void Execute();
134
135private:
136 CheatList(ProgramSegment master, ProgramSegment standard);
137
138 void ProcessBlockPairs(const Block& block);
139 void ExecuteSingleCheat(const Cheat& cheat);
140
141 void ExecuteBlock(const Block& block);
142
143 bool EvaluateConditional(const Cheat& cheat) const;
144
145 // Individual cheat operations
146 void WriteImmediate(const Cheat& cheat);
147 void BeginConditional(const Cheat& cheat);
148 void EndConditional(const Cheat& cheat);
149 void Loop(const Cheat& cheat);
150 void LoadImmediate(const Cheat& cheat);
151 void LoadIndexed(const Cheat& cheat);
152 void StoreIndexed(const Cheat& cheat);
153 void RegisterArithmetic(const Cheat& cheat);
154 void BeginConditionalInput(const Cheat& cheat);
155
156 VAddr SanitizeAddress(VAddr in) const;
157
158 // Master Codes are defined as codes that cannot be disabled and are run prior to all
159 // others.
160 ProgramSegment master_list;
161 // All other codes
162 ProgramSegment standard_list;
163
164 bool in_standard = false;
165
166 // 16 (0x0-0xF) scratch registers that can be used by cheats
167 std::array<u64, 16> scratch{};
168
169 MemoryWriter writer = nullptr;
170 MemoryReader reader = nullptr;
171
172 u64 main_region_begin{};
173 u64 heap_region_begin{};
174 u64 main_region_end{};
175 u64 heap_region_end{};
176
177 u64 current_block{};
178 // The current index of the cheat within the current Block
179 u64 current_index{};
180
181 // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
182 // pushed onto this queue. When a end block is encountered, the condition is checked.
183 std::map<u64, u64> block_pairs;
184
185 std::set<u64> encountered_loops;
186};
187
188// Intermediary class that parses a text file or other disk format for storing cheats into a
189// CheatList object, that can be used for execution.
190class CheatParser {
191public:
192 virtual ~CheatParser();
193
194 virtual CheatList Parse(const std::vector<u8>& data) const = 0;
195
196protected:
197 CheatList MakeCheatList(CheatList::ProgramSegment master,
198 CheatList::ProgramSegment standard) const;
199};
200
201// CheatParser implementation that parses text files
202class TextCheatParser final : public CheatParser {
203public:
204 ~TextCheatParser() override;
205
206 CheatList Parse(const std::vector<u8>& data) const override;
207
208private:
209 std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
210};
211
212// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
213class CheatEngine final {
214public:
215 CheatEngine(std::vector<CheatList> cheats, const std::string& build_id);
216 ~CheatEngine();
217
218private:
219 void FrameCallback(u64 userdata, int cycles_late);
220
221 CoreTiming::EventType* event;
222
223 std::vector<CheatList> cheats;
224};
225
226} // namespace FileSys