diff options
| author | 2019-05-30 19:34:02 -0400 | |
|---|---|---|
| committer | 2019-09-21 21:44:22 -0400 | |
| commit | 12aa127df3826857149bfc4b787cfb7df3fdcafe (patch) | |
| tree | ef83925202b36ab21fbf0b5c67e465a04e048b4e /src/core/memory | |
| parent | log: Add logging class for Cheat Engine (diff) | |
| download | yuzu-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.h | 58 | ||||
| -rw-r--r-- | src/core/memory/dmnt_cheat_vm.cpp | 1206 | ||||
| -rw-r--r-- | src/core/memory/dmnt_cheat_vm.h | 334 |
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 | |||
| 29 | namespace Memory { | ||
| 30 | |||
| 31 | struct MemoryRegionExtents { | ||
| 32 | u64 base; | ||
| 33 | u64 size; | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct 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 | |||
| 46 | struct CheatDefinition { | ||
| 47 | std::array<char, 0x40> readable_name; | ||
| 48 | u32 num_opcodes; | ||
| 49 | std::array<u32, 0x100> opcodes; | ||
| 50 | }; | ||
| 51 | |||
| 52 | struct 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 | |||
| 30 | namespace Memory { | ||
| 31 | |||
| 32 | void DmntCheatVm::DebugLog(u32 log_id, u64 value) { | ||
| 33 | callbacks->DebugLog(static_cast<u8>(log_id), value); | ||
| 34 | } | ||
| 35 | |||
| 36 | void 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 | |||
| 224 | DmntCheatVm::Callbacks::~Callbacks() = default; | ||
| 225 | |||
| 226 | bool 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 | |||
| 564 | void 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 | |||
| 594 | u64 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 | |||
| 610 | u64 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 | |||
| 621 | void 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 | |||
| 632 | bool 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 | |||
| 653 | void 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 | |||
| 32 | namespace Memory { | ||
| 33 | |||
| 34 | enum 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 | |||
| 67 | enum MemoryAccessType : u32 { | ||
| 68 | MemoryAccessType_MainNso = 0, | ||
| 69 | MemoryAccessType_Heap = 1, | ||
| 70 | }; | ||
| 71 | |||
| 72 | enum 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 | |||
| 81 | enum 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 | |||
| 97 | enum 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 | |||
| 106 | enum 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 | |||
| 115 | enum SaveRestoreRegisterOpType : u32 { | ||
| 116 | SaveRestoreRegisterOpType_Restore = 0, | ||
| 117 | SaveRestoreRegisterOpType_Save = 1, | ||
| 118 | SaveRestoreRegisterOpType_ClearSaved = 2, | ||
| 119 | SaveRestoreRegisterOpType_ClearRegs = 3, | ||
| 120 | }; | ||
| 121 | |||
| 122 | enum DebugLogValueType : u32 { | ||
| 123 | DebugLogValueType_MemoryRelAddr = 0, | ||
| 124 | DebugLogValueType_MemoryOfsReg = 1, | ||
| 125 | DebugLogValueType_RegisterRelAddr = 2, | ||
| 126 | DebugLogValueType_RegisterOfsReg = 3, | ||
| 127 | DebugLogValueType_RegisterValue = 4, | ||
| 128 | }; | ||
| 129 | |||
| 130 | union VmInt { | ||
| 131 | u8 bit8; | ||
| 132 | u16 bit16; | ||
| 133 | u32 bit32; | ||
| 134 | u64 bit64; | ||
| 135 | }; | ||
| 136 | |||
| 137 | struct StoreStaticOpcode { | ||
| 138 | u32 bit_width; | ||
| 139 | MemoryAccessType mem_type; | ||
| 140 | u32 offset_register; | ||
| 141 | u64 rel_address; | ||
| 142 | VmInt value; | ||
| 143 | }; | ||
| 144 | |||
| 145 | struct BeginConditionalOpcode { | ||
| 146 | u32 bit_width; | ||
| 147 | MemoryAccessType mem_type; | ||
| 148 | ConditionalComparisonType cond_type; | ||
| 149 | u64 rel_address; | ||
| 150 | VmInt value; | ||
| 151 | }; | ||
| 152 | |||
| 153 | struct EndConditionalOpcode {}; | ||
| 154 | |||
| 155 | struct ControlLoopOpcode { | ||
| 156 | bool start_loop; | ||
| 157 | u32 reg_index; | ||
| 158 | u32 num_iters; | ||
| 159 | }; | ||
| 160 | |||
| 161 | struct LoadRegisterStaticOpcode { | ||
| 162 | u32 reg_index; | ||
| 163 | u64 value; | ||
| 164 | }; | ||
| 165 | |||
| 166 | struct LoadRegisterMemoryOpcode { | ||
| 167 | u32 bit_width; | ||
| 168 | MemoryAccessType mem_type; | ||
| 169 | u32 reg_index; | ||
| 170 | bool load_from_reg; | ||
| 171 | u64 rel_address; | ||
| 172 | }; | ||
| 173 | |||
| 174 | struct 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 | |||
| 183 | struct PerformArithmeticStaticOpcode { | ||
| 184 | u32 bit_width; | ||
| 185 | u32 reg_index; | ||
| 186 | RegisterArithmeticType math_type; | ||
| 187 | u32 value; | ||
| 188 | }; | ||
| 189 | |||
| 190 | struct BeginKeypressConditionalOpcode { | ||
| 191 | u32 key_mask; | ||
| 192 | }; | ||
| 193 | |||
| 194 | struct 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 | |||
| 204 | struct 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 | |||
| 215 | struct 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 | |||
| 228 | struct SaveRestoreRegisterOpcode { | ||
| 229 | u32 dst_index; | ||
| 230 | u32 src_index; | ||
| 231 | SaveRestoreRegisterOpType op_type; | ||
| 232 | }; | ||
| 233 | |||
| 234 | struct SaveRestoreRegisterMaskOpcode { | ||
| 235 | SaveRestoreRegisterOpType op_type; | ||
| 236 | std::array<bool, 0x10> should_operate; | ||
| 237 | }; | ||
| 238 | |||
| 239 | struct 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 | |||
| 250 | struct 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 | |||
| 272 | class DmntCheatVm { | ||
| 273 | public: | ||
| 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 | |||
| 291 | private: | ||
| 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 | |||
| 303 | private: | ||
| 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 | |||
| 323 | public: | ||
| 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 | ||