From 12aa127df3826857149bfc4b787cfb7df3fdcafe Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:34:02 -0400 Subject: [PATCH] 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. --- src/core/memory/dmnt_cheat_types.h | 58 ++ src/core/memory/dmnt_cheat_vm.cpp | 1206 ++++++++++++++++++++++++++++ src/core/memory/dmnt_cheat_vm.h | 334 ++++++++ 3 files changed, 1598 insertions(+) create mode 100644 src/core/memory/dmnt_cheat_types.h create mode 100644 src/core/memory/dmnt_cheat_vm.cpp create mode 100644 src/core/memory/dmnt_cheat_vm.h 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 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#pragma once + +#include "common/common_types.h" + +namespace Memory { + +struct MemoryRegionExtents { + u64 base; + u64 size; +}; + +struct CheatProcessMetadata { + u64 process_id; + u64 title_id; + MemoryRegionExtents main_nso_extents; + MemoryRegionExtents heap_extents; + MemoryRegionExtents alias_extents; + MemoryRegionExtents address_space_extents; + std::array main_nso_build_id; +}; + +struct CheatDefinition { + std::array readable_name; + u32 num_opcodes; + std::array opcodes; +}; + +struct CheatEntry { + bool enabled; + u32 cheat_id; + CheatDefinition definition; +}; + +} // 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 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "core/memory/dmnt_cheat_types.h" +#include "core/memory/dmnt_cheat_vm.h" + +namespace Memory { + +void DmntCheatVm::DebugLog(u32 log_id, u64 value) { + callbacks->DebugLog(static_cast(log_id), value); +} + +void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { + switch (opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: + this->LogToDebugFile("Opcode: Store Static\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.store_static.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode.store_static.mem_type); + this->LogToDebugFile("Reg Idx: %x\n", opcode.store_static.offset_register); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.store_static.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode.store_static.value.bit64); + break; + case CheatVmOpcodeType_BeginConditionalBlock: + this->LogToDebugFile("Opcode: Begin Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.begin_cond.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode.begin_cond.mem_type); + this->LogToDebugFile("Cond Type: %x\n", opcode.begin_cond.cond_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_cond.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode.begin_cond.value.bit64); + break; + case CheatVmOpcodeType_EndConditionalBlock: + this->LogToDebugFile("Opcode: End Conditional\n"); + break; + case CheatVmOpcodeType_ControlLoop: + if (opcode.ctrl_loop.start_loop) { + this->LogToDebugFile("Opcode: Start Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode.ctrl_loop.reg_index); + this->LogToDebugFile("Num Iters: %x\n", opcode.ctrl_loop.num_iters); + } else { + this->LogToDebugFile("Opcode: End Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode.ctrl_loop.reg_index); + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + this->LogToDebugFile("Opcode: Load Register Static\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode.ldr_static.reg_index); + this->LogToDebugFile("Value: %lx\n", opcode.ldr_static.value); + break; + case CheatVmOpcodeType_LoadRegisterMemory: + this->LogToDebugFile("Opcode: Load Register Memory\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.ldr_memory.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode.ldr_memory.reg_index); + this->LogToDebugFile("Mem Type: %x\n", opcode.ldr_memory.mem_type); + this->LogToDebugFile("From Reg: %d\n", opcode.ldr_memory.load_from_reg); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.ldr_memory.rel_address); + break; + case CheatVmOpcodeType_StoreStaticToAddress: + this->LogToDebugFile("Opcode: Store Static to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.str_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode.str_static.reg_index); + if (opcode.str_static.add_offset_reg) { + this->LogToDebugFile("O Reg Idx: %x\n", opcode.str_static.offset_reg_index); + } + this->LogToDebugFile("Incr Reg: %d\n", opcode.str_static.increment_reg); + this->LogToDebugFile("Value: %lx\n", opcode.str_static.value); + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + this->LogToDebugFile("Opcode: Perform Static Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.perform_math_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode.perform_math_static.reg_index); + this->LogToDebugFile("Math Type: %x\n", opcode.perform_math_static.math_type); + this->LogToDebugFile("Value: %lx\n", opcode.perform_math_static.value); + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + this->LogToDebugFile("Opcode: Begin Keypress Conditional\n"); + this->LogToDebugFile("Key Mask: %x\n", opcode.begin_keypress_cond.key_mask); + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + this->LogToDebugFile("Opcode: Perform Register Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.perform_math_reg.bit_width); + this->LogToDebugFile("Dst Idx: %x\n", opcode.perform_math_reg.dst_reg_index); + this->LogToDebugFile("Src1 Idx: %x\n", opcode.perform_math_reg.src_reg_1_index); + if (opcode.perform_math_reg.has_immediate) { + this->LogToDebugFile("Value: %lx\n", opcode.perform_math_reg.value.bit64); + } else { + this->LogToDebugFile("Src2 Idx: %x\n", opcode.perform_math_reg.src_reg_2_index); + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + this->LogToDebugFile("Opcode: Store Register to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.str_register.bit_width); + this->LogToDebugFile("S Reg Idx: %x\n", opcode.str_register.str_reg_index); + this->LogToDebugFile("A Reg Idx: %x\n", opcode.str_register.addr_reg_index); + this->LogToDebugFile("Incr Reg: %d\n", opcode.str_register.increment_reg); + switch (opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + break; + case StoreRegisterOffsetType_Reg: + this->LogToDebugFile("O Reg Idx: %x\n", opcode.str_register.ofs_reg_index); + break; + case StoreRegisterOffsetType_Imm: + this->LogToDebugFile("Rel Addr: %lx\n", opcode.str_register.rel_address); + break; + case StoreRegisterOffsetType_MemReg: + this->LogToDebugFile("Mem Type: %x\n", opcode.str_register.mem_type); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + this->LogToDebugFile("Mem Type: %x\n", opcode.str_register.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.str_register.rel_address); + break; + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + this->LogToDebugFile("Opcode: Begin Register Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.begin_reg_cond.bit_width); + this->LogToDebugFile("Cond Type: %x\n", opcode.begin_reg_cond.cond_type); + this->LogToDebugFile("V Reg Idx: %x\n", opcode.begin_reg_cond.val_reg_index); + switch (opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + this->LogToDebugFile("Comp Type: Static Value\n"); + this->LogToDebugFile("Value: %lx\n", opcode.begin_reg_cond.value.bit64); + break; + case CompareRegisterValueType_OtherRegister: + this->LogToDebugFile("Comp Type: Other Register\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode.begin_reg_cond.other_reg_index); + break; + case CompareRegisterValueType_MemoryRelAddr: + this->LogToDebugFile("Comp Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode.begin_reg_cond.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + this->LogToDebugFile("Comp Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode.begin_reg_cond.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode.begin_reg_cond.ofs_reg_index); + break; + case CompareRegisterValueType_RegisterRelAddr: + this->LogToDebugFile("Comp Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode.begin_reg_cond.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_RegisterOfsReg: + this->LogToDebugFile("Comp Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode.begin_reg_cond.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode.begin_reg_cond.ofs_reg_index); + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + this->LogToDebugFile("Opcode: Save or Restore Register\n"); + this->LogToDebugFile("Dst Idx: %x\n", opcode.save_restore_reg.dst_index); + this->LogToDebugFile("Src Idx: %x\n", opcode.save_restore_reg.src_index); + this->LogToDebugFile("Op Type: %d\n", opcode.save_restore_reg.op_type); + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + this->LogToDebugFile("Opcode: Save or Restore Register Mask\n"); + this->LogToDebugFile("Op Type: %d\n", opcode.save_restore_regmask.op_type); + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Act[%02x]: %d\n", i, + opcode.save_restore_regmask.should_operate[i]); + } + break; + case CheatVmOpcodeType_DebugLog: + this->LogToDebugFile("Opcode: Debug Log\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode.debug_log.bit_width); + this->LogToDebugFile("Log ID: %x\n", opcode.debug_log.log_id); + this->LogToDebugFile("Val Type: %x\n", opcode.debug_log.val_type); + switch (opcode.debug_log.val_type) { + case DebugLogValueType_RegisterValue: + this->LogToDebugFile("Val Type: Register Value\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode.debug_log.val_reg_index); + break; + case DebugLogValueType_MemoryRelAddr: + this->LogToDebugFile("Val Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode.debug_log.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + this->LogToDebugFile("Val Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode.debug_log.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode.debug_log.ofs_reg_index); + break; + case DebugLogValueType_RegisterRelAddr: + this->LogToDebugFile("Val Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode.debug_log.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode.debug_log.rel_address); + break; + case DebugLogValueType_RegisterOfsReg: + this->LogToDebugFile("Val Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode.debug_log.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode.debug_log.ofs_reg_index); + break; + } + default: + this->LogToDebugFile("Unknown opcode: %x\n", opcode.opcode); + break; + } +} + +DmntCheatVm::Callbacks::~Callbacks() = default; + +bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { + /* If we've ever seen a decode failure, return false. */ + bool valid = this->decode_success; + CheatVmOpcode opcode = {}; + SCOPE_EXIT({ + this->decode_success &= valid; + if (valid) { + out = opcode; + } + }); + + /* Helper function for getting instruction dwords. */ + auto GetNextDword = [&]() { + if (this->instruction_ptr >= this->num_opcodes) { + valid = false; + return static_cast(0); + } + return this->program[this->instruction_ptr++]; + }; + + /* Helper function for parsing a VmInt. */ + auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val = {0}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = (u8)first_dword; + break; + case 2: + val.bit16 = (u16)first_dword; + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); + break; + } + + return val; + }; + + /* Read opcode. */ + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF)); + if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) { + opcode.opcode = + (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); + } + if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { + opcode.opcode = + (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); + } + + /* detect condition start. */ + switch (opcode.opcode) { + case CheatVmOpcodeType_BeginConditionalBlock: + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: { + /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.store_static.bit_width = (first_dword >> 24) & 0xF; + opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.store_static.offset_register = ((first_dword >> 16) & 0xF); + opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width); + } break; + case CheatVmOpcodeType_BeginConditionalBlock: { + /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF; + opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.begin_cond.value = GetNextVmInt(opcode.store_static.bit_width); + } break; + case CheatVmOpcodeType_EndConditionalBlock: { + /* 20000000 */ + /* There's actually nothing left to process here! */ + } break; + case CheatVmOpcodeType_ControlLoop: { + /* 300R0000 VVVVVVVV */ + /* 310R0000 */ + /* Parse register, whether loop start or loop end. */ + opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); + + /* Read number of iters if loop start. */ + if (opcode.ctrl_loop.start_loop) { + opcode.ctrl_loop.num_iters = GetNextDword(); + } + } break; + case CheatVmOpcodeType_LoadRegisterStatic: { + /* 400R0000 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } break; + case CheatVmOpcodeType_LoadRegisterMemory: { + /* 5TMRI0AA AAAAAAAA */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF; + opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + } break; + case CheatVmOpcodeType_StoreStaticToAddress: { + /* 6T0RIor0 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.str_static.bit_width = (first_dword >> 24) & 0xF; + opcode.str_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } break; + case CheatVmOpcodeType_PerformArithmeticStatic: { + /* 7T0RC000 VVVVVVVV */ + /* Read additional words. */ + opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF); + opcode.perform_math_static.value = GetNextDword(); + } break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: { + /* 8kkkkkkk */ + /* Just parse the mask. */ + opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + } break; + case CheatVmOpcodeType_PerformArithmeticRegister: { + /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */ + opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF); + opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (opcode.perform_math_reg.has_immediate) { + opcode.perform_math_reg.src_reg_2_index = 0; + opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width); + } else { + opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + } break; + case CheatVmOpcodeType_StoreRegisterToAddress: { + /* ATSRIOxa (aaaaaaaa) */ + /* A = opcode 10 */ + /* T = bit width */ + /* S = src register index */ + /* R = address register index */ + /* I = 1 if increment address register, 0 if not increment address register */ + /* O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + + Relative Address */ + /* x = offset register (for offset type 1), memory type (for offset type 3) */ + /* a = relative address (for offset type 2+3) */ + opcode.str_register.bit_width = (first_dword >> 24) & 0xF; + opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF); + opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF)); + opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + case StoreRegisterOffsetType_Reg: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Imm: + opcode.str_register.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case StoreRegisterOffsetType_MemReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.str_register.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + default: + opcode.str_register.ofs_type = StoreRegisterOffsetType_None; + break; + } + } break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: { + /* C0TcSX## */ + /* C0TcS0Ma aaaaaaaa */ + /* C0TcS1Mr */ + /* C0TcS2Ra aaaaaaaa */ + /* C0TcS3Rr */ + /* C0TcS400 VVVVVVVV (VVVVVVVV) */ + /* C0TcS5X0 */ + /* C0 = opcode 0xC0 */ + /* T = bit width */ + /* c = condition type. */ + /* S = source register. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + * register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = static + * value, 5 = other register. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = other register. */ + /* V = value. */ + opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF); + + switch (opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width); + break; + case CompareRegisterValueType_OtherRegister: + opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType_MemoryRelAddr: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_MemoryOfsReg: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType_RegisterRelAddr: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_RegisterOfsReg: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + } + } break; + case CheatVmOpcodeType_SaveRestoreRegister: { + /* C10D0Sx0 */ + /* C1 = opcode 0xC1 */ + /* D = destination index. */ + /* S = source index. */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring + * a register. */ + /* NOTE: If we add more save slots later, current encoding is backwards compatible. */ + opcode.save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + opcode.save_restore_reg.src_index = (first_dword >> 8) & 0xF; + opcode.save_restore_reg.op_type = (SaveRestoreRegisterOpType)((first_dword >> 4) & 0xF); + } break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: { + /* C2x0XXXX */ + /* C2 = opcode 0xC2 */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. */ + /* X = 16-bit bitmask, bit i --> save or restore register i. */ + opcode.save_restore_regmask.op_type = + (SaveRestoreRegisterOpType)((first_dword >> 20) & 0xF); + for (size_t i = 0; i < NumRegisters; i++) { + opcode.save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + } + } break; + case CheatVmOpcodeType_DebugLog: { + /* FFFTIX## */ + /* FFFTI0Ma aaaaaaaa */ + /* FFFTI1Mr */ + /* FFFTI2Ra aaaaaaaa */ + /* FFFTI3Rr */ + /* FFFTI4X0 */ + /* FFF = opcode 0xFFF */ + /* T = bit width. */ + /* I = log id. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + * register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = register + * value. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = value register. */ + opcode.debug_log.bit_width = (first_dword >> 16) & 0xF; + opcode.debug_log.log_id = ((first_dword >> 12) & 0xF); + opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF); + + switch (opcode.debug_log.val_type) { + case DebugLogValueType_RegisterValue: + opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType_MemoryRelAddr: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_MemoryOfsReg: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType_RegisterRelAddr: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = + (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_RegisterOfsReg: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + } break; + case CheatVmOpcodeType_ExtendedWidth: + case CheatVmOpcodeType_DoubleExtendedWidth: + default: + /* Unrecognized instruction cannot be decoded. */ + valid = false; + break; + } + + /* End decoding. */ + return valid; +} + +void DmntCheatVm::SkipConditionalBlock() { + if (this->condition_depth > 0) { + /* We want to continue until we're out of the current block. */ + const size_t desired_depth = this->condition_depth - 1; + + CheatVmOpcode skip_opcode{}; + while (this->condition_depth > desired_depth && this->DecodeNextOpcode(skip_opcode)) { + /* Decode instructions until we see end of the current conditional block. */ + /* NOTE: This is broken in gateway's implementation. */ + /* Gateway currently checks for "0x2" instead of "0x20000000" */ + /* In addition, they do a linear scan instead of correctly decoding opcodes. */ + /* This causes issues if "0x2" appears as an immediate in the conditional block... */ + + /* We also support nesting of conditional blocks, and Gateway does not. */ + if (skip_opcode.begin_conditional_block) { + this->condition_depth++; + } else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) { + this->condition_depth--; + } + } + } else { + /* Skipping, but this->condition_depth = 0. */ + /* This is an error condition. */ + /* However, I don't actually believe it is possible for this to happen. */ + /* I guess we'll throw a fatal error here, so as to encourage me to fix the VM */ + /* in the event that someone triggers it? I don't know how you'd do that. */ + UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); + } +} + +u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + /* Invalid bit width -> return 0. */ + return 0; + } +} + +u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType_MainNso: + default: + return metadata.main_nso_extents.base + rel_address; + case MemoryAccessType_Heap: + return metadata.heap_extents.base + rel_address; + } +} + +void DmntCheatVm::ResetState() { + for (size_t i = 0; i < DmntCheatVm::NumRegisters; i++) { + this->registers[i] = 0; + this->saved_values[i] = 0; + this->loop_tops[i] = 0; + } + this->instruction_ptr = 0; + this->condition_depth = 0; + this->decode_success = true; +} + +bool DmntCheatVm::LoadProgram(const std::vector& entries) { + /* Reset opcode count. */ + this->num_opcodes = 0; + + for (size_t i = 0; i < entries.size(); i++) { + if (entries[i].enabled) { + /* Bounds check. */ + if (entries[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) { + this->num_opcodes = 0; + return false; + } + + for (size_t n = 0; n < entries[i].definition.num_opcodes; n++) { + this->program[this->num_opcodes++] = entries[i].definition.opcodes[n]; + } + } + } + + return true; +} + +void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { + CheatVmOpcode cur_opcode{}; + + /* Get Keys down. */ + u64 kDown = callbacks->HidKeysDown(); + + this->LogToDebugFile("Started VM execution.\n"); + this->LogToDebugFile("Main NSO: %012lx\n", metadata.main_nso_extents.base); + this->LogToDebugFile("Heap: %012lx\n", metadata.main_nso_extents.base); + this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF)); + + /* Clear VM state. */ + this->ResetState(); + + /* Loop until program finishes. */ + while (this->DecodeNextOpcode(cur_opcode)) { + this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr); + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]); + } + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]); + } + this->LogOpcode(cur_opcode); + + /* Increment conditional depth, if relevant. */ + if (cur_opcode.begin_conditional_block) { + this->condition_depth++; + } + + switch (cur_opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: { + /* Calculate address, write value to memory. */ + u64 dst_address = GetCheatProcessAddress( + metadata, cur_opcode.store_static.mem_type, + cur_opcode.store_static.rel_address + + this->registers[cur_opcode.store_static.offset_register]); + u64 dst_value = + GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width); + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.store_static.bit_width); + break; + } + } break; + case CheatVmOpcodeType_BeginConditionalBlock: { + /* Read value from memory. */ + u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, + cur_opcode.begin_cond.rel_address); + u64 src_value = 0; + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, &src_value, cur_opcode.begin_cond.bit_width); + break; + } + /* Check against condition. */ + u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width); + bool cond_met = false; + switch (cur_opcode.begin_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(); + } + } break; + case CheatVmOpcodeType_EndConditionalBlock: + /* Decrement the condition depth. */ + /* We will assume, graciously, that mismatched conditional block ends are a nop. */ + if (this->condition_depth > 0) { + this->condition_depth--; + } + break; + case CheatVmOpcodeType_ControlLoop: + if (cur_opcode.ctrl_loop.start_loop) { + /* Start a loop. */ + this->registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters; + this->loop_tops[cur_opcode.ctrl_loop.reg_index] = this->instruction_ptr; + } else { + /* End a loop. */ + this->registers[cur_opcode.ctrl_loop.reg_index]--; + if (this->registers[cur_opcode.ctrl_loop.reg_index] != 0) { + this->instruction_ptr = this->loop_tops[cur_opcode.ctrl_loop.reg_index]; + } + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + /* Set a register to a static value. */ + this->registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value; + break; + case CheatVmOpcodeType_LoadRegisterMemory: { + /* Choose source address. */ + u64 src_address; + if (cur_opcode.ldr_memory.load_from_reg) { + src_address = this->registers[cur_opcode.ldr_memory.reg_index] + + cur_opcode.ldr_memory.rel_address; + } else { + src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, + cur_opcode.ldr_memory.rel_address); + } + /* Read into register. Gateway only reads on valid bitwidth. */ + switch (cur_opcode.ldr_memory.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, + &this->registers[cur_opcode.ldr_memory.reg_index], + cur_opcode.ldr_memory.bit_width); + break; + } + } break; + case CheatVmOpcodeType_StoreStaticToAddress: { + /* Calculate address. */ + u64 dst_address = this->registers[cur_opcode.str_static.reg_index]; + u64 dst_value = cur_opcode.str_static.value; + if (cur_opcode.str_static.add_offset_reg) { + dst_address += this->registers[cur_opcode.str_static.offset_reg_index]; + } + /* Write value to memory. Gateway only writes on valid bitwidth. */ + switch (cur_opcode.str_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_static.bit_width); + break; + } + /* Increment register if relevant. */ + if (cur_opcode.str_static.increment_reg) { + this->registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width; + } + } break; + case CheatVmOpcodeType_PerformArithmeticStatic: { + /* Do requested math. */ + switch (cur_opcode.perform_math_static.math_type) { + case RegisterArithmeticType_Addition: + this->registers[cur_opcode.perform_math_static.reg_index] += + (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Subtraction: + this->registers[cur_opcode.perform_math_static.reg_index] -= + (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Multiplication: + this->registers[cur_opcode.perform_math_static.reg_index] *= + (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_LeftShift: + this->registers[cur_opcode.perform_math_static.reg_index] <<= + (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_RightShift: + this->registers[cur_opcode.perform_math_static.reg_index] >>= + (u64)cur_opcode.perform_math_static.value; + break; + default: + /* Do not handle extensions here. */ + break; + } + /* Apply bit width. */ + switch (cur_opcode.perform_math_static.bit_width) { + case 1: + this->registers[cur_opcode.perform_math_static.reg_index] = + static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 2: + this->registers[cur_opcode.perform_math_static.reg_index] = + static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 4: + this->registers[cur_opcode.perform_math_static.reg_index] = + static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 8: + this->registers[cur_opcode.perform_math_static.reg_index] = + static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + } + } break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + /* Check for keypress. */ + if ((cur_opcode.begin_keypress_cond.key_mask & kDown) != + cur_opcode.begin_keypress_cond.key_mask) { + /* Keys not pressed. Skip conditional block. */ + this->SkipConditionalBlock(); + } + break; + case CheatVmOpcodeType_PerformArithmeticRegister: { + const u64 operand_1_value = + this->registers[cur_opcode.perform_math_reg.src_reg_1_index]; + const u64 operand_2_value = + cur_opcode.perform_math_reg.has_immediate + ? GetVmInt(cur_opcode.perform_math_reg.value, + cur_opcode.perform_math_reg.bit_width) + : this->registers[cur_opcode.perform_math_reg.src_reg_2_index]; + + u64 res_val = 0; + /* Do requested math. */ + switch (cur_opcode.perform_math_reg.math_type) { + case RegisterArithmeticType_Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType_Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType_Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType_LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType_RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType_LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType_LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType_LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType_LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType_None: + res_val = operand_1_value; + break; + } + + /* Apply bit width. */ + switch (cur_opcode.perform_math_reg.bit_width) { + case 1: + res_val = static_cast(res_val); + break; + case 2: + res_val = static_cast(res_val); + break; + case 4: + res_val = static_cast(res_val); + break; + case 8: + res_val = static_cast(res_val); + break; + } + + /* Save to register. */ + this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val; + } break; + case CheatVmOpcodeType_StoreRegisterToAddress: { + /* Calculate address. */ + u64 dst_value = this->registers[cur_opcode.str_register.str_reg_index]; + u64 dst_address = this->registers[cur_opcode.str_register.addr_reg_index]; + switch (cur_opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Reg: + dst_address += this->registers[cur_opcode.str_register.ofs_reg_index]; + break; + case StoreRegisterOffsetType_Imm: + dst_address += cur_opcode.str_register.rel_address; + break; + case StoreRegisterOffsetType_MemReg: + dst_address = + GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, + this->registers[cur_opcode.str_register.addr_reg_index]); + break; + case StoreRegisterOffsetType_MemImm: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, + cur_opcode.str_register.rel_address); + break; + case StoreRegisterOffsetType_MemImmReg: + dst_address = + GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, + this->registers[cur_opcode.str_register.addr_reg_index] + + cur_opcode.str_register.rel_address); + break; + } + + /* Write value to memory. Write only on valid bitwidth. */ + switch (cur_opcode.str_register.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_register.bit_width); + break; + } + + /* Increment register if relevant. */ + if (cur_opcode.str_register.increment_reg) { + this->registers[cur_opcode.str_register.addr_reg_index] += + cur_opcode.str_register.bit_width; + } + } break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: { + /* Get value from register. */ + u64 src_value = 0; + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + src_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = + static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + + /* Read value from memory. */ + u64 cond_value = 0; + if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) { + cond_value = + GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width); + } else if (cur_opcode.begin_reg_cond.comp_type == + CompareRegisterValueType_OtherRegister) { + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + cond_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul); + break; + case 2: + cond_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast( + this->registers[cur_opcode.begin_reg_cond.other_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (cur_opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_MemoryRelAddr: + cond_address = + GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, + cur_opcode.begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + cond_address = GetCheatProcessAddress( + metadata, cur_opcode.begin_reg_cond.mem_type, + this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]); + break; + case CompareRegisterValueType_RegisterRelAddr: + cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + + cur_opcode.begin_reg_cond.rel_address; + break; + case CompareRegisterValueType_RegisterOfsReg: + cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + + this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(cond_address, &cond_value, + cur_opcode.begin_reg_cond.bit_width); + break; + } + } + + /* Check against condition. */ + bool cond_met = false; + switch (cur_opcode.begin_reg_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(); + } + } break; + case CheatVmOpcodeType_SaveRestoreRegister: + /* Save or restore a register. */ + switch (cur_opcode.save_restore_reg.op_type) { + case SaveRestoreRegisterOpType_ClearRegs: + this->registers[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_ClearSaved: + this->saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + this->saved_values[cur_opcode.save_restore_reg.dst_index] = + this->registers[cur_opcode.save_restore_reg.src_index]; + break; + case SaveRestoreRegisterOpType_Restore: + default: + this->registers[cur_opcode.save_restore_reg.dst_index] = + this->saved_values[cur_opcode.save_restore_reg.src_index]; + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + /* Save or restore register mask. */ + u64* src; + u64* dst; + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_Save: + src = this->registers.data(); + dst = this->saved_values.data(); + break; + case SaveRestoreRegisterOpType_ClearRegs: + case SaveRestoreRegisterOpType_Restore: + default: + src = this->registers.data(); + dst = this->saved_values.data(); + break; + } + for (size_t i = 0; i < NumRegisters; i++) { + if (cur_opcode.save_restore_regmask.should_operate[i]) { + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + case SaveRestoreRegisterOpType_Restore: + default: + dst[i] = src[i]; + break; + } + } + } + break; + case CheatVmOpcodeType_DebugLog: { + /* Read value from memory. */ + u64 log_value = 0; + if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { + switch (cur_opcode.debug_log.bit_width) { + case 1: + log_value = static_cast( + this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast( + this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = static_cast( + this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast( + this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (cur_opcode.debug_log.val_type) { + case DebugLogValueType_MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, + cur_opcode.debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + val_address = + GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, + this->registers[cur_opcode.debug_log.ofs_reg_index]); + break; + case DebugLogValueType_RegisterRelAddr: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + + cur_opcode.debug_log.rel_address; + break; + case DebugLogValueType_RegisterOfsReg: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + + this->registers[cur_opcode.debug_log.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.debug_log.bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(val_address, &log_value, cur_opcode.debug_log.bit_width); + break; + } + } + + /* Log value. */ + this->DebugLog(cur_opcode.debug_log.log_id, log_value); + } break; + default: + /* By default, we do a no-op. */ + break; + } + } +} + +} // 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 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#pragma once + +#include +#include +#include "common/common_types.h" +#include "core/memory/dmnt_cheat_types.h" + +namespace Memory { + +enum CheatVmOpcodeType : u32 { + CheatVmOpcodeType_StoreStatic = 0, + CheatVmOpcodeType_BeginConditionalBlock = 1, + CheatVmOpcodeType_EndConditionalBlock = 2, + CheatVmOpcodeType_ControlLoop = 3, + CheatVmOpcodeType_LoadRegisterStatic = 4, + CheatVmOpcodeType_LoadRegisterMemory = 5, + CheatVmOpcodeType_StoreStaticToAddress = 6, + CheatVmOpcodeType_PerformArithmeticStatic = 7, + CheatVmOpcodeType_BeginKeypressConditionalBlock = 8, + + /* These are not implemented by Gateway's VM. */ + CheatVmOpcodeType_PerformArithmeticRegister = 9, + CheatVmOpcodeType_StoreRegisterToAddress = 10, + CheatVmOpcodeType_Reserved11 = 11, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_ExtendedWidth = 12, + + /* Extended width opcodes. */ + CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, + CheatVmOpcodeType_SaveRestoreRegister = 0xC1, + CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, + + /* Double-extended width opcodes. */ + CheatVmOpcodeType_DebugLog = 0xFFF, +}; + +enum MemoryAccessType : u32 { + MemoryAccessType_MainNso = 0, + MemoryAccessType_Heap = 1, +}; + +enum ConditionalComparisonType : u32 { + ConditionalComparisonType_GT = 1, + ConditionalComparisonType_GE = 2, + ConditionalComparisonType_LT = 3, + ConditionalComparisonType_LE = 4, + ConditionalComparisonType_EQ = 5, + ConditionalComparisonType_NE = 6, +}; + +enum RegisterArithmeticType : u32 { + RegisterArithmeticType_Addition = 0, + RegisterArithmeticType_Subtraction = 1, + RegisterArithmeticType_Multiplication = 2, + RegisterArithmeticType_LeftShift = 3, + RegisterArithmeticType_RightShift = 4, + + /* These are not supported by Gateway's VM. */ + RegisterArithmeticType_LogicalAnd = 5, + RegisterArithmeticType_LogicalOr = 6, + RegisterArithmeticType_LogicalNot = 7, + RegisterArithmeticType_LogicalXor = 8, + + RegisterArithmeticType_None = 9, +}; + +enum StoreRegisterOffsetType : u32 { + StoreRegisterOffsetType_None = 0, + StoreRegisterOffsetType_Reg = 1, + StoreRegisterOffsetType_Imm = 2, + StoreRegisterOffsetType_MemReg = 3, + StoreRegisterOffsetType_MemImm = 4, + StoreRegisterOffsetType_MemImmReg = 5, +}; + +enum CompareRegisterValueType : u32 { + CompareRegisterValueType_MemoryRelAddr = 0, + CompareRegisterValueType_MemoryOfsReg = 1, + CompareRegisterValueType_RegisterRelAddr = 2, + CompareRegisterValueType_RegisterOfsReg = 3, + CompareRegisterValueType_StaticValue = 4, + CompareRegisterValueType_OtherRegister = 5, +}; + +enum SaveRestoreRegisterOpType : u32 { + SaveRestoreRegisterOpType_Restore = 0, + SaveRestoreRegisterOpType_Save = 1, + SaveRestoreRegisterOpType_ClearSaved = 2, + SaveRestoreRegisterOpType_ClearRegs = 3, +}; + +enum DebugLogValueType : u32 { + DebugLogValueType_MemoryRelAddr = 0, + DebugLogValueType_MemoryOfsReg = 1, + DebugLogValueType_RegisterRelAddr = 2, + DebugLogValueType_RegisterOfsReg = 3, + DebugLogValueType_RegisterValue = 4, +}; + +union VmInt { + u8 bit8; + u16 bit16; + u32 bit32; + u64 bit64; +}; + +struct StoreStaticOpcode { + u32 bit_width; + MemoryAccessType mem_type; + u32 offset_register; + u64 rel_address; + VmInt value; +}; + +struct BeginConditionalOpcode { + u32 bit_width; + MemoryAccessType mem_type; + ConditionalComparisonType cond_type; + u64 rel_address; + VmInt value; +}; + +struct EndConditionalOpcode {}; + +struct ControlLoopOpcode { + bool start_loop; + u32 reg_index; + u32 num_iters; +}; + +struct LoadRegisterStaticOpcode { + u32 reg_index; + u64 value; +}; + +struct LoadRegisterMemoryOpcode { + u32 bit_width; + MemoryAccessType mem_type; + u32 reg_index; + bool load_from_reg; + u64 rel_address; +}; + +struct StoreStaticToAddressOpcode { + u32 bit_width; + u32 reg_index; + bool increment_reg; + bool add_offset_reg; + u32 offset_reg_index; + u64 value; +}; + +struct PerformArithmeticStaticOpcode { + u32 bit_width; + u32 reg_index; + RegisterArithmeticType math_type; + u32 value; +}; + +struct BeginKeypressConditionalOpcode { + u32 key_mask; +}; + +struct PerformArithmeticRegisterOpcode { + u32 bit_width; + RegisterArithmeticType math_type; + u32 dst_reg_index; + u32 src_reg_1_index; + u32 src_reg_2_index; + bool has_immediate; + VmInt value; +}; + +struct StoreRegisterToAddressOpcode { + u32 bit_width; + u32 str_reg_index; + u32 addr_reg_index; + bool increment_reg; + StoreRegisterOffsetType ofs_type; + MemoryAccessType mem_type; + u32 ofs_reg_index; + u64 rel_address; +}; + +struct BeginRegisterConditionalOpcode { + u32 bit_width; + ConditionalComparisonType cond_type; + u32 val_reg_index; + CompareRegisterValueType comp_type; + MemoryAccessType mem_type; + u32 addr_reg_index; + u32 other_reg_index; + u32 ofs_reg_index; + u64 rel_address; + VmInt value; +}; + +struct SaveRestoreRegisterOpcode { + u32 dst_index; + u32 src_index; + SaveRestoreRegisterOpType op_type; +}; + +struct SaveRestoreRegisterMaskOpcode { + SaveRestoreRegisterOpType op_type; + std::array should_operate; +}; + +struct DebugLogOpcode { + u32 bit_width; + u32 log_id; + DebugLogValueType val_type; + MemoryAccessType mem_type; + u32 addr_reg_index; + u32 val_reg_index; + u32 ofs_reg_index; + u64 rel_address; +}; + +struct CheatVmOpcode { + CheatVmOpcodeType opcode; + bool begin_conditional_block; + union { + StoreStaticOpcode store_static; + BeginConditionalOpcode begin_cond; + EndConditionalOpcode end_cond; + ControlLoopOpcode ctrl_loop; + LoadRegisterStaticOpcode ldr_static; + LoadRegisterMemoryOpcode ldr_memory; + StoreStaticToAddressOpcode str_static; + PerformArithmeticStaticOpcode perform_math_static; + BeginKeypressConditionalOpcode begin_keypress_cond; + PerformArithmeticRegisterOpcode perform_math_reg; + StoreRegisterToAddressOpcode str_register; + BeginRegisterConditionalOpcode begin_reg_cond; + SaveRestoreRegisterOpcode save_restore_reg; + SaveRestoreRegisterMaskOpcode save_restore_regmask; + DebugLogOpcode debug_log; + }; +}; + +class DmntCheatVm { +public: + /// Helper Type for DmntCheatVm <=> yuzu Interface + class Callbacks { + public: + virtual ~Callbacks(); + + virtual void MemoryRead(VAddr address, void* data, u64 size) = 0; + virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0; + + virtual u64 HidKeysDown() = 0; + + virtual void DebugLog(u8 id, u64 value) = 0; + virtual void CommandLog(std::string_view data) = 0; + }; + + constexpr static size_t MaximumProgramOpcodeCount = 0x400; + constexpr static size_t NumRegisters = 0x10; + +private: + std::unique_ptr callbacks; + + size_t num_opcodes = 0; + size_t instruction_ptr = 0; + size_t condition_depth = 0; + bool decode_success = false; + std::array program{}; + std::array registers{}; + std::array saved_values{}; + std::array loop_tops{}; + +private: + bool DecodeNextOpcode(CheatVmOpcode& out); + void SkipConditionalBlock(); + void ResetState(); + + /* For implementing the DebugLog opcode. */ + void DebugLog(u32 log_id, u64 value); + + /* For debugging. These will be IFDEF'd out normally. */ + template + void LogToDebugFile(const char* format, const Args&... args) { + callbacks->CommandLog(fmt::sprintf(format, args...)); + } + + void LogOpcode(const CheatVmOpcode& opcode); + + static u64 GetVmInt(VmInt value, u32 bit_width); + static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address); + +public: + DmntCheatVm(std::unique_ptr callbacks) : callbacks(std::move(callbacks)) {} + + size_t GetProgramSize() { + return this->num_opcodes; + } + + bool LoadProgram(const std::vector& cheats); + void Execute(const CheatProcessMetadata& metadata); +}; + +}; // namespace Memory