summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar bunnei2019-03-21 21:41:59 -0400
committerGravatar GitHub2019-03-21 21:41:59 -0400
commit639f0c524d3406b7c156dc75af4e934c6b5adcb6 (patch)
treee03093771bbffc832c48670331cf9af54aa4a9fc
parentMerge pull request #2260 from lioncash/sdl (diff)
parentvm_manager: Remove cheat-specific ranges from VMManager (diff)
downloadyuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.tar.gz
yuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.tar.xz
yuzu-639f0c524d3406b7c156dc75af4e934c6b5adcb6.zip
Merge pull request #1933 from DarkLordZach/cheat-engine
file_sys: Implement parser and interpreter for game memory cheats
Diffstat (limited to '')
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/file_sys/cheat_engine.cpp493
-rw-r--r--src/core/file_sys/cheat_engine.h227
-rw-r--r--src/core/file_sys/patch_manager.cpp54
-rw-r--r--src/core/file_sys/patch_manager.h4
-rw-r--r--src/core/hle/kernel/vm_manager.h3
-rw-r--r--src/core/hle/service/hid/hid.h3
-rw-r--r--src/core/loader/nso.cpp12
10 files changed, 813 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 16920e2e9..bbbe60896 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -31,6 +31,8 @@ add_library(core STATIC
31 file_sys/bis_factory.h 31 file_sys/bis_factory.h
32 file_sys/card_image.cpp 32 file_sys/card_image.cpp
33 file_sys/card_image.h 33 file_sys/card_image.h
34 file_sys/cheat_engine.cpp
35 file_sys/cheat_engine.h
34 file_sys/content_archive.cpp 36 file_sys/content_archive.cpp
35 file_sys/content_archive.h 37 file_sys/content_archive.h
36 file_sys/control_metadata.cpp 38 file_sys/control_metadata.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 89b3fb418..a88e332be 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -32,6 +32,7 @@
32#include "core/perf_stats.h" 32#include "core/perf_stats.h"
33#include "core/settings.h" 33#include "core/settings.h"
34#include "core/telemetry_session.h" 34#include "core/telemetry_session.h"
35#include "file_sys/cheat_engine.h"
35#include "frontend/applets/profile_select.h" 36#include "frontend/applets/profile_select.h"
36#include "frontend/applets/software_keyboard.h" 37#include "frontend/applets/software_keyboard.h"
37#include "frontend/applets/web_browser.h" 38#include "frontend/applets/web_browser.h"
@@ -205,6 +206,7 @@ struct System::Impl {
205 GDBStub::Shutdown(); 206 GDBStub::Shutdown();
206 Service::Shutdown(); 207 Service::Shutdown();
207 service_manager.reset(); 208 service_manager.reset();
209 cheat_engine.reset();
208 telemetry_session.reset(); 210 telemetry_session.reset();
209 gpu_core.reset(); 211 gpu_core.reset();
210 212
@@ -255,6 +257,8 @@ struct System::Impl {
255 CpuCoreManager cpu_core_manager; 257 CpuCoreManager cpu_core_manager;
256 bool is_powered_on = false; 258 bool is_powered_on = false;
257 259
260 std::unique_ptr<FileSys::CheatEngine> cheat_engine;
261
258 /// Frontend applets 262 /// Frontend applets
259 std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector; 263 std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
260 std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard; 264 std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
@@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
453 return impl->debug_context.get(); 457 return impl->debug_context.get();
454} 458}
455 459
460void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
461 const std::string& build_id, VAddr code_region_start,
462 VAddr code_region_end) {
463 impl->cheat_engine =
464 std::make_unique<FileSys::CheatEngine>(list, build_id, code_region_start, code_region_end);
465}
466
456void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) { 467void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
457 impl->virtual_filesystem = std::move(vfs); 468 impl->virtual_filesystem = std::move(vfs);
458} 469}
diff --git a/src/core/core.h b/src/core/core.h
index ba76a41d8..4d83b93cc 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -20,6 +20,7 @@ class WebBrowserApplet;
20} // namespace Core::Frontend 20} // namespace Core::Frontend
21 21
22namespace FileSys { 22namespace FileSys {
23class CheatList;
23class VfsFilesystem; 24class VfsFilesystem;
24} // namespace FileSys 25} // namespace FileSys
25 26
@@ -253,6 +254,9 @@ public:
253 254
254 std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const; 255 std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
255 256
257 void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
258 VAddr code_region_start, VAddr code_region_end);
259
256 void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet); 260 void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
257 261
258 const Frontend::ProfileSelectApplet& GetProfileSelector() const; 262 const Frontend::ProfileSelectApplet& GetProfileSelector() const;
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp
new file mode 100644
index 000000000..09ca9d705
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.cpp
@@ -0,0 +1,493 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <locale>
6#include "common/hex_util.h"
7#include "common/microprofile.h"
8#include "common/swap.h"
9#include "core/core.h"
10#include "core/core_timing.h"
11#include "core/core_timing_util.h"
12#include "core/file_sys/cheat_engine.h"
13#include "core/hle/kernel/process.h"
14#include "core/hle/service/hid/controllers/controller_base.h"
15#include "core/hle/service/hid/controllers/npad.h"
16#include "core/hle/service/hid/hid.h"
17#include "core/hle/service/sm/sm.h"
18
19namespace FileSys {
20
21constexpr u64 CHEAT_ENGINE_TICKS = Core::Timing::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 std::array<ComparisonFunction, 6> comparison_functions{
86 [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
87 [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
88 [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
89 };
90
91 if (cheat.type == CodeType::ConditionalInput) {
92 const auto applet_resource = Core::System::GetInstance()
93 .ServiceManager()
94 .GetService<Service::HID::Hid>("hid")
95 ->GetAppletResource();
96 if (applet_resource == nullptr) {
97 LOG_WARNING(
98 Common_Filesystem,
99 "Attempted to evaluate input conditional, but applet resource is not initialized!");
100 return false;
101 }
102
103 const auto press_state =
104 applet_resource
105 ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
106 .GetAndResetPressState();
107 return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
108 }
109
110 ASSERT(cheat.type == CodeType::Conditional);
111
112 const auto offset =
113 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
114 ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
115 auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
116 const auto addr = cheat.Address() + offset;
117
118 return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
119}
120
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 const auto& register_3 = scratch.at(cheat.register_3);
161
162 const auto addr = cheat.Address() + offset + register_3;
163 LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
164 cheat.Value(8, cheat.width));
165 writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
166}
167
168void CheatList::BeginConditional(const Cheat& cheat) {
169 if (EvaluateConditional(cheat)) {
170 return;
171 }
172
173 const auto iter = block_pairs.find(current_index);
174 ASSERT(iter != block_pairs.end());
175 current_index = iter->second - 1;
176}
177
178void CheatList::EndConditional(const Cheat& cheat) {
179 LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
180}
181
182void CheatList::Loop(const Cheat& cheat) {
183 if (cheat.end_of_loop.Value())
184 ASSERT(!cheat.end_of_loop.Value());
185
186 auto& register_3 = scratch.at(cheat.register_3);
187 const auto iter = block_pairs.find(current_index);
188 ASSERT(iter != block_pairs.end());
189 ASSERT(iter->first < iter->second);
190
191 for (int i = cheat.Value(4, 4); i >= 0; --i) {
192 register_3 = i;
193 for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
194 current_index = c;
195 ExecuteSingleCheat(
196 (in_standard ? standard_list : master_list)[current_block].second[c]);
197 }
198 }
199
200 current_index = iter->second;
201}
202
203void CheatList::LoadImmediate(const Cheat& cheat) {
204 auto& register_3 = scratch.at(cheat.register_3);
205
206 LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
207 cheat.Value(4, 8));
208 register_3 = cheat.Value(4, 8);
209}
210
211void CheatList::LoadIndexed(const Cheat& cheat) {
212 const auto offset =
213 cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
214 auto& register_3 = scratch.at(cheat.register_3);
215
216 const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
217 LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
218 cheat.register_3, addr);
219 register_3 = reader(cheat.width, SanitizeAddress(addr));
220}
221
222void CheatList::StoreIndexed(const Cheat& cheat) {
223 const auto& register_3 = scratch.at(cheat.register_3);
224
225 const auto addr =
226 register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
227 LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
228 cheat.Value(4, cheat.width), addr);
229 writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
230}
231
232void CheatList::RegisterArithmetic(const Cheat& cheat) {
233 using ArithmeticFunction = u64 (*)(u64, u64);
234 constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
235 [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
236 [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
237 [](u64 a, u64 b) { return a >> b; },
238 };
239
240 using ArithmeticOverflowCheck = bool (*)(u64, u64);
241 constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
242 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
243 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
244 [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
245 [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
246 [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
247 };
248
249 static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
250 "Missing or have extra arithmetic overflow checks compared to functions!");
251
252 auto& register_3 = scratch.at(cheat.register_3);
253
254 ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
255 auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
256 auto* overflow_function =
257 arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
258 LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
259 cheat.register_3, cheat.ValueWidth(4));
260
261 if (overflow_function(register_3, cheat.ValueWidth(4))) {
262 LOG_WARNING(Common_Filesystem,
263 "overflow will occur when performing arithmetic operation={:02X} with operands "
264 "a={:016X}, b={:016X}!",
265 static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
266 }
267
268 register_3 = function(register_3, cheat.ValueWidth(4));
269}
270
271void CheatList::BeginConditionalInput(const Cheat& cheat) {
272 if (EvaluateConditional(cheat))
273 return;
274
275 const auto iter = block_pairs.find(current_index);
276 ASSERT(iter != block_pairs.end());
277 current_index = iter->second - 1;
278}
279
280VAddr CheatList::SanitizeAddress(VAddr in) const {
281 if ((in < main_region_begin || in >= main_region_end) &&
282 (in < heap_region_begin || in >= heap_region_end)) {
283 LOG_ERROR(Common_Filesystem,
284 "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
285 "the cheat may be incorrect. However, this may be normal early in execution if "
286 "the game has not properly set up yet.",
287 in);
288 return 0; ///< Invalid addresses will hard crash
289 }
290
291 return in;
292}
293
294void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
295 using CheatOperationFunction = void (CheatList::*)(const Cheat&);
296 constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
297 &CheatList::WriteImmediate, &CheatList::BeginConditional,
298 &CheatList::EndConditional, &CheatList::Loop,
299 &CheatList::LoadImmediate, &CheatList::LoadIndexed,
300 &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
301 &CheatList::BeginConditionalInput,
302 };
303
304 const auto index = static_cast<u8>(cheat.type.Value());
305 ASSERT(index < sizeof(cheat_operation_functions));
306 const auto op = cheat_operation_functions[index];
307 (this->*op)(cheat);
308}
309
310void CheatList::ExecuteBlock(const Block& block) {
311 encountered_loops.clear();
312
313 ProcessBlockPairs(block);
314 for (std::size_t i = 0; i < block.size(); ++i) {
315 current_index = i;
316 ExecuteSingleCheat(block[i]);
317 i = current_index;
318 }
319}
320
321CheatParser::~CheatParser() = default;
322
323CheatList CheatParser::MakeCheatList(CheatList::ProgramSegment master,
324 CheatList::ProgramSegment standard) const {
325 return {master, standard};
326}
327
328TextCheatParser::~TextCheatParser() = default;
329
330CheatList TextCheatParser::Parse(const std::vector<u8>& data) const {
331 std::stringstream ss;
332 ss.write(reinterpret_cast<const char*>(data.data()), data.size());
333
334 std::vector<std::string> lines;
335 std::string stream_line;
336 while (std::getline(ss, stream_line)) {
337 // Remove a trailing \r
338 if (!stream_line.empty() && stream_line.back() == '\r')
339 stream_line.pop_back();
340 lines.push_back(std::move(stream_line));
341 }
342
343 CheatList::ProgramSegment master_list;
344 CheatList::ProgramSegment standard_list;
345
346 for (std::size_t i = 0; i < lines.size(); ++i) {
347 auto line = lines[i];
348
349 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
350 const auto master = line[0] == '{';
351 const auto begin = master ? line.find('{') : line.find('[');
352 const auto end = master ? line.rfind('}') : line.rfind(']');
353
354 ASSERT(begin != std::string::npos && end != std::string::npos);
355
356 const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
357 CheatList::Block block{};
358
359 while (i < lines.size() - 1) {
360 line = lines[++i];
361 if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
362 --i;
363 break;
364 }
365
366 if (line.size() < 8)
367 continue;
368
369 Cheat out{};
370 out.raw = ParseSingleLineCheat(line);
371 block.push_back(out);
372 }
373
374 (master ? master_list : standard_list).emplace_back(patch_name, block);
375 }
376 }
377
378 return MakeCheatList(master_list, standard_list);
379}
380
381std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
382 std::array<u8, 16> out{};
383
384 if (line.size() < 8)
385 return out;
386
387 const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
388 std::memcpy(out.data(), word1.data(), sizeof(u32));
389
390 if (line.size() < 17 || line[8] != ' ')
391 return out;
392
393 const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
394 std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
395
396 if (line.size() < 26 || line[17] != ' ') {
397 // Perform shifting in case value is truncated early.
398 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
399 if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
400 type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
401 std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
402 std::memset(out.data() + 4, 0, sizeof(u32));
403 }
404
405 return out;
406 }
407
408 const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
409 std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
410
411 if (line.size() < 35 || line[26] != ' ') {
412 // Perform shifting in case value is truncated early.
413 const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
414 if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
415 std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
416 std::memset(out.data() + 8, 0, sizeof(u32));
417 }
418
419 return out;
420 }
421
422 const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
423 std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
424
425 return out;
426}
427
428u64 MemoryReadImpl(u32 width, VAddr addr) {
429 switch (width) {
430 case 1:
431 return Memory::Read8(addr);
432 case 2:
433 return Memory::Read16(addr);
434 case 4:
435 return Memory::Read32(addr);
436 case 8:
437 return Memory::Read64(addr);
438 default:
439 UNREACHABLE();
440 return 0;
441 }
442}
443
444void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
445 switch (width) {
446 case 1:
447 Memory::Write8(addr, static_cast<u8>(value));
448 break;
449 case 2:
450 Memory::Write16(addr, static_cast<u16>(value));
451 break;
452 case 4:
453 Memory::Write32(addr, static_cast<u32>(value));
454 break;
455 case 8:
456 Memory::Write64(addr, value);
457 break;
458 default:
459 UNREACHABLE();
460 }
461}
462
463CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id,
464 VAddr code_region_start, VAddr code_region_end)
465 : cheats(std::move(cheats)) {
466 auto& core_timing{Core::System::GetInstance().CoreTiming()};
467 event = core_timing.RegisterEvent(
468 "CheatEngine::FrameCallback::" + build_id,
469 [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
470 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
471
472 const auto& vm_manager = Core::System::GetInstance().CurrentProcess()->VMManager();
473 for (auto& list : this->cheats) {
474 list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
475 code_region_end, vm_manager.GetHeapRegionEndAddress(),
476 &MemoryWriteImpl, &MemoryReadImpl);
477 }
478}
479
480CheatEngine::~CheatEngine() {
481 auto& core_timing{Core::System::GetInstance().CoreTiming()};
482 core_timing.UnscheduleEvent(event, 0);
483}
484
485void CheatEngine::FrameCallback(u64 userdata, int cycles_late) {
486 for (auto& list : cheats)
487 list.Execute();
488
489 auto& core_timing{Core::System::GetInstance().CoreTiming()};
490 core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
491}
492
493} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h
new file mode 100644
index 000000000..7ed69a2c8
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.h
@@ -0,0 +1,227 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <map>
8#include <set>
9#include <vector>
10#include <queue>
11#include "common/bit_field.h"
12#include "common/common_types.h"
13
14namespace Core::Timing {
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 (*)(u32, VAddr, u64);
127 // (width in bytes, address) -> value
128 using MemoryReader = u64 (*)(u32, VAddr);
129
130 void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
131 MemoryWriter writer, MemoryReader reader);
132
133 void Execute();
134
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, VAddr code_region_start,
216 VAddr code_region_end);
217 ~CheatEngine();
218
219private:
220 void FrameCallback(u64 userdata, int cycles_late);
221
222 Core::Timing::EventType* event;
223
224 std::vector<CheatList> cheats;
225};
226
227} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 61706966e..2b09e5d35 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -7,6 +7,7 @@
7#include <cstddef> 7#include <cstddef>
8#include <cstring> 8#include <cstring>
9 9
10#include "common/file_util.h"
10#include "common/hex_util.h" 11#include "common/hex_util.h"
11#include "common/logging/log.h" 12#include "common/logging/log.h"
12#include "core/file_sys/content_archive.h" 13#include "core/file_sys/content_archive.h"
@@ -232,6 +233,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
232 return !CollectPatches(patch_dirs, build_id).empty(); 233 return !CollectPatches(patch_dirs, build_id).empty();
233} 234}
234 235
236static std::optional<CheatList> ReadCheatFileFromFolder(u64 title_id,
237 const std::array<u8, 0x20>& build_id_,
238 const VirtualDir& base_path, bool upper) {
239 const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
240 const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
241 const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
242
243 if (file == nullptr) {
244 LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
245 title_id, build_id);
246 return std::nullopt;
247 }
248
249 std::vector<u8> data(file->GetSize());
250 if (file->Read(data.data(), data.size()) != data.size()) {
251 LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
252 title_id, build_id);
253 return std::nullopt;
254 }
255
256 TextCheatParser parser;
257 return parser.Parse(data);
258}
259
260std::vector<CheatList> PatchManager::CreateCheatList(const std::array<u8, 32>& build_id_) const {
261 std::vector<CheatList> out;
262
263 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
264 auto patch_dirs = load_dir->GetSubdirectories();
265 std::sort(patch_dirs.begin(), patch_dirs.end(),
266 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
267
268 out.reserve(patch_dirs.size());
269 for (const auto& subdir : patch_dirs) {
270 auto cheats_dir = subdir->GetSubdirectory("cheats");
271 if (cheats_dir != nullptr) {
272 auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true);
273 if (res.has_value()) {
274 out.push_back(std::move(*res));
275 continue;
276 }
277
278 res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false);
279 if (res.has_value())
280 out.push_back(std::move(*res));
281 }
282 }
283
284 return out;
285}
286
235static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { 287static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
236 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); 288 const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
237 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || 289 if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
@@ -403,6 +455,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
403 } 455 }
404 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) 456 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
405 AppendCommaIfNotEmpty(types, "LayeredFS"); 457 AppendCommaIfNotEmpty(types, "LayeredFS");
458 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
459 AppendCommaIfNotEmpty(types, "Cheats");
406 460
407 if (types.empty()) 461 if (types.empty())
408 continue; 462 continue;
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index b8a1652fd..3e3ac6aca 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -8,6 +8,7 @@
8#include <memory> 8#include <memory>
9#include <string> 9#include <string>
10#include "common/common_types.h" 10#include "common/common_types.h"
11#include "core/file_sys/cheat_engine.h"
11#include "core/file_sys/nca_metadata.h" 12#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs.h"
13 14
@@ -45,6 +46,9 @@ public:
45 // Used to prevent expensive copies in NSO loader. 46 // Used to prevent expensive copies in NSO loader.
46 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const; 47 bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
47 48
49 // Creates a CheatList object with all
50 std::vector<CheatList> CreateCheatList(const std::array<u8, 0x20>& build_id) const;
51
48 // Currently tracked RomFS patches: 52 // Currently tracked RomFS patches:
49 // - Game Updates 53 // - Game Updates
50 // - LayeredFS 54 // - LayeredFS
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index b96980f8f..13ff4ebb3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -617,6 +617,9 @@ private:
617 VAddr new_map_region_base = 0; 617 VAddr new_map_region_base = 0;
618 VAddr new_map_region_end = 0; 618 VAddr new_map_region_end = 0;
619 619
620 VAddr main_code_region_base = 0;
621 VAddr main_code_region_end = 0;
622
620 VAddr tls_io_region_base = 0; 623 VAddr tls_io_region_base = 0;
621 VAddr tls_io_region_end = 0; 624 VAddr tls_io_region_end = 0;
622 625
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 7cc58db4c..498602de5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,6 +4,9 @@
4 4
5#pragma once 5#pragma once
6 6
7#include "core/hle/service/hid/controllers/controller_base.h"
8#include "core/hle/service/service.h"
9
7#include "controllers/controller_base.h" 10#include "controllers/controller_base.h"
8#include "core/hle/service/service.h" 11#include "core/hle/service/service.h"
9 12
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index e1c8908a1..0eb9fd7f7 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -7,8 +7,10 @@
7#include <lz4.h> 7#include <lz4.h>
8#include "common/common_funcs.h" 8#include "common/common_funcs.h"
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/hex_util.h"
10#include "common/logging/log.h" 11#include "common/logging/log.h"
11#include "common/swap.h" 12#include "common/swap.h"
13#include "core/core.h"
12#include "core/file_sys/patch_manager.h" 14#include "core/file_sys/patch_manager.h"
13#include "core/gdbstub/gdbstub.h" 15#include "core/gdbstub/gdbstub.h"
14#include "core/hle/kernel/code_set.h" 16#include "core/hle/kernel/code_set.h"
@@ -165,6 +167,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
165 std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size()); 167 std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
166 } 168 }
167 169
170 // Apply cheats if they exist and the program has a valid title ID
171 if (pm) {
172 const auto cheats = pm->CreateCheatList(nso_header.build_id);
173 if (!cheats.empty()) {
174 Core::System::GetInstance().RegisterCheatList(
175 cheats, Common::HexArrayToString(nso_header.build_id), load_base,
176 load_base + program_image.size());
177 }
178 }
179
168 // Load codeset for current process 180 // Load codeset for current process
169 codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image)); 181 codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
170 process.LoadModule(std::move(codeset), load_base); 182 process.LoadModule(std::move(codeset), load_base);