summaryrefslogtreecommitdiff
path: root/src/core/memory
diff options
context:
space:
mode:
authorGravatar Zach Hilman2019-05-30 19:34:02 -0400
committerGravatar Zach Hilman2019-09-21 21:44:22 -0400
commit12aa127df3826857149bfc4b787cfb7df3fdcafe (patch)
treeef83925202b36ab21fbf0b5c67e465a04e048b4e /src/core/memory
parentlog: Add logging class for Cheat Engine (diff)
downloadyuzu-12aa127df3826857149bfc4b787cfb7df3fdcafe.tar.gz
yuzu-12aa127df3826857149bfc4b787cfb7df3fdcafe.tar.xz
yuzu-12aa127df3826857149bfc4b787cfb7df3fdcafe.zip
memory: Port Atmosphere's DmntCheatVm
This was done because the current VM contained many inaccuracies and this also allows cheats to have identical behavior between hardware and yuzu.
Diffstat (limited to 'src/core/memory')
-rw-r--r--src/core/memory/dmnt_cheat_types.h58
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp1206
-rw-r--r--src/core/memory/dmnt_cheat_vm.h334
3 files changed, 1598 insertions, 0 deletions
diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h
new file mode 100644
index 000000000..aa1264c32
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_types.h
@@ -0,0 +1,58 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include "common/common_types.h"
28
29namespace Memory {
30
31struct MemoryRegionExtents {
32 u64 base;
33 u64 size;
34};
35
36struct CheatProcessMetadata {
37 u64 process_id;
38 u64 title_id;
39 MemoryRegionExtents main_nso_extents;
40 MemoryRegionExtents heap_extents;
41 MemoryRegionExtents alias_extents;
42 MemoryRegionExtents address_space_extents;
43 std::array<u8, 0x20> main_nso_build_id;
44};
45
46struct CheatDefinition {
47 std::array<char, 0x40> readable_name;
48 u32 num_opcodes;
49 std::array<u32, 0x100> opcodes;
50};
51
52struct CheatEntry {
53 bool enabled;
54 u32 cheat_id;
55 CheatDefinition definition;
56};
57
58} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp
new file mode 100644
index 000000000..a3f450dac
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.cpp
@@ -0,0 +1,1206 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#include "common/assert.h"
26#include "common/scope_exit.h"
27#include "core/memory/dmnt_cheat_types.h"
28#include "core/memory/dmnt_cheat_vm.h"
29
30namespace Memory {
31
32void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
33 callbacks->DebugLog(static_cast<u8>(log_id), value);
34}
35
36void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
37 switch (opcode.opcode) {
38 case CheatVmOpcodeType_StoreStatic:
39 this->LogToDebugFile("Opcode: Store Static\n");
40 this->LogToDebugFile("Bit Width: %x\n", opcode.store_static.bit_width);
41 this->LogToDebugFile("Mem Type: %x\n", opcode.store_static.mem_type);
42 this->LogToDebugFile("Reg Idx: %x\n", opcode.store_static.offset_register);
43 this->LogToDebugFile("Rel Addr: %lx\n", opcode.store_static.rel_address);
44 this->LogToDebugFile("Value: %lx\n", opcode.store_static.value.bit64);
45 break;
46 case CheatVmOpcodeType_BeginConditionalBlock:
47 this->LogToDebugFile("Opcode: Begin Conditional\n");
48 this->LogToDebugFile("Bit Width: %x\n", opcode.begin_cond.bit_width);
49 this->LogToDebugFile("Mem Type: %x\n", opcode.begin_cond.mem_type);
50 this->LogToDebugFile("Cond Type: %x\n", opcode.begin_cond.cond_type);
51 this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_cond.rel_address);
52 this->LogToDebugFile("Value: %lx\n", opcode.begin_cond.value.bit64);
53 break;
54 case CheatVmOpcodeType_EndConditionalBlock:
55 this->LogToDebugFile("Opcode: End Conditional\n");
56 break;
57 case CheatVmOpcodeType_ControlLoop:
58 if (opcode.ctrl_loop.start_loop) {
59 this->LogToDebugFile("Opcode: Start Loop\n");
60 this->LogToDebugFile("Reg Idx: %x\n", opcode.ctrl_loop.reg_index);
61 this->LogToDebugFile("Num Iters: %x\n", opcode.ctrl_loop.num_iters);
62 } else {
63 this->LogToDebugFile("Opcode: End Loop\n");
64 this->LogToDebugFile("Reg Idx: %x\n", opcode.ctrl_loop.reg_index);
65 }
66 break;
67 case CheatVmOpcodeType_LoadRegisterStatic:
68 this->LogToDebugFile("Opcode: Load Register Static\n");
69 this->LogToDebugFile("Reg Idx: %x\n", opcode.ldr_static.reg_index);
70 this->LogToDebugFile("Value: %lx\n", opcode.ldr_static.value);
71 break;
72 case CheatVmOpcodeType_LoadRegisterMemory:
73 this->LogToDebugFile("Opcode: Load Register Memory\n");
74 this->LogToDebugFile("Bit Width: %x\n", opcode.ldr_memory.bit_width);
75 this->LogToDebugFile("Reg Idx: %x\n", opcode.ldr_memory.reg_index);
76 this->LogToDebugFile("Mem Type: %x\n", opcode.ldr_memory.mem_type);
77 this->LogToDebugFile("From Reg: %d\n", opcode.ldr_memory.load_from_reg);
78 this->LogToDebugFile("Rel Addr: %lx\n", opcode.ldr_memory.rel_address);
79 break;
80 case CheatVmOpcodeType_StoreStaticToAddress:
81 this->LogToDebugFile("Opcode: Store Static to Address\n");
82 this->LogToDebugFile("Bit Width: %x\n", opcode.str_static.bit_width);
83 this->LogToDebugFile("Reg Idx: %x\n", opcode.str_static.reg_index);
84 if (opcode.str_static.add_offset_reg) {
85 this->LogToDebugFile("O Reg Idx: %x\n", opcode.str_static.offset_reg_index);
86 }
87 this->LogToDebugFile("Incr Reg: %d\n", opcode.str_static.increment_reg);
88 this->LogToDebugFile("Value: %lx\n", opcode.str_static.value);
89 break;
90 case CheatVmOpcodeType_PerformArithmeticStatic:
91 this->LogToDebugFile("Opcode: Perform Static Arithmetic\n");
92 this->LogToDebugFile("Bit Width: %x\n", opcode.perform_math_static.bit_width);
93 this->LogToDebugFile("Reg Idx: %x\n", opcode.perform_math_static.reg_index);
94 this->LogToDebugFile("Math Type: %x\n", opcode.perform_math_static.math_type);
95 this->LogToDebugFile("Value: %lx\n", opcode.perform_math_static.value);
96 break;
97 case CheatVmOpcodeType_BeginKeypressConditionalBlock:
98 this->LogToDebugFile("Opcode: Begin Keypress Conditional\n");
99 this->LogToDebugFile("Key Mask: %x\n", opcode.begin_keypress_cond.key_mask);
100 break;
101 case CheatVmOpcodeType_PerformArithmeticRegister:
102 this->LogToDebugFile("Opcode: Perform Register Arithmetic\n");
103 this->LogToDebugFile("Bit Width: %x\n", opcode.perform_math_reg.bit_width);
104 this->LogToDebugFile("Dst Idx: %x\n", opcode.perform_math_reg.dst_reg_index);
105 this->LogToDebugFile("Src1 Idx: %x\n", opcode.perform_math_reg.src_reg_1_index);
106 if (opcode.perform_math_reg.has_immediate) {
107 this->LogToDebugFile("Value: %lx\n", opcode.perform_math_reg.value.bit64);
108 } else {
109 this->LogToDebugFile("Src2 Idx: %x\n", opcode.perform_math_reg.src_reg_2_index);
110 }
111 break;
112 case CheatVmOpcodeType_StoreRegisterToAddress:
113 this->LogToDebugFile("Opcode: Store Register to Address\n");
114 this->LogToDebugFile("Bit Width: %x\n", opcode.str_register.bit_width);
115 this->LogToDebugFile("S Reg Idx: %x\n", opcode.str_register.str_reg_index);
116 this->LogToDebugFile("A Reg Idx: %x\n", opcode.str_register.addr_reg_index);
117 this->LogToDebugFile("Incr Reg: %d\n", opcode.str_register.increment_reg);
118 switch (opcode.str_register.ofs_type) {
119 case StoreRegisterOffsetType_None:
120 break;
121 case StoreRegisterOffsetType_Reg:
122 this->LogToDebugFile("O Reg Idx: %x\n", opcode.str_register.ofs_reg_index);
123 break;
124 case StoreRegisterOffsetType_Imm:
125 this->LogToDebugFile("Rel Addr: %lx\n", opcode.str_register.rel_address);
126 break;
127 case StoreRegisterOffsetType_MemReg:
128 this->LogToDebugFile("Mem Type: %x\n", opcode.str_register.mem_type);
129 break;
130 case StoreRegisterOffsetType_MemImm:
131 case StoreRegisterOffsetType_MemImmReg:
132 this->LogToDebugFile("Mem Type: %x\n", opcode.str_register.mem_type);
133 this->LogToDebugFile("Rel Addr: %lx\n", opcode.str_register.rel_address);
134 break;
135 }
136 break;
137 case CheatVmOpcodeType_BeginRegisterConditionalBlock:
138 this->LogToDebugFile("Opcode: Begin Register Conditional\n");
139 this->LogToDebugFile("Bit Width: %x\n", opcode.begin_reg_cond.bit_width);
140 this->LogToDebugFile("Cond Type: %x\n", opcode.begin_reg_cond.cond_type);
141 this->LogToDebugFile("V Reg Idx: %x\n", opcode.begin_reg_cond.val_reg_index);
142 switch (opcode.begin_reg_cond.comp_type) {
143 case CompareRegisterValueType_StaticValue:
144 this->LogToDebugFile("Comp Type: Static Value\n");
145 this->LogToDebugFile("Value: %lx\n", opcode.begin_reg_cond.value.bit64);
146 break;
147 case CompareRegisterValueType_OtherRegister:
148 this->LogToDebugFile("Comp Type: Other Register\n");
149 this->LogToDebugFile("X Reg Idx: %x\n", opcode.begin_reg_cond.other_reg_index);
150 break;
151 case CompareRegisterValueType_MemoryRelAddr:
152 this->LogToDebugFile("Comp Type: Memory Relative Address\n");
153 this->LogToDebugFile("Mem Type: %x\n", opcode.begin_reg_cond.mem_type);
154 this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_reg_cond.rel_address);
155 break;
156 case CompareRegisterValueType_MemoryOfsReg:
157 this->LogToDebugFile("Comp Type: Memory Offset Register\n");
158 this->LogToDebugFile("Mem Type: %x\n", opcode.begin_reg_cond.mem_type);
159 this->LogToDebugFile("O Reg Idx: %x\n", opcode.begin_reg_cond.ofs_reg_index);
160 break;
161 case CompareRegisterValueType_RegisterRelAddr:
162 this->LogToDebugFile("Comp Type: Register Relative Address\n");
163 this->LogToDebugFile("A Reg Idx: %x\n", opcode.begin_reg_cond.addr_reg_index);
164 this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_reg_cond.rel_address);
165 break;
166 case CompareRegisterValueType_RegisterOfsReg:
167 this->LogToDebugFile("Comp Type: Register Offset Register\n");
168 this->LogToDebugFile("A Reg Idx: %x\n", opcode.begin_reg_cond.addr_reg_index);
169 this->LogToDebugFile("O Reg Idx: %x\n", opcode.begin_reg_cond.ofs_reg_index);
170 break;
171 }
172 break;
173 case CheatVmOpcodeType_SaveRestoreRegister:
174 this->LogToDebugFile("Opcode: Save or Restore Register\n");
175 this->LogToDebugFile("Dst Idx: %x\n", opcode.save_restore_reg.dst_index);
176 this->LogToDebugFile("Src Idx: %x\n", opcode.save_restore_reg.src_index);
177 this->LogToDebugFile("Op Type: %d\n", opcode.save_restore_reg.op_type);
178 break;
179 case CheatVmOpcodeType_SaveRestoreRegisterMask:
180 this->LogToDebugFile("Opcode: Save or Restore Register Mask\n");
181 this->LogToDebugFile("Op Type: %d\n", opcode.save_restore_regmask.op_type);
182 for (size_t i = 0; i < NumRegisters; i++) {
183 this->LogToDebugFile("Act[%02x]: %d\n", i,
184 opcode.save_restore_regmask.should_operate[i]);
185 }
186 break;
187 case CheatVmOpcodeType_DebugLog:
188 this->LogToDebugFile("Opcode: Debug Log\n");
189 this->LogToDebugFile("Bit Width: %x\n", opcode.debug_log.bit_width);
190 this->LogToDebugFile("Log ID: %x\n", opcode.debug_log.log_id);
191 this->LogToDebugFile("Val Type: %x\n", opcode.debug_log.val_type);
192 switch (opcode.debug_log.val_type) {
193 case DebugLogValueType_RegisterValue:
194 this->LogToDebugFile("Val Type: Register Value\n");
195 this->LogToDebugFile("X Reg Idx: %x\n", opcode.debug_log.val_reg_index);
196 break;
197 case DebugLogValueType_MemoryRelAddr:
198 this->LogToDebugFile("Val Type: Memory Relative Address\n");
199 this->LogToDebugFile("Mem Type: %x\n", opcode.debug_log.mem_type);
200 this->LogToDebugFile("Rel Addr: %lx\n", opcode.debug_log.rel_address);
201 break;
202 case DebugLogValueType_MemoryOfsReg:
203 this->LogToDebugFile("Val Type: Memory Offset Register\n");
204 this->LogToDebugFile("Mem Type: %x\n", opcode.debug_log.mem_type);
205 this->LogToDebugFile("O Reg Idx: %x\n", opcode.debug_log.ofs_reg_index);
206 break;
207 case DebugLogValueType_RegisterRelAddr:
208 this->LogToDebugFile("Val Type: Register Relative Address\n");
209 this->LogToDebugFile("A Reg Idx: %x\n", opcode.debug_log.addr_reg_index);
210 this->LogToDebugFile("Rel Addr: %lx\n", opcode.debug_log.rel_address);
211 break;
212 case DebugLogValueType_RegisterOfsReg:
213 this->LogToDebugFile("Val Type: Register Offset Register\n");
214 this->LogToDebugFile("A Reg Idx: %x\n", opcode.debug_log.addr_reg_index);
215 this->LogToDebugFile("O Reg Idx: %x\n", opcode.debug_log.ofs_reg_index);
216 break;
217 }
218 default:
219 this->LogToDebugFile("Unknown opcode: %x\n", opcode.opcode);
220 break;
221 }
222}
223
224DmntCheatVm::Callbacks::~Callbacks() = default;
225
226bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
227 /* If we've ever seen a decode failure, return false. */
228 bool valid = this->decode_success;
229 CheatVmOpcode opcode = {};
230 SCOPE_EXIT({
231 this->decode_success &= valid;
232 if (valid) {
233 out = opcode;
234 }
235 });
236
237 /* Helper function for getting instruction dwords. */
238 auto GetNextDword = [&]() {
239 if (this->instruction_ptr >= this->num_opcodes) {
240 valid = false;
241 return static_cast<u32>(0);
242 }
243 return this->program[this->instruction_ptr++];
244 };
245
246 /* Helper function for parsing a VmInt. */
247 auto GetNextVmInt = [&](const u32 bit_width) {
248 VmInt val = {0};
249
250 const u32 first_dword = GetNextDword();
251 switch (bit_width) {
252 case 1:
253 val.bit8 = (u8)first_dword;
254 break;
255 case 2:
256 val.bit16 = (u16)first_dword;
257 break;
258 case 4:
259 val.bit32 = first_dword;
260 break;
261 case 8:
262 val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword());
263 break;
264 }
265
266 return val;
267 };
268
269 /* Read opcode. */
270 const u32 first_dword = GetNextDword();
271 if (!valid) {
272 return valid;
273 }
274
275 opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF));
276 if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) {
277 opcode.opcode =
278 (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF));
279 }
280 if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) {
281 opcode.opcode =
282 (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF));
283 }
284
285 /* detect condition start. */
286 switch (opcode.opcode) {
287 case CheatVmOpcodeType_BeginConditionalBlock:
288 case CheatVmOpcodeType_BeginKeypressConditionalBlock:
289 case CheatVmOpcodeType_BeginRegisterConditionalBlock:
290 opcode.begin_conditional_block = true;
291 break;
292 default:
293 opcode.begin_conditional_block = false;
294 break;
295 }
296
297 switch (opcode.opcode) {
298 case CheatVmOpcodeType_StoreStatic: {
299 /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */
300 /* Read additional words. */
301 const u32 second_dword = GetNextDword();
302 opcode.store_static.bit_width = (first_dword >> 24) & 0xF;
303 opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
304 opcode.store_static.offset_register = ((first_dword >> 16) & 0xF);
305 opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
306 opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width);
307 } break;
308 case CheatVmOpcodeType_BeginConditionalBlock: {
309 /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */
310 /* Read additional words. */
311 const u32 second_dword = GetNextDword();
312 opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF;
313 opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
314 opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF);
315 opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
316 opcode.begin_cond.value = GetNextVmInt(opcode.store_static.bit_width);
317 } break;
318 case CheatVmOpcodeType_EndConditionalBlock: {
319 /* 20000000 */
320 /* There's actually nothing left to process here! */
321 } break;
322 case CheatVmOpcodeType_ControlLoop: {
323 /* 300R0000 VVVVVVVV */
324 /* 310R0000 */
325 /* Parse register, whether loop start or loop end. */
326 opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
327 opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
328
329 /* Read number of iters if loop start. */
330 if (opcode.ctrl_loop.start_loop) {
331 opcode.ctrl_loop.num_iters = GetNextDword();
332 }
333 } break;
334 case CheatVmOpcodeType_LoadRegisterStatic: {
335 /* 400R0000 VVVVVVVV VVVVVVVV */
336 /* Read additional words. */
337 opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF);
338 opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword());
339 } break;
340 case CheatVmOpcodeType_LoadRegisterMemory: {
341 /* 5TMRI0AA AAAAAAAA */
342 /* Read additional words. */
343 const u32 second_dword = GetNextDword();
344 opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF;
345 opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF);
346 opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
347 opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
348 opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword);
349 } break;
350 case CheatVmOpcodeType_StoreStaticToAddress: {
351 /* 6T0RIor0 VVVVVVVV VVVVVVVV */
352 /* Read additional words. */
353 opcode.str_static.bit_width = (first_dword >> 24) & 0xF;
354 opcode.str_static.reg_index = ((first_dword >> 16) & 0xF);
355 opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
356 opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
357 opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
358 opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword());
359 } break;
360 case CheatVmOpcodeType_PerformArithmeticStatic: {
361 /* 7T0RC000 VVVVVVVV */
362 /* Read additional words. */
363 opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF;
364 opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
365 opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF);
366 opcode.perform_math_static.value = GetNextDword();
367 } break;
368 case CheatVmOpcodeType_BeginKeypressConditionalBlock: {
369 /* 8kkkkkkk */
370 /* Just parse the mask. */
371 opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
372 } break;
373 case CheatVmOpcodeType_PerformArithmeticRegister: {
374 /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */
375 opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
376 opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF);
377 opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
378 opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
379 opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
380 if (opcode.perform_math_reg.has_immediate) {
381 opcode.perform_math_reg.src_reg_2_index = 0;
382 opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width);
383 } else {
384 opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
385 }
386 } break;
387 case CheatVmOpcodeType_StoreRegisterToAddress: {
388 /* ATSRIOxa (aaaaaaaa) */
389 /* A = opcode 10 */
390 /* T = bit width */
391 /* S = src register index */
392 /* R = address register index */
393 /* I = 1 if increment address register, 0 if not increment address register */
394 /* O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region,
395 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region +
396 Relative Address */
397 /* x = offset register (for offset type 1), memory type (for offset type 3) */
398 /* a = relative address (for offset type 2+3) */
399 opcode.str_register.bit_width = (first_dword >> 24) & 0xF;
400 opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF);
401 opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
402 opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
403 opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF));
404 opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
405 switch (opcode.str_register.ofs_type) {
406 case StoreRegisterOffsetType_None:
407 case StoreRegisterOffsetType_Reg:
408 /* Nothing more to do */
409 break;
410 case StoreRegisterOffsetType_Imm:
411 opcode.str_register.rel_address =
412 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
413 break;
414 case StoreRegisterOffsetType_MemReg:
415 opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
416 break;
417 case StoreRegisterOffsetType_MemImm:
418 case StoreRegisterOffsetType_MemImmReg:
419 opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
420 opcode.str_register.rel_address =
421 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
422 break;
423 default:
424 opcode.str_register.ofs_type = StoreRegisterOffsetType_None;
425 break;
426 }
427 } break;
428 case CheatVmOpcodeType_BeginRegisterConditionalBlock: {
429 /* C0TcSX## */
430 /* C0TcS0Ma aaaaaaaa */
431 /* C0TcS1Mr */
432 /* C0TcS2Ra aaaaaaaa */
433 /* C0TcS3Rr */
434 /* C0TcS400 VVVVVVVV (VVVVVVVV) */
435 /* C0TcS5X0 */
436 /* C0 = opcode 0xC0 */
437 /* T = bit width */
438 /* c = condition type. */
439 /* S = source register. */
440 /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
441 * register, */
442 /* 2 = register with relative offset, 3 = register with offset register, 4 = static
443 * value, 5 = other register. */
444 /* M = memory type. */
445 /* R = address register. */
446 /* a = relative address. */
447 /* r = offset register. */
448 /* X = other register. */
449 /* V = value. */
450 opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
451 opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF);
452 opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
453 opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF);
454
455 switch (opcode.begin_reg_cond.comp_type) {
456 case CompareRegisterValueType_StaticValue:
457 opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width);
458 break;
459 case CompareRegisterValueType_OtherRegister:
460 opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
461 break;
462 case CompareRegisterValueType_MemoryRelAddr:
463 opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
464 opcode.begin_reg_cond.rel_address =
465 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
466 break;
467 case CompareRegisterValueType_MemoryOfsReg:
468 opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
469 opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
470 break;
471 case CompareRegisterValueType_RegisterRelAddr:
472 opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
473 opcode.begin_reg_cond.rel_address =
474 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
475 break;
476 case CompareRegisterValueType_RegisterOfsReg:
477 opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
478 opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
479 break;
480 }
481 } break;
482 case CheatVmOpcodeType_SaveRestoreRegister: {
483 /* C10D0Sx0 */
484 /* C1 = opcode 0xC1 */
485 /* D = destination index. */
486 /* S = source index. */
487 /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
488 * a register. */
489 /* NOTE: If we add more save slots later, current encoding is backwards compatible. */
490 opcode.save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
491 opcode.save_restore_reg.src_index = (first_dword >> 8) & 0xF;
492 opcode.save_restore_reg.op_type = (SaveRestoreRegisterOpType)((first_dword >> 4) & 0xF);
493 } break;
494 case CheatVmOpcodeType_SaveRestoreRegisterMask: {
495 /* C2x0XXXX */
496 /* C2 = opcode 0xC2 */
497 /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. */
498 /* X = 16-bit bitmask, bit i --> save or restore register i. */
499 opcode.save_restore_regmask.op_type =
500 (SaveRestoreRegisterOpType)((first_dword >> 20) & 0xF);
501 for (size_t i = 0; i < NumRegisters; i++) {
502 opcode.save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
503 }
504 } break;
505 case CheatVmOpcodeType_DebugLog: {
506 /* FFFTIX## */
507 /* FFFTI0Ma aaaaaaaa */
508 /* FFFTI1Mr */
509 /* FFFTI2Ra aaaaaaaa */
510 /* FFFTI3Rr */
511 /* FFFTI4X0 */
512 /* FFF = opcode 0xFFF */
513 /* T = bit width. */
514 /* I = log id. */
515 /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
516 * register, */
517 /* 2 = register with relative offset, 3 = register with offset register, 4 = register
518 * value. */
519 /* M = memory type. */
520 /* R = address register. */
521 /* a = relative address. */
522 /* r = offset register. */
523 /* X = value register. */
524 opcode.debug_log.bit_width = (first_dword >> 16) & 0xF;
525 opcode.debug_log.log_id = ((first_dword >> 12) & 0xF);
526 opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF);
527
528 switch (opcode.debug_log.val_type) {
529 case DebugLogValueType_RegisterValue:
530 opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
531 break;
532 case DebugLogValueType_MemoryRelAddr:
533 opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
534 opcode.debug_log.rel_address =
535 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
536 break;
537 case DebugLogValueType_MemoryOfsReg:
538 opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF);
539 opcode.debug_log.ofs_reg_index = (first_dword & 0xF);
540 break;
541 case DebugLogValueType_RegisterRelAddr:
542 opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
543 opcode.debug_log.rel_address =
544 (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword()));
545 break;
546 case DebugLogValueType_RegisterOfsReg:
547 opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
548 opcode.debug_log.ofs_reg_index = (first_dword & 0xF);
549 break;
550 }
551 } break;
552 case CheatVmOpcodeType_ExtendedWidth:
553 case CheatVmOpcodeType_DoubleExtendedWidth:
554 default:
555 /* Unrecognized instruction cannot be decoded. */
556 valid = false;
557 break;
558 }
559
560 /* End decoding. */
561 return valid;
562}
563
564void DmntCheatVm::SkipConditionalBlock() {
565 if (this->condition_depth > 0) {
566 /* We want to continue until we're out of the current block. */
567 const size_t desired_depth = this->condition_depth - 1;
568
569 CheatVmOpcode skip_opcode{};
570 while (this->condition_depth > desired_depth && this->DecodeNextOpcode(skip_opcode)) {
571 /* Decode instructions until we see end of the current conditional block. */
572 /* NOTE: This is broken in gateway's implementation. */
573 /* Gateway currently checks for "0x2" instead of "0x20000000" */
574 /* In addition, they do a linear scan instead of correctly decoding opcodes. */
575 /* This causes issues if "0x2" appears as an immediate in the conditional block... */
576
577 /* We also support nesting of conditional blocks, and Gateway does not. */
578 if (skip_opcode.begin_conditional_block) {
579 this->condition_depth++;
580 } else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) {
581 this->condition_depth--;
582 }
583 }
584 } else {
585 /* Skipping, but this->condition_depth = 0. */
586 /* This is an error condition. */
587 /* However, I don't actually believe it is possible for this to happen. */
588 /* I guess we'll throw a fatal error here, so as to encourage me to fix the VM */
589 /* in the event that someone triggers it? I don't know how you'd do that. */
590 UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM");
591 }
592}
593
594u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
595 switch (bit_width) {
596 case 1:
597 return value.bit8;
598 case 2:
599 return value.bit16;
600 case 4:
601 return value.bit32;
602 case 8:
603 return value.bit64;
604 default:
605 /* Invalid bit width -> return 0. */
606 return 0;
607 }
608}
609
610u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
611 MemoryAccessType mem_type, u64 rel_address) {
612 switch (mem_type) {
613 case MemoryAccessType_MainNso:
614 default:
615 return metadata.main_nso_extents.base + rel_address;
616 case MemoryAccessType_Heap:
617 return metadata.heap_extents.base + rel_address;
618 }
619}
620
621void DmntCheatVm::ResetState() {
622 for (size_t i = 0; i < DmntCheatVm::NumRegisters; i++) {
623 this->registers[i] = 0;
624 this->saved_values[i] = 0;
625 this->loop_tops[i] = 0;
626 }
627 this->instruction_ptr = 0;
628 this->condition_depth = 0;
629 this->decode_success = true;
630}
631
632bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
633 /* Reset opcode count. */
634 this->num_opcodes = 0;
635
636 for (size_t i = 0; i < entries.size(); i++) {
637 if (entries[i].enabled) {
638 /* Bounds check. */
639 if (entries[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) {
640 this->num_opcodes = 0;
641 return false;
642 }
643
644 for (size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
645 this->program[this->num_opcodes++] = entries[i].definition.opcodes[n];
646 }
647 }
648 }
649
650 return true;
651}
652
653void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
654 CheatVmOpcode cur_opcode{};
655
656 /* Get Keys down. */
657 u64 kDown = callbacks->HidKeysDown();
658
659 this->LogToDebugFile("Started VM execution.\n");
660 this->LogToDebugFile("Main NSO: %012lx\n", metadata.main_nso_extents.base);
661 this->LogToDebugFile("Heap: %012lx\n", metadata.main_nso_extents.base);
662 this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF));
663
664 /* Clear VM state. */
665 this->ResetState();
666
667 /* Loop until program finishes. */
668 while (this->DecodeNextOpcode(cur_opcode)) {
669 this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr);
670
671 for (size_t i = 0; i < NumRegisters; i++) {
672 this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]);
673 }
674
675 for (size_t i = 0; i < NumRegisters; i++) {
676 this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]);
677 }
678 this->LogOpcode(cur_opcode);
679
680 /* Increment conditional depth, if relevant. */
681 if (cur_opcode.begin_conditional_block) {
682 this->condition_depth++;
683 }
684
685 switch (cur_opcode.opcode) {
686 case CheatVmOpcodeType_StoreStatic: {
687 /* Calculate address, write value to memory. */
688 u64 dst_address = GetCheatProcessAddress(
689 metadata, cur_opcode.store_static.mem_type,
690 cur_opcode.store_static.rel_address +
691 this->registers[cur_opcode.store_static.offset_register]);
692 u64 dst_value =
693 GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width);
694 switch (cur_opcode.store_static.bit_width) {
695 case 1:
696 case 2:
697 case 4:
698 case 8:
699 callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.store_static.bit_width);
700 break;
701 }
702 } break;
703 case CheatVmOpcodeType_BeginConditionalBlock: {
704 /* Read value from memory. */
705 u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type,
706 cur_opcode.begin_cond.rel_address);
707 u64 src_value = 0;
708 switch (cur_opcode.store_static.bit_width) {
709 case 1:
710 case 2:
711 case 4:
712 case 8:
713 callbacks->MemoryRead(src_address, &src_value, cur_opcode.begin_cond.bit_width);
714 break;
715 }
716 /* Check against condition. */
717 u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width);
718 bool cond_met = false;
719 switch (cur_opcode.begin_cond.cond_type) {
720 case ConditionalComparisonType_GT:
721 cond_met = src_value > cond_value;
722 break;
723 case ConditionalComparisonType_GE:
724 cond_met = src_value >= cond_value;
725 break;
726 case ConditionalComparisonType_LT:
727 cond_met = src_value < cond_value;
728 break;
729 case ConditionalComparisonType_LE:
730 cond_met = src_value <= cond_value;
731 break;
732 case ConditionalComparisonType_EQ:
733 cond_met = src_value == cond_value;
734 break;
735 case ConditionalComparisonType_NE:
736 cond_met = src_value != cond_value;
737 break;
738 }
739 /* Skip conditional block if condition not met. */
740 if (!cond_met) {
741 this->SkipConditionalBlock();
742 }
743 } break;
744 case CheatVmOpcodeType_EndConditionalBlock:
745 /* Decrement the condition depth. */
746 /* We will assume, graciously, that mismatched conditional block ends are a nop. */
747 if (this->condition_depth > 0) {
748 this->condition_depth--;
749 }
750 break;
751 case CheatVmOpcodeType_ControlLoop:
752 if (cur_opcode.ctrl_loop.start_loop) {
753 /* Start a loop. */
754 this->registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters;
755 this->loop_tops[cur_opcode.ctrl_loop.reg_index] = this->instruction_ptr;
756 } else {
757 /* End a loop. */
758 this->registers[cur_opcode.ctrl_loop.reg_index]--;
759 if (this->registers[cur_opcode.ctrl_loop.reg_index] != 0) {
760 this->instruction_ptr = this->loop_tops[cur_opcode.ctrl_loop.reg_index];
761 }
762 }
763 break;
764 case CheatVmOpcodeType_LoadRegisterStatic:
765 /* Set a register to a static value. */
766 this->registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value;
767 break;
768 case CheatVmOpcodeType_LoadRegisterMemory: {
769 /* Choose source address. */
770 u64 src_address;
771 if (cur_opcode.ldr_memory.load_from_reg) {
772 src_address = this->registers[cur_opcode.ldr_memory.reg_index] +
773 cur_opcode.ldr_memory.rel_address;
774 } else {
775 src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type,
776 cur_opcode.ldr_memory.rel_address);
777 }
778 /* Read into register. Gateway only reads on valid bitwidth. */
779 switch (cur_opcode.ldr_memory.bit_width) {
780 case 1:
781 case 2:
782 case 4:
783 case 8:
784 callbacks->MemoryRead(src_address,
785 &this->registers[cur_opcode.ldr_memory.reg_index],
786 cur_opcode.ldr_memory.bit_width);
787 break;
788 }
789 } break;
790 case CheatVmOpcodeType_StoreStaticToAddress: {
791 /* Calculate address. */
792 u64 dst_address = this->registers[cur_opcode.str_static.reg_index];
793 u64 dst_value = cur_opcode.str_static.value;
794 if (cur_opcode.str_static.add_offset_reg) {
795 dst_address += this->registers[cur_opcode.str_static.offset_reg_index];
796 }
797 /* Write value to memory. Gateway only writes on valid bitwidth. */
798 switch (cur_opcode.str_static.bit_width) {
799 case 1:
800 case 2:
801 case 4:
802 case 8:
803 callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_static.bit_width);
804 break;
805 }
806 /* Increment register if relevant. */
807 if (cur_opcode.str_static.increment_reg) {
808 this->registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width;
809 }
810 } break;
811 case CheatVmOpcodeType_PerformArithmeticStatic: {
812 /* Do requested math. */
813 switch (cur_opcode.perform_math_static.math_type) {
814 case RegisterArithmeticType_Addition:
815 this->registers[cur_opcode.perform_math_static.reg_index] +=
816 (u64)cur_opcode.perform_math_static.value;
817 break;
818 case RegisterArithmeticType_Subtraction:
819 this->registers[cur_opcode.perform_math_static.reg_index] -=
820 (u64)cur_opcode.perform_math_static.value;
821 break;
822 case RegisterArithmeticType_Multiplication:
823 this->registers[cur_opcode.perform_math_static.reg_index] *=
824 (u64)cur_opcode.perform_math_static.value;
825 break;
826 case RegisterArithmeticType_LeftShift:
827 this->registers[cur_opcode.perform_math_static.reg_index] <<=
828 (u64)cur_opcode.perform_math_static.value;
829 break;
830 case RegisterArithmeticType_RightShift:
831 this->registers[cur_opcode.perform_math_static.reg_index] >>=
832 (u64)cur_opcode.perform_math_static.value;
833 break;
834 default:
835 /* Do not handle extensions here. */
836 break;
837 }
838 /* Apply bit width. */
839 switch (cur_opcode.perform_math_static.bit_width) {
840 case 1:
841 this->registers[cur_opcode.perform_math_static.reg_index] =
842 static_cast<u8>(this->registers[cur_opcode.perform_math_static.reg_index]);
843 break;
844 case 2:
845 this->registers[cur_opcode.perform_math_static.reg_index] =
846 static_cast<u16>(this->registers[cur_opcode.perform_math_static.reg_index]);
847 break;
848 case 4:
849 this->registers[cur_opcode.perform_math_static.reg_index] =
850 static_cast<u32>(this->registers[cur_opcode.perform_math_static.reg_index]);
851 break;
852 case 8:
853 this->registers[cur_opcode.perform_math_static.reg_index] =
854 static_cast<u64>(this->registers[cur_opcode.perform_math_static.reg_index]);
855 break;
856 }
857 } break;
858 case CheatVmOpcodeType_BeginKeypressConditionalBlock:
859 /* Check for keypress. */
860 if ((cur_opcode.begin_keypress_cond.key_mask & kDown) !=
861 cur_opcode.begin_keypress_cond.key_mask) {
862 /* Keys not pressed. Skip conditional block. */
863 this->SkipConditionalBlock();
864 }
865 break;
866 case CheatVmOpcodeType_PerformArithmeticRegister: {
867 const u64 operand_1_value =
868 this->registers[cur_opcode.perform_math_reg.src_reg_1_index];
869 const u64 operand_2_value =
870 cur_opcode.perform_math_reg.has_immediate
871 ? GetVmInt(cur_opcode.perform_math_reg.value,
872 cur_opcode.perform_math_reg.bit_width)
873 : this->registers[cur_opcode.perform_math_reg.src_reg_2_index];
874
875 u64 res_val = 0;
876 /* Do requested math. */
877 switch (cur_opcode.perform_math_reg.math_type) {
878 case RegisterArithmeticType_Addition:
879 res_val = operand_1_value + operand_2_value;
880 break;
881 case RegisterArithmeticType_Subtraction:
882 res_val = operand_1_value - operand_2_value;
883 break;
884 case RegisterArithmeticType_Multiplication:
885 res_val = operand_1_value * operand_2_value;
886 break;
887 case RegisterArithmeticType_LeftShift:
888 res_val = operand_1_value << operand_2_value;
889 break;
890 case RegisterArithmeticType_RightShift:
891 res_val = operand_1_value >> operand_2_value;
892 break;
893 case RegisterArithmeticType_LogicalAnd:
894 res_val = operand_1_value & operand_2_value;
895 break;
896 case RegisterArithmeticType_LogicalOr:
897 res_val = operand_1_value | operand_2_value;
898 break;
899 case RegisterArithmeticType_LogicalNot:
900 res_val = ~operand_1_value;
901 break;
902 case RegisterArithmeticType_LogicalXor:
903 res_val = operand_1_value ^ operand_2_value;
904 break;
905 case RegisterArithmeticType_None:
906 res_val = operand_1_value;
907 break;
908 }
909
910 /* Apply bit width. */
911 switch (cur_opcode.perform_math_reg.bit_width) {
912 case 1:
913 res_val = static_cast<u8>(res_val);
914 break;
915 case 2:
916 res_val = static_cast<u16>(res_val);
917 break;
918 case 4:
919 res_val = static_cast<u32>(res_val);
920 break;
921 case 8:
922 res_val = static_cast<u64>(res_val);
923 break;
924 }
925
926 /* Save to register. */
927 this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val;
928 } break;
929 case CheatVmOpcodeType_StoreRegisterToAddress: {
930 /* Calculate address. */
931 u64 dst_value = this->registers[cur_opcode.str_register.str_reg_index];
932 u64 dst_address = this->registers[cur_opcode.str_register.addr_reg_index];
933 switch (cur_opcode.str_register.ofs_type) {
934 case StoreRegisterOffsetType_None:
935 /* Nothing more to do */
936 break;
937 case StoreRegisterOffsetType_Reg:
938 dst_address += this->registers[cur_opcode.str_register.ofs_reg_index];
939 break;
940 case StoreRegisterOffsetType_Imm:
941 dst_address += cur_opcode.str_register.rel_address;
942 break;
943 case StoreRegisterOffsetType_MemReg:
944 dst_address =
945 GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type,
946 this->registers[cur_opcode.str_register.addr_reg_index]);
947 break;
948 case StoreRegisterOffsetType_MemImm:
949 dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type,
950 cur_opcode.str_register.rel_address);
951 break;
952 case StoreRegisterOffsetType_MemImmReg:
953 dst_address =
954 GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type,
955 this->registers[cur_opcode.str_register.addr_reg_index] +
956 cur_opcode.str_register.rel_address);
957 break;
958 }
959
960 /* Write value to memory. Write only on valid bitwidth. */
961 switch (cur_opcode.str_register.bit_width) {
962 case 1:
963 case 2:
964 case 4:
965 case 8:
966 callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_register.bit_width);
967 break;
968 }
969
970 /* Increment register if relevant. */
971 if (cur_opcode.str_register.increment_reg) {
972 this->registers[cur_opcode.str_register.addr_reg_index] +=
973 cur_opcode.str_register.bit_width;
974 }
975 } break;
976 case CheatVmOpcodeType_BeginRegisterConditionalBlock: {
977 /* Get value from register. */
978 u64 src_value = 0;
979 switch (cur_opcode.begin_reg_cond.bit_width) {
980 case 1:
981 src_value = static_cast<u8>(
982 this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul);
983 break;
984 case 2:
985 src_value = static_cast<u16>(
986 this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul);
987 break;
988 case 4:
989 src_value = static_cast<u32>(
990 this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul);
991 break;
992 case 8:
993 src_value =
994 static_cast<u64>(this->registers[cur_opcode.begin_reg_cond.val_reg_index] &
995 0xFFFFFFFFFFFFFFFFul);
996 break;
997 }
998
999 /* Read value from memory. */
1000 u64 cond_value = 0;
1001 if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) {
1002 cond_value =
1003 GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width);
1004 } else if (cur_opcode.begin_reg_cond.comp_type ==
1005 CompareRegisterValueType_OtherRegister) {
1006 switch (cur_opcode.begin_reg_cond.bit_width) {
1007 case 1:
1008 cond_value = static_cast<u8>(
1009 this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul);
1010 break;
1011 case 2:
1012 cond_value = static_cast<u16>(
1013 this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul);
1014 break;
1015 case 4:
1016 cond_value = static_cast<u32>(
1017 this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul);
1018 break;
1019 case 8:
1020 cond_value = static_cast<u64>(
1021 this->registers[cur_opcode.begin_reg_cond.other_reg_index] &
1022 0xFFFFFFFFFFFFFFFFul);
1023 break;
1024 }
1025 } else {
1026 u64 cond_address = 0;
1027 switch (cur_opcode.begin_reg_cond.comp_type) {
1028 case CompareRegisterValueType_MemoryRelAddr:
1029 cond_address =
1030 GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type,
1031 cur_opcode.begin_reg_cond.rel_address);
1032 break;
1033 case CompareRegisterValueType_MemoryOfsReg:
1034 cond_address = GetCheatProcessAddress(
1035 metadata, cur_opcode.begin_reg_cond.mem_type,
1036 this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]);
1037 break;
1038 case CompareRegisterValueType_RegisterRelAddr:
1039 cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] +
1040 cur_opcode.begin_reg_cond.rel_address;
1041 break;
1042 case CompareRegisterValueType_RegisterOfsReg:
1043 cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] +
1044 this->registers[cur_opcode.begin_reg_cond.ofs_reg_index];
1045 break;
1046 default:
1047 break;
1048 }
1049 switch (cur_opcode.begin_reg_cond.bit_width) {
1050 case 1:
1051 case 2:
1052 case 4:
1053 case 8:
1054 callbacks->MemoryRead(cond_address, &cond_value,
1055 cur_opcode.begin_reg_cond.bit_width);
1056 break;
1057 }
1058 }
1059
1060 /* Check against condition. */
1061 bool cond_met = false;
1062 switch (cur_opcode.begin_reg_cond.cond_type) {
1063 case ConditionalComparisonType_GT:
1064 cond_met = src_value > cond_value;
1065 break;
1066 case ConditionalComparisonType_GE:
1067 cond_met = src_value >= cond_value;
1068 break;
1069 case ConditionalComparisonType_LT:
1070 cond_met = src_value < cond_value;
1071 break;
1072 case ConditionalComparisonType_LE:
1073 cond_met = src_value <= cond_value;
1074 break;
1075 case ConditionalComparisonType_EQ:
1076 cond_met = src_value == cond_value;
1077 break;
1078 case ConditionalComparisonType_NE:
1079 cond_met = src_value != cond_value;
1080 break;
1081 }
1082
1083 /* Skip conditional block if condition not met. */
1084 if (!cond_met) {
1085 this->SkipConditionalBlock();
1086 }
1087 } break;
1088 case CheatVmOpcodeType_SaveRestoreRegister:
1089 /* Save or restore a register. */
1090 switch (cur_opcode.save_restore_reg.op_type) {
1091 case SaveRestoreRegisterOpType_ClearRegs:
1092 this->registers[cur_opcode.save_restore_reg.dst_index] = 0ul;
1093 break;
1094 case SaveRestoreRegisterOpType_ClearSaved:
1095 this->saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul;
1096 break;
1097 case SaveRestoreRegisterOpType_Save:
1098 this->saved_values[cur_opcode.save_restore_reg.dst_index] =
1099 this->registers[cur_opcode.save_restore_reg.src_index];
1100 break;
1101 case SaveRestoreRegisterOpType_Restore:
1102 default:
1103 this->registers[cur_opcode.save_restore_reg.dst_index] =
1104 this->saved_values[cur_opcode.save_restore_reg.src_index];
1105 break;
1106 }
1107 break;
1108 case CheatVmOpcodeType_SaveRestoreRegisterMask:
1109 /* Save or restore register mask. */
1110 u64* src;
1111 u64* dst;
1112 switch (cur_opcode.save_restore_regmask.op_type) {
1113 case SaveRestoreRegisterOpType_ClearSaved:
1114 case SaveRestoreRegisterOpType_Save:
1115 src = this->registers.data();
1116 dst = this->saved_values.data();
1117 break;
1118 case SaveRestoreRegisterOpType_ClearRegs:
1119 case SaveRestoreRegisterOpType_Restore:
1120 default:
1121 src = this->registers.data();
1122 dst = this->saved_values.data();
1123 break;
1124 }
1125 for (size_t i = 0; i < NumRegisters; i++) {
1126 if (cur_opcode.save_restore_regmask.should_operate[i]) {
1127 switch (cur_opcode.save_restore_regmask.op_type) {
1128 case SaveRestoreRegisterOpType_ClearSaved:
1129 case SaveRestoreRegisterOpType_ClearRegs:
1130 dst[i] = 0ul;
1131 break;
1132 case SaveRestoreRegisterOpType_Save:
1133 case SaveRestoreRegisterOpType_Restore:
1134 default:
1135 dst[i] = src[i];
1136 break;
1137 }
1138 }
1139 }
1140 break;
1141 case CheatVmOpcodeType_DebugLog: {
1142 /* Read value from memory. */
1143 u64 log_value = 0;
1144 if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) {
1145 switch (cur_opcode.debug_log.bit_width) {
1146 case 1:
1147 log_value = static_cast<u8>(
1148 this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul);
1149 break;
1150 case 2:
1151 log_value = static_cast<u16>(
1152 this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul);
1153 break;
1154 case 4:
1155 log_value = static_cast<u32>(
1156 this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul);
1157 break;
1158 case 8:
1159 log_value = static_cast<u64>(
1160 this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul);
1161 break;
1162 }
1163 } else {
1164 u64 val_address = 0;
1165 switch (cur_opcode.debug_log.val_type) {
1166 case DebugLogValueType_MemoryRelAddr:
1167 val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type,
1168 cur_opcode.debug_log.rel_address);
1169 break;
1170 case DebugLogValueType_MemoryOfsReg:
1171 val_address =
1172 GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type,
1173 this->registers[cur_opcode.debug_log.ofs_reg_index]);
1174 break;
1175 case DebugLogValueType_RegisterRelAddr:
1176 val_address = this->registers[cur_opcode.debug_log.addr_reg_index] +
1177 cur_opcode.debug_log.rel_address;
1178 break;
1179 case DebugLogValueType_RegisterOfsReg:
1180 val_address = this->registers[cur_opcode.debug_log.addr_reg_index] +
1181 this->registers[cur_opcode.debug_log.ofs_reg_index];
1182 break;
1183 default:
1184 break;
1185 }
1186 switch (cur_opcode.debug_log.bit_width) {
1187 case 1:
1188 case 2:
1189 case 4:
1190 case 8:
1191 callbacks->MemoryRead(val_address, &log_value, cur_opcode.debug_log.bit_width);
1192 break;
1193 }
1194 }
1195
1196 /* Log value. */
1197 this->DebugLog(cur_opcode.debug_log.log_id, log_value);
1198 } break;
1199 default:
1200 /* By default, we do a no-op. */
1201 break;
1202 }
1203 }
1204}
1205
1206} // namespace Memory
diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h
new file mode 100644
index 000000000..bea451db4
--- /dev/null
+++ b/src/core/memory/dmnt_cheat_vm.h
@@ -0,0 +1,334 @@
1/*
2 * Copyright (c) 2018-2019 Atmosphère-NX
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms and conditions of the GNU General Public License,
6 * version 2, as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17/*
18 * Adapted by DarkLordZach for use/interaction with yuzu
19 *
20 * Modifications Copyright 2019 yuzu emulator team
21 * Licensed under GPLv2 or any later version
22 * Refer to the license.txt file included.
23 */
24
25#pragma once
26
27#include <vector>
28#include <fmt/printf.h>
29#include "common/common_types.h"
30#include "core/memory/dmnt_cheat_types.h"
31
32namespace Memory {
33
34enum CheatVmOpcodeType : u32 {
35 CheatVmOpcodeType_StoreStatic = 0,
36 CheatVmOpcodeType_BeginConditionalBlock = 1,
37 CheatVmOpcodeType_EndConditionalBlock = 2,
38 CheatVmOpcodeType_ControlLoop = 3,
39 CheatVmOpcodeType_LoadRegisterStatic = 4,
40 CheatVmOpcodeType_LoadRegisterMemory = 5,
41 CheatVmOpcodeType_StoreStaticToAddress = 6,
42 CheatVmOpcodeType_PerformArithmeticStatic = 7,
43 CheatVmOpcodeType_BeginKeypressConditionalBlock = 8,
44
45 /* These are not implemented by Gateway's VM. */
46 CheatVmOpcodeType_PerformArithmeticRegister = 9,
47 CheatVmOpcodeType_StoreRegisterToAddress = 10,
48 CheatVmOpcodeType_Reserved11 = 11,
49
50 /* This is a meta entry, and not a real opcode. */
51 /* This is to facilitate multi-nybble instruction decoding. */
52 CheatVmOpcodeType_ExtendedWidth = 12,
53
54 /* Extended width opcodes. */
55 CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0,
56 CheatVmOpcodeType_SaveRestoreRegister = 0xC1,
57 CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2,
58
59 /* This is a meta entry, and not a real opcode. */
60 /* This is to facilitate multi-nybble instruction decoding. */
61 CheatVmOpcodeType_DoubleExtendedWidth = 0xF0,
62
63 /* Double-extended width opcodes. */
64 CheatVmOpcodeType_DebugLog = 0xFFF,
65};
66
67enum MemoryAccessType : u32 {
68 MemoryAccessType_MainNso = 0,
69 MemoryAccessType_Heap = 1,
70};
71
72enum ConditionalComparisonType : u32 {
73 ConditionalComparisonType_GT = 1,
74 ConditionalComparisonType_GE = 2,
75 ConditionalComparisonType_LT = 3,
76 ConditionalComparisonType_LE = 4,
77 ConditionalComparisonType_EQ = 5,
78 ConditionalComparisonType_NE = 6,
79};
80
81enum RegisterArithmeticType : u32 {
82 RegisterArithmeticType_Addition = 0,
83 RegisterArithmeticType_Subtraction = 1,
84 RegisterArithmeticType_Multiplication = 2,
85 RegisterArithmeticType_LeftShift = 3,
86 RegisterArithmeticType_RightShift = 4,
87
88 /* These are not supported by Gateway's VM. */
89 RegisterArithmeticType_LogicalAnd = 5,
90 RegisterArithmeticType_LogicalOr = 6,
91 RegisterArithmeticType_LogicalNot = 7,
92 RegisterArithmeticType_LogicalXor = 8,
93
94 RegisterArithmeticType_None = 9,
95};
96
97enum StoreRegisterOffsetType : u32 {
98 StoreRegisterOffsetType_None = 0,
99 StoreRegisterOffsetType_Reg = 1,
100 StoreRegisterOffsetType_Imm = 2,
101 StoreRegisterOffsetType_MemReg = 3,
102 StoreRegisterOffsetType_MemImm = 4,
103 StoreRegisterOffsetType_MemImmReg = 5,
104};
105
106enum CompareRegisterValueType : u32 {
107 CompareRegisterValueType_MemoryRelAddr = 0,
108 CompareRegisterValueType_MemoryOfsReg = 1,
109 CompareRegisterValueType_RegisterRelAddr = 2,
110 CompareRegisterValueType_RegisterOfsReg = 3,
111 CompareRegisterValueType_StaticValue = 4,
112 CompareRegisterValueType_OtherRegister = 5,
113};
114
115enum SaveRestoreRegisterOpType : u32 {
116 SaveRestoreRegisterOpType_Restore = 0,
117 SaveRestoreRegisterOpType_Save = 1,
118 SaveRestoreRegisterOpType_ClearSaved = 2,
119 SaveRestoreRegisterOpType_ClearRegs = 3,
120};
121
122enum DebugLogValueType : u32 {
123 DebugLogValueType_MemoryRelAddr = 0,
124 DebugLogValueType_MemoryOfsReg = 1,
125 DebugLogValueType_RegisterRelAddr = 2,
126 DebugLogValueType_RegisterOfsReg = 3,
127 DebugLogValueType_RegisterValue = 4,
128};
129
130union VmInt {
131 u8 bit8;
132 u16 bit16;
133 u32 bit32;
134 u64 bit64;
135};
136
137struct StoreStaticOpcode {
138 u32 bit_width;
139 MemoryAccessType mem_type;
140 u32 offset_register;
141 u64 rel_address;
142 VmInt value;
143};
144
145struct BeginConditionalOpcode {
146 u32 bit_width;
147 MemoryAccessType mem_type;
148 ConditionalComparisonType cond_type;
149 u64 rel_address;
150 VmInt value;
151};
152
153struct EndConditionalOpcode {};
154
155struct ControlLoopOpcode {
156 bool start_loop;
157 u32 reg_index;
158 u32 num_iters;
159};
160
161struct LoadRegisterStaticOpcode {
162 u32 reg_index;
163 u64 value;
164};
165
166struct LoadRegisterMemoryOpcode {
167 u32 bit_width;
168 MemoryAccessType mem_type;
169 u32 reg_index;
170 bool load_from_reg;
171 u64 rel_address;
172};
173
174struct StoreStaticToAddressOpcode {
175 u32 bit_width;
176 u32 reg_index;
177 bool increment_reg;
178 bool add_offset_reg;
179 u32 offset_reg_index;
180 u64 value;
181};
182
183struct PerformArithmeticStaticOpcode {
184 u32 bit_width;
185 u32 reg_index;
186 RegisterArithmeticType math_type;
187 u32 value;
188};
189
190struct BeginKeypressConditionalOpcode {
191 u32 key_mask;
192};
193
194struct PerformArithmeticRegisterOpcode {
195 u32 bit_width;
196 RegisterArithmeticType math_type;
197 u32 dst_reg_index;
198 u32 src_reg_1_index;
199 u32 src_reg_2_index;
200 bool has_immediate;
201 VmInt value;
202};
203
204struct StoreRegisterToAddressOpcode {
205 u32 bit_width;
206 u32 str_reg_index;
207 u32 addr_reg_index;
208 bool increment_reg;
209 StoreRegisterOffsetType ofs_type;
210 MemoryAccessType mem_type;
211 u32 ofs_reg_index;
212 u64 rel_address;
213};
214
215struct BeginRegisterConditionalOpcode {
216 u32 bit_width;
217 ConditionalComparisonType cond_type;
218 u32 val_reg_index;
219 CompareRegisterValueType comp_type;
220 MemoryAccessType mem_type;
221 u32 addr_reg_index;
222 u32 other_reg_index;
223 u32 ofs_reg_index;
224 u64 rel_address;
225 VmInt value;
226};
227
228struct SaveRestoreRegisterOpcode {
229 u32 dst_index;
230 u32 src_index;
231 SaveRestoreRegisterOpType op_type;
232};
233
234struct SaveRestoreRegisterMaskOpcode {
235 SaveRestoreRegisterOpType op_type;
236 std::array<bool, 0x10> should_operate;
237};
238
239struct DebugLogOpcode {
240 u32 bit_width;
241 u32 log_id;
242 DebugLogValueType val_type;
243 MemoryAccessType mem_type;
244 u32 addr_reg_index;
245 u32 val_reg_index;
246 u32 ofs_reg_index;
247 u64 rel_address;
248};
249
250struct CheatVmOpcode {
251 CheatVmOpcodeType opcode;
252 bool begin_conditional_block;
253 union {
254 StoreStaticOpcode store_static;
255 BeginConditionalOpcode begin_cond;
256 EndConditionalOpcode end_cond;
257 ControlLoopOpcode ctrl_loop;
258 LoadRegisterStaticOpcode ldr_static;
259 LoadRegisterMemoryOpcode ldr_memory;
260 StoreStaticToAddressOpcode str_static;
261 PerformArithmeticStaticOpcode perform_math_static;
262 BeginKeypressConditionalOpcode begin_keypress_cond;
263 PerformArithmeticRegisterOpcode perform_math_reg;
264 StoreRegisterToAddressOpcode str_register;
265 BeginRegisterConditionalOpcode begin_reg_cond;
266 SaveRestoreRegisterOpcode save_restore_reg;
267 SaveRestoreRegisterMaskOpcode save_restore_regmask;
268 DebugLogOpcode debug_log;
269 };
270};
271
272class DmntCheatVm {
273public:
274 /// Helper Type for DmntCheatVm <=> yuzu Interface
275 class Callbacks {
276 public:
277 virtual ~Callbacks();
278
279 virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
280 virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
281
282 virtual u64 HidKeysDown() = 0;
283
284 virtual void DebugLog(u8 id, u64 value) = 0;
285 virtual void CommandLog(std::string_view data) = 0;
286 };
287
288 constexpr static size_t MaximumProgramOpcodeCount = 0x400;
289 constexpr static size_t NumRegisters = 0x10;
290
291private:
292 std::unique_ptr<Callbacks> callbacks;
293
294 size_t num_opcodes = 0;
295 size_t instruction_ptr = 0;
296 size_t condition_depth = 0;
297 bool decode_success = false;
298 std::array<u32, MaximumProgramOpcodeCount> program{};
299 std::array<u64, NumRegisters> registers{};
300 std::array<u64, NumRegisters> saved_values{};
301 std::array<size_t, NumRegisters> loop_tops{};
302
303private:
304 bool DecodeNextOpcode(CheatVmOpcode& out);
305 void SkipConditionalBlock();
306 void ResetState();
307
308 /* For implementing the DebugLog opcode. */
309 void DebugLog(u32 log_id, u64 value);
310
311 /* For debugging. These will be IFDEF'd out normally. */
312 template <typename... Args>
313 void LogToDebugFile(const char* format, const Args&... args) {
314 callbacks->CommandLog(fmt::sprintf(format, args...));
315 }
316
317 void LogOpcode(const CheatVmOpcode& opcode);
318
319 static u64 GetVmInt(VmInt value, u32 bit_width);
320 static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
321 MemoryAccessType mem_type, u64 rel_address);
322
323public:
324 DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
325
326 size_t GetProgramSize() {
327 return this->num_opcodes;
328 }
329
330 bool LoadProgram(const std::vector<CheatEntry>& cheats);
331 void Execute(const CheatProcessMetadata& metadata);
332};
333
334}; // namespace Memory