From 470466b31b3ddc198482041e34e78095d467054f Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:32:46 -0400 Subject: [PATCH 1/9] log: Add logging class for Cheat Engine This is better than just using something like Common.Filesystem or Common.Memory --- src/common/logging/backend.cpp | 1 + src/common/logging/log.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a03179520..1111cfbad 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) { CLS(Input) \ CLS(Network) \ CLS(Loader) \ + CLS(CheatEngine) \ CLS(Crypto) \ CLS(WebService) diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 8ed6d5050..259708116 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -117,6 +117,7 @@ enum class Class : ClassType { Audio_DSP, ///< The HLE implementation of the DSP Audio_Sink, ///< Emulator audio output backend Loader, ///< ROM loader + CheatEngine, ///< Memory manipulation and engine VM functions Crypto, ///< Cryptographic engine/functions Input, ///< Input emulation Network, ///< Network emulation From 12aa127df3826857149bfc4b787cfb7df3fdcafe Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:34:02 -0400 Subject: [PATCH 2/9] 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 From 7d41c1f52390abb47e67d3fc43310e9d87fbd862 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:35:03 -0400 Subject: [PATCH 3/9] cheat_engine: Move to memory and strip VM This is to go with the Atmosphere VM port, now it just contains the callbacks needed for the interface between DmntCheatVm and yuzu, along with the cheat parsers. --- src/core/CMakeLists.txt | 7 +- src/core/file_sys/cheat_engine.cpp | 492 ----------------------------- src/core/file_sys/cheat_engine.h | 234 -------------- src/core/memory/cheat_engine.cpp | 234 ++++++++++++++ src/core/memory/cheat_engine.h | 86 +++++ 5 files changed, 325 insertions(+), 728 deletions(-) delete mode 100644 src/core/file_sys/cheat_engine.cpp delete mode 100644 src/core/file_sys/cheat_engine.h create mode 100644 src/core/memory/cheat_engine.cpp create mode 100644 src/core/memory/cheat_engine.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 877a9e353..a6b56c9c6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -33,8 +33,6 @@ add_library(core STATIC file_sys/bis_factory.h file_sys/card_image.cpp file_sys/card_image.h - file_sys/cheat_engine.cpp - file_sys/cheat_engine.h file_sys/content_archive.cpp file_sys/content_archive.h file_sys/control_metadata.cpp @@ -477,6 +475,11 @@ add_library(core STATIC loader/nsp.h loader/xci.cpp loader/xci.h + memory/cheat_engine.cpp + memory/cheat_engine.h + memory/dmnt_cheat_types.h + memory/dmnt_cheat_vm.cpp + memory/dmnt_cheat_vm.h memory.cpp memory.h memory_setup.h diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp deleted file mode 100644 index b06c2f20a..000000000 --- a/src/core/file_sys/cheat_engine.cpp +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include "common/hex_util.h" -#include "common/microprofile.h" -#include "common/swap.h" -#include "core/core.h" -#include "core/core_timing.h" -#include "core/core_timing_util.h" -#include "core/file_sys/cheat_engine.h" -#include "core/hle/kernel/process.h" -#include "core/hle/service/hid/controllers/npad.h" -#include "core/hle/service/hid/hid.h" -#include "core/hle/service/sm/sm.h" - -namespace FileSys { - -constexpr s64 CHEAT_ENGINE_TICKS = static_cast(Core::Timing::BASE_CLOCK_RATE / 60); -constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; - -u64 Cheat::Address() const { - u64 out; - std::memcpy(&out, raw.data(), sizeof(u64)); - return Common::swap64(out) & 0xFFFFFFFFFF; -} - -u64 Cheat::ValueWidth(u64 offset) const { - return Value(offset, width); -} - -u64 Cheat::Value(u64 offset, u64 width) const { - u64 out; - std::memcpy(&out, raw.data() + offset, sizeof(u64)); - out = Common::swap64(out); - if (width == 8) - return out; - return out & ((1ull << (width * CHAR_BIT)) - 1); -} - -u32 Cheat::KeypadValue() const { - u32 out; - std::memcpy(&out, raw.data(), sizeof(u32)); - return Common::swap32(out) & 0x0FFFFFFF; -} - -void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, - VAddr heap_end, MemoryWriter writer, MemoryReader reader) { - this->main_region_begin = main_begin; - this->main_region_end = main_end; - this->heap_region_begin = heap_begin; - this->heap_region_end = heap_end; - this->writer = writer; - this->reader = reader; -} - -MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); - -void CheatList::Execute() { - MICROPROFILE_SCOPE(Cheat_Engine); - - std::fill(scratch.begin(), scratch.end(), 0); - in_standard = false; - for (std::size_t i = 0; i < master_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); - current_block = i; - ExecuteBlock(master_list[i].second); - } - - in_standard = true; - for (std::size_t i = 0; i < standard_list.size(); ++i) { - LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); - current_block = i; - ExecuteBlock(standard_list[i].second); - } -} - -CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) - : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} - -bool CheatList::EvaluateConditional(const Cheat& cheat) const { - using ComparisonFunction = bool (*)(u64, u64); - constexpr std::array comparison_functions{ - [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, - [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, - [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, - }; - - if (cheat.type == CodeType::ConditionalInput) { - const auto applet_resource = - system->ServiceManager().GetService("hid")->GetAppletResource(); - if (applet_resource == nullptr) { - LOG_WARNING( - Common_Filesystem, - "Attempted to evaluate input conditional, but applet resource is not initialized!"); - return false; - } - - const auto press_state = - applet_resource - ->GetController(Service::HID::HidController::NPad) - .GetAndResetPressState(); - return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; - } - - ASSERT(cheat.type == CodeType::Conditional); - - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - ASSERT(static_cast(cheat.comparison_op.Value()) < 6); - auto* function = comparison_functions[static_cast(cheat.comparison_op.Value())]; - const auto addr = cheat.Address() + offset; - - return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); -} - -void CheatList::ProcessBlockPairs(const Block& block) { - block_pairs.clear(); - - u64 scope = 0; - std::map pairs; - - for (std::size_t i = 0; i < block.size(); ++i) { - const auto& cheat = block[i]; - - switch (cheat.type) { - case CodeType::Conditional: - case CodeType::ConditionalInput: - pairs.insert_or_assign(scope, i); - ++scope; - break; - case CodeType::EndConditional: { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - break; - } - case CodeType::Loop: { - if (cheat.end_of_loop) { - --scope; - const auto idx = pairs.at(scope); - block_pairs.insert_or_assign(idx, i); - } else { - pairs.insert_or_assign(scope, i); - ++scope; - } - break; - } - } - } -} - -void CheatList::WriteImmediate(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = cheat.Address() + offset + register_3; - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, - cheat.Value(8, cheat.width)); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); -} - -void CheatList::BeginConditional(const Cheat& cheat) { - if (EvaluateConditional(cheat)) { - return; - } - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -void CheatList::EndConditional(const Cheat& cheat) { - LOG_DEBUG(Common_Filesystem, "Ending conditional block."); -} - -void CheatList::Loop(const Cheat& cheat) { - if (cheat.end_of_loop.Value()) - ASSERT(!cheat.end_of_loop.Value()); - - auto& register_3 = scratch.at(cheat.register_3); - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - ASSERT(iter->first < iter->second); - - const s32 initial_value = static_cast(cheat.Value(4, sizeof(s32))); - for (s32 i = initial_value; i >= 0; --i) { - register_3 = static_cast(i); - for (std::size_t c = iter->first + 1; c < iter->second; ++c) { - current_index = c; - ExecuteSingleCheat( - (in_standard ? standard_list : master_list)[current_block].second[c]); - } - } - - current_index = iter->second; -} - -void CheatList::LoadImmediate(const Cheat& cheat) { - auto& register_3 = scratch.at(cheat.register_3); - - LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, - cheat.Value(4, 8)); - register_3 = cheat.Value(4, 8); -} - -void CheatList::LoadIndexed(const Cheat& cheat) { - const auto offset = - cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; - auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); - LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", - cheat.register_3, addr); - register_3 = reader(cheat.width, SanitizeAddress(addr)); -} - -void CheatList::StoreIndexed(const Cheat& cheat) { - const auto& register_3 = scratch.at(cheat.register_3); - - const auto addr = - register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); - LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", - cheat.Value(4, cheat.width), addr); - writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); -} - -void CheatList::RegisterArithmetic(const Cheat& cheat) { - using ArithmeticFunction = u64 (*)(u64, u64); - constexpr std::array arithmetic_functions{ - [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, - [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, - [](u64 a, u64 b) { return a >> b; }, - }; - - using ArithmeticOverflowCheck = bool (*)(u64, u64); - constexpr std::array arithmetic_overflow_checks{ - [](u64 a, u64 b) { return a > (std::numeric_limits::max() - b); }, // a + b - [](u64 a, u64 b) { return a > (std::numeric_limits::max() + b); }, // a - b - [](u64 a, u64 b) { return a > (std::numeric_limits::max() / b); }, // a * b - [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b - [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b - }; - - static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), - "Missing or have extra arithmetic overflow checks compared to functions!"); - - auto& register_3 = scratch.at(cheat.register_3); - - ASSERT(static_cast(cheat.arithmetic_op.Value()) < 5); - auto* function = arithmetic_functions[static_cast(cheat.arithmetic_op.Value())]; - auto* overflow_function = - arithmetic_overflow_checks[static_cast(cheat.arithmetic_op.Value())]; - LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", - cheat.register_3, cheat.ValueWidth(4)); - - if (overflow_function(register_3, cheat.ValueWidth(4))) { - LOG_WARNING(Common_Filesystem, - "overflow will occur when performing arithmetic operation={:02X} with operands " - "a={:016X}, b={:016X}!", - static_cast(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); - } - - register_3 = function(register_3, cheat.ValueWidth(4)); -} - -void CheatList::BeginConditionalInput(const Cheat& cheat) { - if (EvaluateConditional(cheat)) - return; - - const auto iter = block_pairs.find(current_index); - ASSERT(iter != block_pairs.end()); - current_index = iter->second - 1; -} - -VAddr CheatList::SanitizeAddress(VAddr in) const { - if ((in < main_region_begin || in >= main_region_end) && - (in < heap_region_begin || in >= heap_region_end)) { - LOG_ERROR(Common_Filesystem, - "Cheat attempting to access memory at invalid address={:016X}, if this persists, " - "the cheat may be incorrect. However, this may be normal early in execution if " - "the game has not properly set up yet.", - in); - return 0; ///< Invalid addresses will hard crash - } - - return in; -} - -void CheatList::ExecuteSingleCheat(const Cheat& cheat) { - using CheatOperationFunction = void (CheatList::*)(const Cheat&); - constexpr std::array cheat_operation_functions{ - &CheatList::WriteImmediate, &CheatList::BeginConditional, - &CheatList::EndConditional, &CheatList::Loop, - &CheatList::LoadImmediate, &CheatList::LoadIndexed, - &CheatList::StoreIndexed, &CheatList::RegisterArithmetic, - &CheatList::BeginConditionalInput, - }; - - const auto index = static_cast(cheat.type.Value()); - ASSERT(index < sizeof(cheat_operation_functions)); - const auto op = cheat_operation_functions[index]; - (this->*op)(cheat); -} - -void CheatList::ExecuteBlock(const Block& block) { - encountered_loops.clear(); - - ProcessBlockPairs(block); - for (std::size_t i = 0; i < block.size(); ++i) { - current_index = i; - ExecuteSingleCheat(block[i]); - i = current_index; - } -} - -CheatParser::~CheatParser() = default; - -CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const { - return {system, std::move(master), std::move(standard)}; -} - -TextCheatParser::~TextCheatParser() = default; - -CheatList TextCheatParser::Parse(const Core::System& system, const std::vector& data) const { - std::stringstream ss; - ss.write(reinterpret_cast(data.data()), data.size()); - - std::vector lines; - std::string stream_line; - while (std::getline(ss, stream_line)) { - // Remove a trailing \r - if (!stream_line.empty() && stream_line.back() == '\r') - stream_line.pop_back(); - lines.push_back(std::move(stream_line)); - } - - CheatList::ProgramSegment master_list; - CheatList::ProgramSegment standard_list; - - for (std::size_t i = 0; i < lines.size(); ++i) { - auto line = lines[i]; - - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - const auto master = line[0] == '{'; - const auto begin = master ? line.find('{') : line.find('['); - const auto end = master ? line.rfind('}') : line.rfind(']'); - - ASSERT(begin != std::string::npos && end != std::string::npos); - - const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; - CheatList::Block block{}; - - while (i < lines.size() - 1) { - line = lines[++i]; - if (!line.empty() && (line[0] == '[' || line[0] == '{')) { - --i; - break; - } - - if (line.size() < 8) - continue; - - Cheat out{}; - out.raw = ParseSingleLineCheat(line); - block.push_back(out); - } - - (master ? master_list : standard_list).emplace_back(patch_name, block); - } - } - - return MakeCheatList(system, master_list, standard_list); -} - -std::array TextCheatParser::ParseSingleLineCheat(const std::string& line) const { - std::array out{}; - - if (line.size() < 8) - return out; - - const auto word1 = Common::HexStringToArray(std::string_view{line.data(), 8}); - std::memcpy(out.data(), word1.data(), sizeof(u32)); - - if (line.size() < 17 || line[8] != ' ') - return out; - - const auto word2 = Common::HexStringToArray(std::string_view{line.data() + 9, 8}); - std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); - - if (line.size() < 26 || line[17] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast((out[0] & 0xF0) >> 4); - if (type == CodeType::Loop || type == CodeType::LoadImmediate || - type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { - std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); - std::memset(out.data() + 4, 0, sizeof(u32)); - } - - return out; - } - - const auto word3 = Common::HexStringToArray(std::string_view{line.data() + 18, 8}); - std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); - - if (line.size() < 35 || line[26] != ' ') { - // Perform shifting in case value is truncated early. - const auto type = static_cast((out[0] & 0xF0) >> 4); - if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { - std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); - std::memset(out.data() + 8, 0, sizeof(u32)); - } - - return out; - } - - const auto word4 = Common::HexStringToArray(std::string_view{line.data() + 27, 8}); - std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); - - return out; -} - -namespace { -u64 MemoryReadImpl(u32 width, VAddr addr) { - switch (width) { - case 1: - return Memory::Read8(addr); - case 2: - return Memory::Read16(addr); - case 4: - return Memory::Read32(addr); - case 8: - return Memory::Read64(addr); - default: - UNREACHABLE(); - return 0; - } -} - -void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { - switch (width) { - case 1: - Memory::Write8(addr, static_cast(value)); - break; - case 2: - Memory::Write16(addr, static_cast(value)); - break; - case 4: - Memory::Write32(addr, static_cast(value)); - break; - case 8: - Memory::Write64(addr, value); - break; - default: - UNREACHABLE(); - } -} -} // Anonymous namespace - -CheatEngine::CheatEngine(Core::System& system, std::vector cheats_, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) - : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { - event = core_timing.RegisterEvent( - "CheatEngine::FrameCallback::" + build_id, - [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); - - const auto& vm_manager = system.CurrentProcess()->VMManager(); - for (auto& list : this->cheats) { - list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), - code_region_end, vm_manager.GetHeapRegionEndAddress(), - &MemoryWriteImpl, &MemoryReadImpl); - } -} - -CheatEngine::~CheatEngine() { - core_timing.UnscheduleEvent(event, 0); -} - -void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { - for (auto& list : cheats) { - list.Execute(); - } - - core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); -} - -} // namespace FileSys diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h deleted file mode 100644 index ac22a82cb..000000000 --- a/src/core/file_sys/cheat_engine.h +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include -#include -#include -#include "common/bit_field.h" -#include "common/common_types.h" - -namespace Core { -class System; -} - -namespace Core::Timing { -class CoreTiming; -struct EventType; -} // namespace Core::Timing - -namespace FileSys { - -enum class CodeType : u32 { - // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Writes a T sized value Y to the address A added to the value of register R in memory domain M - WriteImmediate = 0, - - // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY - // Compares the T sized value Y to the value at address A in memory domain M using the - // conditional function C. If success, continues execution. If failure, jumps to the matching - // EndConditional statement. - Conditional = 1, - - // 20000000 - // Terminates a Conditional or ConditionalInput block. - EndConditional = 2, - - // 300R0000 VVVVVVVV - // Starts looping V times, storing the current count in register R. - // Loop block is terminated with a matching 310R0000. - Loop = 3, - - // 400R0000 VVVVVVVV VVVVVVVV - // Sets the value of register R to the value V. - LoadImmediate = 4, - - // 5TMRI0AA AAAAAAAA - // Sets the value of register R to the value of width T at address A in memory domain M, with - // the current value of R added to the address if I == 1. - LoadIndexed = 5, - - // 6T0RIFG0 VVVVVVVV VVVVVVVV - // Writes the value V of width T to the memory address stored in register R. Adds the value of - // register G to the final calculation if F is nonzero. Increments the value of register R by T - // after operation if I is nonzero. - StoreIndexed = 6, - - // 7T0RA000 VVVVVVVV - // Performs the arithmetic operation A on the value in register R and the value V of width T, - // storing the result in register R. - RegisterArithmetic = 7, - - // 8KKKKKKK - // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, - // execution continues. If none are, execution skips to the next EndConditional command. - ConditionalInput = 8, -}; - -enum class MemoryType : u32 { - // Addressed relative to start of main NSO - MainNSO = 0, - - // Addressed relative to start of heap - Heap = 1, -}; - -enum class ArithmeticOp : u32 { - Add = 0, - Sub = 1, - Mult = 2, - LShift = 3, - RShift = 4, -}; - -enum class ComparisonOp : u32 { - GreaterThan = 1, - GreaterThanEqual = 2, - LessThan = 3, - LessThanEqual = 4, - Equal = 5, - Inequal = 6, -}; - -union Cheat { - std::array raw; - - BitField<4, 4, CodeType> type; - BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. - BitField<0, 4, u32> end_of_loop; - BitField<12, 4, MemoryType> memory_type; - BitField<8, 4, u32> register_3; - BitField<8, 4, ComparisonOp> comparison_op; - BitField<20, 4, u32> load_from_register; - BitField<20, 4, u32> increment_register; - BitField<20, 4, ArithmeticOp> arithmetic_op; - BitField<16, 4, u32> add_additional_register; - BitField<28, 4, u32> register_6; - - u64 Address() const; - u64 ValueWidth(u64 offset) const; - u64 Value(u64 offset, u64 width) const; - u32 KeypadValue() const; -}; - -class CheatParser; - -// Represents a full collection of cheats for a game. The Execute function should be called every -// interval that all cheats should be executed. Clients should not directly instantiate this class -// (hence private constructor), they should instead receive an instance from CheatParser, which -// guarantees the list is always in an acceptable state. -class CheatList { -public: - friend class CheatParser; - - using Block = std::vector; - using ProgramSegment = std::vector>; - - // (width in bytes, address, value) - using MemoryWriter = void (*)(u32, VAddr, u64); - // (width in bytes, address) -> value - using MemoryReader = u64 (*)(u32, VAddr); - - void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, - MemoryWriter writer, MemoryReader reader); - - void Execute(); - -private: - CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); - - void ProcessBlockPairs(const Block& block); - void ExecuteSingleCheat(const Cheat& cheat); - - void ExecuteBlock(const Block& block); - - bool EvaluateConditional(const Cheat& cheat) const; - - // Individual cheat operations - void WriteImmediate(const Cheat& cheat); - void BeginConditional(const Cheat& cheat); - void EndConditional(const Cheat& cheat); - void Loop(const Cheat& cheat); - void LoadImmediate(const Cheat& cheat); - void LoadIndexed(const Cheat& cheat); - void StoreIndexed(const Cheat& cheat); - void RegisterArithmetic(const Cheat& cheat); - void BeginConditionalInput(const Cheat& cheat); - - VAddr SanitizeAddress(VAddr in) const; - - // Master Codes are defined as codes that cannot be disabled and are run prior to all - // others. - ProgramSegment master_list; - // All other codes - ProgramSegment standard_list; - - bool in_standard = false; - - // 16 (0x0-0xF) scratch registers that can be used by cheats - std::array scratch{}; - - MemoryWriter writer = nullptr; - MemoryReader reader = nullptr; - - u64 main_region_begin{}; - u64 heap_region_begin{}; - u64 main_region_end{}; - u64 heap_region_end{}; - - u64 current_block{}; - // The current index of the cheat within the current Block - u64 current_index{}; - - // The 'stack' of the program. When a conditional or loop statement is encountered, its index is - // pushed onto this queue. When a end block is encountered, the condition is checked. - std::map block_pairs; - - std::set encountered_loops; - - const Core::System* system; -}; - -// Intermediary class that parses a text file or other disk format for storing cheats into a -// CheatList object, that can be used for execution. -class CheatParser { -public: - virtual ~CheatParser(); - - virtual CheatList Parse(const Core::System& system, const std::vector& data) const = 0; - -protected: - CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, - CheatList::ProgramSegment standard) const; -}; - -// CheatParser implementation that parses text files -class TextCheatParser final : public CheatParser { -public: - ~TextCheatParser() override; - - CheatList Parse(const Core::System& system, const std::vector& data) const override; - -private: - std::array ParseSingleLineCheat(const std::string& line) const; -}; - -// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming -class CheatEngine final { -public: - CheatEngine(Core::System& system_, std::vector cheats_, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); - ~CheatEngine(); - -private: - void FrameCallback(u64 userdata, s64 cycles_late); - - std::vector cheats; - - Core::Timing::EventType* event; - Core::Timing::CoreTiming& core_timing; -}; - -} // namespace FileSys diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp new file mode 100644 index 000000000..ea5c76fc0 --- /dev/null +++ b/src/core/memory/cheat_engine.cpp @@ -0,0 +1,234 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include "common/hex_util.h" +#include "common/microprofile.h" +#include "common/swap.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/kernel/process.h" +#include "core/hle/service/hid/controllers/npad.h" +#include "core/hle/service/hid/hid.h" +#include "core/hle/service/sm/sm.h" +#include "core/memory/cheat_engine.h" + +namespace Memory { + +constexpr s64 CHEAT_ENGINE_TICKS = static_cast(Core::Timing::BASE_CLOCK_RATE / 12); +constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; + +StandardVmCallbacks::StandardVmCallbacks(const Core::System& system, + const CheatProcessMetadata& metadata) + : system(system), metadata(metadata) {} + +StandardVmCallbacks::~StandardVmCallbacks() = default; + +void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) { + ReadBlock(SanitizeAddress(address), data, size); +} + +void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) { + WriteBlock(SanitizeAddress(address), data, size); +} + +u64 StandardVmCallbacks::HidKeysDown() { + const auto applet_resource = + system.ServiceManager().GetService("hid")->GetAppletResource(); + if (applet_resource == nullptr) { + LOG_WARNING(CheatEngine, + "Attempted to read input state, but applet resource is not initialized!"); + return false; + } + + const auto press_state = + applet_resource + ->GetController(Service::HID::HidController::NPad) + .GetAndResetPressState(); + return press_state & KEYPAD_BITMASK; +} + +void StandardVmCallbacks::DebugLog(u8 id, u64 value) { + LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); +} + +void StandardVmCallbacks::CommandLog(std::string_view data) { + LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", + data.back() == '\n' ? data.substr(0, data.size() - 1) : data); +} + +VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const { + if ((in < metadata.main_nso_extents.base || + in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && + (in < metadata.heap_extents.base || + in >= metadata.heap_extents.base + metadata.heap_extents.size)) { + LOG_ERROR(CheatEngine, + "Cheat attempting to access memory at invalid address={:016X}, if this " + "persists, " + "the cheat may be incorrect. However, this may be normal early in execution if " + "the game has not properly set up yet.", + in); + return 0; ///< Invalid addresses will hard crash + } + + return in; +} + +CheatParser::~CheatParser() = default; + +TextCheatParser::~TextCheatParser() = default; + +namespace { +template +std::string_view ExtractName(std::string_view data, std::size_t start_index) { + auto end_index = start_index; + while (data[end_index] != match) { + ++end_index; + if (end_index > data.size() || + (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) { + return {}; + } + } + + return data.substr(start_index, end_index - start_index); +} +} // Anonymous namespace + +std::vector TextCheatParser::Parse(const Core::System& system, + std::string_view data) const { + std::vector out(1); + std::optional current_entry = std::nullopt; + + for (std::size_t i = 0; i < data.size(); ++i) { + if (std::isspace(data[i])) { + continue; + } + + if (data[i] == '{') { + current_entry = 0; + + if (out[*current_entry].definition.num_opcodes > 0) { + return {}; + } + + const auto name = ExtractName<'}'>(data, i + 1); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name.length() + 1; + } else if (data[i] == '[') { + current_entry = out.size(); + out.emplace_back(); + + const auto name = ExtractName<']'>(data, i + 1); + if (name.empty()) { + return {}; + } + + std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), + std::min(out[*current_entry].definition.readable_name.size(), + name.size())); + out[*current_entry] + .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = + '\0'; + + i += name.length() + 1; + } else if (std::isxdigit(data[i])) { + if (!current_entry || out[*current_entry].definition.num_opcodes >= + out[*current_entry].definition.opcodes.size()) { + return {}; + } + + const auto hex = std::string(data.substr(i, 8)); + if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { + return {}; + } + + out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = + std::stoul(hex, nullptr, 0x10); + + i += 8; + } else { + return {}; + } + } + + out[0].enabled = out[0].definition.num_opcodes > 0; + out[0].cheat_id = 0; + + for (u32 i = 1; i < out.size(); ++i) { + out[i].enabled = out[i].definition.num_opcodes > 0; + out[i].cheat_id = i; + } + + return out; +} + +CheatEngine::CheatEngine(Core::System& system, std::vector cheats, + const std::array& build_id) + : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique( + system, metadata)}, + cheats(std::move(cheats)) { + metadata.main_nso_build_id = build_id; +} + +CheatEngine::~CheatEngine() { + core_timing.UnscheduleEvent(event, 0); +} + +void CheatEngine::Initialize() { + event = core_timing.RegisterEvent( + "CheatEngine::FrameCallback::" + Common::HexArrayToString(metadata.main_nso_build_id), + [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); + + metadata.process_id = system.CurrentProcess()->GetProcessID(); + metadata.title_id = system.CurrentProcess()->GetTitleID(); + + const auto& vm_manager = system.CurrentProcess()->VMManager(); + metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()}; + metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(), + vm_manager.GetAddressSpaceSize()}; + metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()}; + + is_pending_reload.exchange(true); +} + +void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { + metadata.main_nso_extents = {main_region_begin, main_region_size}; +} + +void CheatEngine::Reload(std::vector cheats) { + this->cheats = std::move(cheats); + is_pending_reload.exchange(true); +} + +MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); + +void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { + if (is_pending_reload.exchange(false)) { + vm.LoadProgram(cheats); + } + + if (vm.GetProgramSize() == 0) { + return; + } + + MICROPROFILE_SCOPE(Cheat_Engine); + + vm.Execute(metadata); + + core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); +} + +} // namespace Memory diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h new file mode 100644 index 000000000..0f012e9b5 --- /dev/null +++ b/src/core/memory/cheat_engine.h @@ -0,0 +1,86 @@ +// Copyright 2018 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" +#include "core/memory/dmnt_cheat_vm.h" + +namespace Core { +class System; +} + +namespace Core::Timing { +class CoreTiming; +struct EventType; +} // namespace Core::Timing + +namespace Memory { + +class StandardVmCallbacks : public DmntCheatVm::Callbacks { +public: + StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata); + ~StandardVmCallbacks() override; + + void MemoryRead(VAddr address, void* data, u64 size) override; + void MemoryWrite(VAddr address, const void* data, u64 size) override; + u64 HidKeysDown() override; + void DebugLog(u8 id, u64 value) override; + void CommandLog(std::string_view data) override; + +private: + VAddr SanitizeAddress(VAddr address) const; + + const CheatProcessMetadata& metadata; + const Core::System& system; +}; + +// Intermediary class that parses a text file or other disk format for storing cheats into a +// CheatList object, that can be used for execution. +class CheatParser { +public: + virtual ~CheatParser(); + + virtual std::vector Parse(const Core::System& system, + std::string_view data) const = 0; +}; + +// CheatParser implementation that parses text files +class TextCheatParser final : public CheatParser { +public: + ~TextCheatParser() override; + + std::vector Parse(const Core::System& system, std::string_view data) const override; +}; + +// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming +class CheatEngine final { +public: + CheatEngine(Core::System& system_, std::vector cheats_, + const std::array& build_id); + ~CheatEngine(); + + void Initialize(); + void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); + + void Reload(std::vector cheats); + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + + DmntCheatVm vm; + CheatProcessMetadata metadata; + + std::vector cheats; + std::atomic_bool is_pending_reload{false}; + + Core::Timing::EventType* event{}; + Core::Timing::CoreTiming& core_timing; + Core::System& system; +}; + +} // namespace Memory From c6becfc9f58c3552eaa5338ca59695e385f5688c Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:35:28 -0400 Subject: [PATCH 4/9] nso: Pass build ID directly As opposed to converting to string and then back to hex array --- src/core/loader/nso.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 70c90109f..e75c700ad 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -152,8 +152,7 @@ std::optional AppLoader_NSO::LoadModule(Kernel::Process& process, auto& system = Core::System::GetInstance(); const auto cheats = pm->CreateCheatList(system, nso_header.build_id); if (!cheats.empty()) { - system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base, - load_base + program_image.size()); + system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size); } } From a0055192fed8748e6ef3f001adc36f771909de31 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:35:52 -0400 Subject: [PATCH 5/9] patch_manager: Update cheat parsing for new VM --- src/core/file_sys/patch_manager.cpp | 29 +++++++++++++++++------------ src/core/file_sys/patch_manager.h | 6 +++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index c1dd0c6d7..90b537834 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -22,6 +22,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/loader/loader.h" #include "core/loader/nso.h" +#include "core/memory/cheat_engine.h" #include "core/settings.h" namespace FileSys { @@ -247,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } -static std::optional ReadCheatFileFromFolder(const Core::System& system, u64 title_id, - const std::array& build_id_, - const VirtualDir& base_path, bool upper) { +namespace { +std::optional> ReadCheatFileFromFolder( + const Core::System& system, u64 title_id, const std::array& build_id_, + const VirtualDir& base_path, bool upper) { const auto build_id_raw = Common::HexToString(build_id_, upper); const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2); const auto file = base_path->GetFile(fmt::format("{}.txt", build_id)); @@ -267,12 +269,15 @@ static std::optional ReadCheatFileFromFolder(const Core::System& syst return std::nullopt; } - TextCheatParser parser; - return parser.Parse(system, data); + Memory::TextCheatParser parser; + return parser.Parse( + system, std::string_view(reinterpret_cast(data.data()), data.size())); } -std::vector PatchManager::CreateCheatList(const Core::System& system, - const std::array& build_id_) const { +} // Anonymous namespace + +std::vector PatchManager::CreateCheatList( + const Core::System& system, const std::array& build_id_) const { const auto load_dir = Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); if (load_dir == nullptr) { @@ -284,20 +289,20 @@ std::vector PatchManager::CreateCheatList(const Core::System& system, std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - std::vector out; - out.reserve(patch_dirs.size()); + std::vector out; for (const auto& subdir : patch_dirs) { auto cheats_dir = subdir->GetSubdirectory("cheats"); if (cheats_dir != nullptr) { auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true); if (res.has_value()) { - out.push_back(std::move(*res)); + std::copy(res->begin(), res->end(), std::back_inserter(out)); continue; } res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false); - if (res.has_value()) - out.push_back(std::move(*res)); + if (res.has_value()) { + std::copy(res->begin(), res->end(), std::back_inserter(out)); + } } } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index a363c6577..e857e6e82 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,9 @@ #include #include #include "common/common_types.h" -#include "core/file_sys/cheat_engine.h" #include "core/file_sys/nca_metadata.h" #include "core/file_sys/vfs.h" +#include "core/memory/dmnt_cheat_types.h" namespace Core { class System; @@ -51,8 +51,8 @@ public: bool HasNSOPatch(const std::array& build_id) const; // Creates a CheatList object with all - std::vector CreateCheatList(const Core::System& system, - const std::array& build_id) const; + std::vector CreateCheatList(const Core::System& system, + const std::array& build_id) const; // Currently tracked RomFS patches: // - Game Updates From 37850eeee57adfb5318aff7cd19bf9ba334ab738 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:36:18 -0400 Subject: [PATCH 6/9] core: Update RegisterCheatList for new VM --- src/core/core.cpp | 17 +++++++++-------- src/core/core.h | 10 +++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index f22244cf7..67ec8d4b9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -37,6 +37,7 @@ #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" +#include "core/memory/cheat_engine.h" #include "core/perf_stats.h" #include "core/reporter.h" #include "core/settings.h" @@ -329,7 +330,7 @@ struct System::Impl { CpuCoreManager cpu_core_manager; bool is_powered_on = false; - std::unique_ptr cheat_engine; + std::unique_ptr cheat_engine; std::unique_ptr memory_freezer; /// Frontend applets @@ -544,13 +545,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { return impl->debug_context.get(); } -void System::RegisterCheatList(const std::vector& list, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) { - impl->cheat_engine = std::make_unique(*this, list, build_id, - code_region_start, code_region_end); -} - void System::SetFilesystem(std::shared_ptr vfs) { impl->virtual_filesystem = std::move(vfs); } @@ -559,6 +553,13 @@ std::shared_ptr System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::RegisterCheatList(const std::vector& list, + const std::array& build_id, VAddr main_region_begin, + u64 main_region_size) { + impl->cheat_engine = std::make_unique(*this, list, build_id); + impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size); +} + void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) { impl->applet_manager.SetAppletFrontendSet(std::move(set)); } diff --git a/src/core/core.h b/src/core/core.h index bb2962fdd..d2a3c82d8 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -18,7 +18,6 @@ class EmuWindow; } // namespace Core::Frontend namespace FileSys { -class CheatList; class ContentProvider; class ContentProviderUnion; enum class ContentProviderUnionSlot; @@ -36,6 +35,10 @@ class AppLoader; enum class ResultStatus : u16; } // namespace Loader +namespace Memory { +struct CheatEntry; +} // namespace Memory + namespace Service { namespace AM::Applets { @@ -286,8 +289,9 @@ public: std::shared_ptr GetFilesystem() const; - void RegisterCheatList(const std::vector& list, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); + void RegisterCheatList(const std::vector& list, + const std::array& build_id, VAddr main_region_begin, + u64 main_region_size); void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); From 3e729c13cc70af6fb8fef6e38bd5deba7a8a0d6e Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Thu, 30 May 2019 19:37:18 -0400 Subject: [PATCH 7/9] core: Initialize cheats after load to avoid VMManager crash This used to occur due to the VMManager being nullptr at the time cheats were registered (during load, but before it was done). This is bypassed by not accessing the VMManager for offset data until load is complete, --- src/core/core.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/core.cpp b/src/core/core.cpp index 67ec8d4b9..fc7039421 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -205,6 +205,11 @@ struct System::Impl { gpu_core->Start(); cpu_core_manager.StartThreads(); + // Initialize cheat engine + if (cheat_engine) { + cheat_engine->Initialize(); + } + // All threads are started, begin main process execution, now that we're in the clear. main_process->Run(load_parameters->main_thread_priority, load_parameters->main_thread_stack_size); From 682174b1123c5721b12eaf8472da46251fea3b1d Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 21 Sep 2019 18:13:10 -0400 Subject: [PATCH 8/9] dmnt_cheat_vm: Make Cheat VM compliant to code style --- src/core/core.cpp | 3 +- src/core/memory/cheat_engine.cpp | 6 +- src/core/memory/dmnt_cheat_vm.cpp | 1500 +++++++++++++++-------------- src/core/memory/dmnt_cheat_vm.h | 217 ++--- 4 files changed, 859 insertions(+), 867 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index fc7039421..76bb2bae9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -17,6 +17,7 @@ #include "core/file_sys/bis_factory.h" #include "core/file_sys/card_image.h" #include "core/file_sys/mode.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/registered_cache.h" #include "core/file_sys/romfs_factory.h" #include "core/file_sys/savedata_factory.h" @@ -43,8 +44,6 @@ #include "core/settings.h" #include "core/telemetry_session.h" #include "core/tools/freezer.h" -#include "file_sys/cheat_engine.h" -#include "file_sys/patch_manager.h" #include "video_core/debug_utils/debug_utils.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp index ea5c76fc0..b56cb0627 100644 --- a/src/core/memory/cheat_engine.cpp +++ b/src/core/memory/cheat_engine.cpp @@ -102,7 +102,7 @@ std::vector TextCheatParser::Parse(const Core::System& system, std::optional current_entry = std::nullopt; for (std::size_t i = 0; i < data.size(); ++i) { - if (std::isspace(data[i])) { + if (::isspace(data[i])) { continue; } @@ -143,7 +143,7 @@ std::vector TextCheatParser::Parse(const Core::System& system, '\0'; i += name.length() + 1; - } else if (std::isxdigit(data[i])) { + } else if (::isxdigit(data[i])) { if (!current_entry || out[*current_entry].definition.num_opcodes >= out[*current_entry].definition.opcodes.size()) { return {}; @@ -188,7 +188,7 @@ CheatEngine::~CheatEngine() { void CheatEngine::Initialize() { event = core_timing.RegisterEvent( - "CheatEngine::FrameCallback::" + Common::HexArrayToString(metadata.main_nso_build_id), + "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp index a3f450dac..cc16d15a4 100644 --- a/src/core/memory/dmnt_cheat_vm.cpp +++ b/src/core/memory/dmnt_cheat_vm.cpp @@ -29,264 +29,272 @@ namespace Memory { +DmntCheatVm::DmntCheatVm(std::unique_ptr callbacks) : callbacks(std::move(callbacks)) {} + +DmntCheatVm::~DmntCheatVm() = default; + 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); + if (auto store_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(store_static->mem_type))); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); + } else if (auto begin_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_cond->mem_type))); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast(begin_cond->cond_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); + } else if (auto end_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: End Conditional"); + } else if (auto ctrl_loop = std::get_if(&opcode.opcode)) { + if (ctrl_loop->start_loop) { + callbacks->CommandLog("Opcode: Start Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); } else { - this->LogToDebugFile("Opcode: End Loop\n"); - this->LogToDebugFile("Reg Idx: %x\n", opcode.ctrl_loop.reg_index); + callbacks->CommandLog("Opcode: End Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", 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); + } else if (auto ldr_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Static"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); + callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value)); + } else if (auto ldr_memory = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Memory"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(ldr_memory->mem_type))); + callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); + } else if (auto str_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); + if (str_static->add_offset_reg) { + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", 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); + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); + callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value)); + } else if (auto perform_math_static = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Static Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); + callbacks->CommandLog( + fmt::format("Math Type: {:X}", static_cast(perform_math_static->math_type))); + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); + } else if (auto begin_keypress_cond = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Keypress Conditional"); + callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); + } else if (auto perform_math_reg = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Register Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); + callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); + if (perform_math_reg->has_immediate) { + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); } else { - this->LogToDebugFile("Src2 Idx: %x\n", opcode.perform_math_reg.src_reg_2_index); + callbacks->CommandLog( + fmt::format("Src2 Idx: {:X}", 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: + } else if (auto str_register = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Register to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); + callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: break; - case StoreRegisterOffsetType_Reg: - this->LogToDebugFile("O Reg Idx: %x\n", opcode.str_register.ofs_reg_index); + case StoreRegisterOffsetType::Reg: + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); break; - case StoreRegisterOffsetType_Imm: - this->LogToDebugFile("Rel Addr: %lx\n", opcode.str_register.rel_address); + case StoreRegisterOffsetType::Imm: + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); break; - case StoreRegisterOffsetType_MemReg: - this->LogToDebugFile("Mem Type: %x\n", opcode.str_register.mem_type); + case StoreRegisterOffsetType::MemReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(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); + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", 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); + } else if (auto begin_reg_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Register Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast(begin_reg_cond->cond_type))); + callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::StaticValue: + callbacks->CommandLog("Comp Type: Static Value"); + callbacks->CommandLog(fmt::format("Value: {:X}", 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); + case CompareRegisterValueType::OtherRegister: + callbacks->CommandLog("Comp Type: Other Register"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", 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); + case CompareRegisterValueType::MemoryRelAddr: + callbacks->CommandLog("Comp Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", 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); + case CompareRegisterValueType::MemoryOfsReg: + callbacks->CommandLog("Comp Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", 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); + case CompareRegisterValueType::RegisterRelAddr: + callbacks->CommandLog("Comp Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", 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); + case CompareRegisterValueType::RegisterOfsReg: + callbacks->CommandLog("Comp Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", 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]); + } else if (auto save_restore_reg = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register"); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); + callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_reg->op_type))); + } else if (auto save_restore_regmask = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register Mask"); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_regmask->op_type))); + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog( + fmt::format("Act[{:02X}]: {:d}", i, 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); + } else if (auto debug_log = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Debug Log"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); + callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); + callbacks->CommandLog( + fmt::format("Val Type: {:X}", static_cast(debug_log->val_type))); + switch (debug_log->val_type) { + case DebugLogValueType::RegisterValue: + callbacks->CommandLog("Val Type: Register Value"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", 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); + case DebugLogValueType::MemoryRelAddr: + callbacks->CommandLog("Val Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", 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); + case DebugLogValueType::MemoryOfsReg: + callbacks->CommandLog("Val Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", 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); + case DebugLogValueType::RegisterRelAddr: + callbacks->CommandLog("Val Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", 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); + case DebugLogValueType::RegisterOfsReg: + callbacks->CommandLog("Val Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); break; } - default: - this->LogToDebugFile("Unknown opcode: %x\n", opcode.opcode); - break; + } else if (auto instr = std::get_if(&opcode.opcode)) { + callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(instr->opcode))); } } DmntCheatVm::Callbacks::~Callbacks() = default; bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { - /* If we've ever seen a decode failure, return false. */ - bool valid = this->decode_success; + // If we've ever seen a decode failure, return false. + bool valid = decode_success; CheatVmOpcode opcode = {}; SCOPE_EXIT({ - this->decode_success &= valid; + decode_success &= valid; if (valid) { out = opcode; } }); - /* Helper function for getting instruction dwords. */ - auto GetNextDword = [&]() { - if (this->instruction_ptr >= this->num_opcodes) { + // Helper function for getting instruction dwords. + const auto GetNextDword = [&] { + if (instruction_ptr >= num_opcodes) { valid = false; return static_cast(0); } - return this->program[this->instruction_ptr++]; + return program[instruction_ptr++]; }; - /* Helper function for parsing a VmInt. */ - auto GetNextVmInt = [&](const u32 bit_width) { - VmInt val = {0}; + // Helper function for parsing a VmInt. + const auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val{}; const u32 first_dword = GetNextDword(); switch (bit_width) { case 1: - val.bit8 = (u8)first_dword; + val.bit8 = static_cast(first_dword); break; case 2: - val.bit16 = (u16)first_dword; + val.bit16 = static_cast(first_dword); break; case 4: val.bit32 = first_dword; break; case 8: - val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); + val.bit64 = (static_cast(first_dword) << 32ul) | static_cast(GetNextDword()); break; } return val; }; - /* Read opcode. */ + // 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)); + auto opcode_type = static_cast(((first_dword >> 28) & 0xF)); + if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 24) & 0xF)); } - if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { - opcode.opcode = - (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); + if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 20) & 0xF)); } - /* detect condition start. */ - switch (opcode.opcode) { - case CheatVmOpcodeType_BeginConditionalBlock: - case CheatVmOpcodeType_BeginKeypressConditionalBlock: - case CheatVmOpcodeType_BeginRegisterConditionalBlock: + // detect condition start. + switch (opcode_type) { + case CheatVmOpcodeType::BeginConditionalBlock: + case CheatVmOpcodeType::BeginKeypressConditionalBlock: + case CheatVmOpcodeType::BeginRegisterConditionalBlock: opcode.begin_conditional_block = true; break; default: @@ -294,299 +302,335 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { break; } - switch (opcode.opcode) { - case CheatVmOpcodeType_StoreStatic: { - /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ - /* Read additional words. */ + switch (opcode_type) { + case CheatVmOpcodeType::StoreStatic: { + StoreStaticOpcode store_static{}; + // 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); + store_static.bit_width = (first_dword >> 24) & 0xF; + store_static.mem_type = static_cast((first_dword >> 20) & 0xF); + store_static.offset_register = ((first_dword >> 16) & 0xF); + store_static.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + store_static.value = GetNextVmInt(store_static.bit_width); + opcode.opcode = store_static; } break; - case CheatVmOpcodeType_BeginConditionalBlock: { - /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ - /* Read additional words. */ + case CheatVmOpcodeType::BeginConditionalBlock: { + BeginConditionalOpcode begin_cond{}; + // 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); + begin_cond.bit_width = (first_dword >> 24) & 0xF; + begin_cond.mem_type = static_cast((first_dword >> 20) & 0xF); + begin_cond.cond_type = static_cast((first_dword >> 16) & 0xF); + begin_cond.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + begin_cond.value = GetNextVmInt(begin_cond.bit_width); + opcode.opcode = begin_cond; } break; - case CheatVmOpcodeType_EndConditionalBlock: { - /* 20000000 */ - /* There's actually nothing left to process here! */ + case CheatVmOpcodeType::EndConditionalBlock: { + // 20000000 + // There's actually nothing left to process here! + opcode.opcode = EndConditionalOpcode{}; } 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); + case CheatVmOpcodeType::ControlLoop: { + ControlLoopOpcode ctrl_loop{}; + // 300R0000 VVVVVVVV + // 310R0000 + // Parse register, whether loop start or loop end. + ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + 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(); + // Read number of iters if loop start. + if (ctrl_loop.start_loop) { + ctrl_loop.num_iters = GetNextDword(); } + opcode.opcode = ctrl_loop; } 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()); + case CheatVmOpcodeType::LoadRegisterStatic: { + LoadRegisterStaticOpcode ldr_static{}; + // 400R0000 VVVVVVVV VVVVVVVV + // Read additional words. + ldr_static.reg_index = ((first_dword >> 16) & 0xF); + ldr_static.value = + (static_cast(GetNextDword()) << 32ul) | static_cast(GetNextDword()); + opcode.opcode = ldr_static; } break; - case CheatVmOpcodeType_LoadRegisterMemory: { - /* 5TMRI0AA AAAAAAAA */ - /* Read additional words. */ + case CheatVmOpcodeType::LoadRegisterMemory: { + LoadRegisterMemoryOpcode ldr_memory{}; + // 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); + ldr_memory.bit_width = (first_dword >> 24) & 0xF; + ldr_memory.mem_type = static_cast((first_dword >> 20) & 0xF); + ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; + ldr_memory.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + opcode.opcode = ldr_memory; } 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()); + case CheatVmOpcodeType::StoreStaticToAddress: { + StoreStaticToAddressOpcode str_static{}; + // 6T0RIor0 VVVVVVVV VVVVVVVV + // Read additional words. + str_static.bit_width = (first_dword >> 24) & 0xF; + str_static.reg_index = ((first_dword >> 16) & 0xF); + str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + str_static.value = + (static_cast(GetNextDword()) << 32ul) | static_cast(GetNextDword()); + opcode.opcode = str_static; } 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(); + case CheatVmOpcodeType::PerformArithmeticStatic: { + PerformArithmeticStaticOpcode perform_math_static{}; + // 7T0RC000 VVVVVVVV + // Read additional words. + perform_math_static.bit_width = (first_dword >> 24) & 0xF; + perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + perform_math_static.math_type = + static_cast((first_dword >> 12) & 0xF); + perform_math_static.value = GetNextDword(); + opcode.opcode = perform_math_static; } break; - case CheatVmOpcodeType_BeginKeypressConditionalBlock: { - /* 8kkkkkkk */ - /* Just parse the mask. */ - opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + case CheatVmOpcodeType::BeginKeypressConditionalBlock: { + BeginKeypressConditionalOpcode begin_keypress_cond{}; + // 8kkkkkkk + // Just parse the mask. + 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); + case CheatVmOpcodeType::PerformArithmeticRegister: { + PerformArithmeticRegisterOpcode perform_math_reg{}; + // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) + perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + perform_math_reg.math_type = static_cast((first_dword >> 20) & 0xF); + perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (perform_math_reg.has_immediate) { + perform_math_reg.src_reg_2_index = 0; + perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); } else { - opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); } + opcode.opcode = perform_math_reg; } 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 */ + case CheatVmOpcodeType::StoreRegisterToAddress: { + StoreRegisterToAddressOpcode str_register{}; + // 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) + str_register.bit_width = (first_dword >> 24) & 0xF; + str_register.str_reg_index = ((first_dword >> 20) & 0xF); + str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_register.ofs_type = static_cast(((first_dword >> 8) & 0xF)); + str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (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())); + case StoreRegisterOffsetType::Imm: + str_register.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); break; - case StoreRegisterOffsetType_MemReg: - opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + case StoreRegisterOffsetType::MemReg: + str_register.mem_type = static_cast((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())); + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + str_register.mem_type = static_cast((first_dword >> 4) & 0xF); + str_register.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); break; default: - opcode.str_register.ofs_type = StoreRegisterOffsetType_None; + str_register.ofs_type = StoreRegisterOffsetType::None; break; } + opcode.opcode = str_register; } 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); + case CheatVmOpcodeType::BeginRegisterConditionalBlock: { + BeginRegisterConditionalOpcode begin_reg_cond{}; + // 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. + begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + begin_reg_cond.cond_type = + static_cast((first_dword >> 16) & 0xF); + begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + begin_reg_cond.comp_type = static_cast((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); + switch (begin_reg_cond.comp_type) { + case CompareRegisterValueType::StaticValue: + begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); break; - case CompareRegisterValueType_OtherRegister: - opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + case CompareRegisterValueType::OtherRegister: + 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())); + case CompareRegisterValueType::MemoryRelAddr: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(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); + case CompareRegisterValueType::MemoryOfsReg: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + 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())); + case CompareRegisterValueType::RegisterRelAddr: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(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); + case CompareRegisterValueType::RegisterOfsReg: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); break; } + opcode.opcode = begin_reg_cond; } 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); + case CheatVmOpcodeType::SaveRestoreRegister: { + SaveRestoreRegisterOpcode save_restore_reg{}; + // 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. + save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + save_restore_reg.src_index = (first_dword >> 8) & 0xF; + save_restore_reg.op_type = static_cast((first_dword >> 4) & 0xF); + opcode.opcode = save_restore_reg; } 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; + case CheatVmOpcodeType::SaveRestoreRegisterMask: { + SaveRestoreRegisterMaskOpcode save_restore_regmask{}; + // 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. + save_restore_regmask.op_type = + static_cast((first_dword >> 20) & 0xF); + for (std::size_t i = 0; i < NumRegisters; i++) { + save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; } + opcode.opcode = save_restore_regmask; } 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); + case CheatVmOpcodeType::DebugLog: { + DebugLogOpcode debug_log{}; + // 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. + debug_log.bit_width = (first_dword >> 16) & 0xF; + debug_log.log_id = ((first_dword >> 12) & 0xF); + debug_log.val_type = static_cast((first_dword >> 8) & 0xF); - switch (opcode.debug_log.val_type) { - case DebugLogValueType_RegisterValue: - opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + switch (debug_log.val_type) { + case DebugLogValueType::RegisterValue: + 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())); + case DebugLogValueType::MemoryRelAddr: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); break; - case DebugLogValueType_MemoryOfsReg: - opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + case DebugLogValueType::MemoryOfsReg: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + 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())); + case DebugLogValueType::RegisterRelAddr: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); break; - case DebugLogValueType_RegisterOfsReg: - opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); - opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + case DebugLogValueType::RegisterOfsReg: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = (first_dword & 0xF); break; } + opcode.opcode = debug_log; } break; - case CheatVmOpcodeType_ExtendedWidth: - case CheatVmOpcodeType_DoubleExtendedWidth: + case CheatVmOpcodeType::ExtendedWidth: + case CheatVmOpcodeType::DoubleExtendedWidth: default: - /* Unrecognized instruction cannot be decoded. */ + // Unrecognized instruction cannot be decoded. valid = false; + opcode.opcode = UnrecognizedInstruction{opcode_type}; break; } - /* End decoding. */ + // 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; + if (condition_depth > 0) { + // We want to continue until we're out of the current block. + const std::size_t desired_depth = 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... */ + while (condition_depth > desired_depth && 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. */ + // 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--; + condition_depth++; + } else if (std::holds_alternative(skip_opcode.opcode)) { + 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. */ + // Skipping, but 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"); } } @@ -602,7 +646,7 @@ u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { case 8: return value.bit64; default: - /* Invalid bit width -> return 0. */ + // Invalid bit width -> return 0. return 0; } } @@ -610,39 +654,37 @@ u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, MemoryAccessType mem_type, u64 rel_address) { switch (mem_type) { - case MemoryAccessType_MainNso: + case MemoryAccessType::MainNso: default: return metadata.main_nso_extents.base + rel_address; - case MemoryAccessType_Heap: + 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; + registers.fill(0); + saved_values.fill(0); + loop_tops.fill(0); + instruction_ptr = 0; + condition_depth = 0; + decode_success = true; } bool DmntCheatVm::LoadProgram(const std::vector& entries) { - /* Reset opcode count. */ - this->num_opcodes = 0; + // Reset opcode count. + num_opcodes = 0; - for (size_t i = 0; i < entries.size(); i++) { + for (std::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; + // Bounds check. + if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { + 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]; + for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { + program[num_opcodes++] = entries[i].definition.opcodes[n]; } } } @@ -653,262 +695,249 @@ bool DmntCheatVm::LoadProgram(const std::vector& entries) { void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { CheatVmOpcode cur_opcode{}; - /* Get Keys down. */ + // 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)); + callbacks->CommandLog("Started VM execution."); + callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast(kDown & 0x0FFFFFFF))); - /* Clear VM state. */ - this->ResetState(); + // Clear VM state. + ResetState(); - /* Loop until program finishes. */ - while (this->DecodeNextOpcode(cur_opcode)) { - this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr); + // Loop until program finishes. + while (DecodeNextOpcode(cur_opcode)) { + callbacks->CommandLog( + fmt::format("Instruction Ptr: {:04X}", static_cast(instruction_ptr))); - for (size_t i = 0; i < NumRegisters; i++) { - this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]); + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); } - for (size_t i = 0; i < NumRegisters; i++) { - this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]); + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); } - this->LogOpcode(cur_opcode); + LogOpcode(cur_opcode); - /* Increment conditional depth, if relevant. */ + // Increment conditional depth, if relevant. if (cur_opcode.begin_conditional_block) { - this->condition_depth++; + 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) { + if (auto store_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address, write value to memory. + u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, + store_static->rel_address + + registers[store_static->offset_register]); + u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); + switch (store_static->bit_width) { case 1: case 2: case 4: case 8: - callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.store_static.bit_width); + callbacks->MemoryWrite(dst_address, &dst_value, 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); + } else if (auto begin_cond = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. + u64 src_address = + GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); u64 src_value = 0; - switch (cur_opcode.store_static.bit_width) { + switch (store_static->bit_width) { case 1: case 2: case 4: case 8: - callbacks->MemoryRead(src_address, &src_value, cur_opcode.begin_cond.bit_width); + callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width); break; } - /* Check against condition. */ - u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width); + // Check against condition. + u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); bool cond_met = false; - switch (cur_opcode.begin_cond.cond_type) { - case ConditionalComparisonType_GT: + switch (begin_cond->cond_type) { + case ConditionalComparisonType::GT: cond_met = src_value > cond_value; break; - case ConditionalComparisonType_GE: + case ConditionalComparisonType::GE: cond_met = src_value >= cond_value; break; - case ConditionalComparisonType_LT: + case ConditionalComparisonType::LT: cond_met = src_value < cond_value; break; - case ConditionalComparisonType_LE: + case ConditionalComparisonType::LE: cond_met = src_value <= cond_value; break; - case ConditionalComparisonType_EQ: + case ConditionalComparisonType::EQ: cond_met = src_value == cond_value; break; - case ConditionalComparisonType_NE: + case ConditionalComparisonType::NE: cond_met = src_value != cond_value; break; } - /* Skip conditional block if condition not met. */ + // Skip conditional block if condition not met. if (!cond_met) { - this->SkipConditionalBlock(); + 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--; + } else if (auto end_cond = std::get_if(&cur_opcode.opcode)) { + // Decrement the condition depth. + // We will assume, graciously, that mismatched conditional block ends are a nop. + if (condition_depth > 0) { + 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 if (auto ctrl_loop = std::get_if(&cur_opcode.opcode)) { + if (ctrl_loop->start_loop) { + // Start a loop. + registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; + loop_tops[ctrl_loop->reg_index] = 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]; + // End a loop. + registers[ctrl_loop->reg_index]--; + if (registers[ctrl_loop->reg_index] != 0) { + instruction_ptr = loop_tops[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. */ + } else if (auto ldr_static = std::get_if(&cur_opcode.opcode)) { + // Set a register to a static value. + registers[ldr_static->reg_index] = ldr_static->value; + } else if (auto ldr_memory = std::get_if(&cur_opcode.opcode)) { + // 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; + if (ldr_memory->load_from_reg) { + src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; } else { - src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, - cur_opcode.ldr_memory.rel_address); + src_address = + GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); } - /* Read into register. Gateway only reads on valid bitwidth. */ - switch (cur_opcode.ldr_memory.bit_width) { + // Read into register. Gateway only reads on valid bitwidth. + switch (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); + callbacks->MemoryRead(src_address, ®isters[ldr_memory->reg_index], + 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]; + } else if (auto str_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_address = registers[str_static->reg_index]; + u64 dst_value = str_static->value; + if (str_static->add_offset_reg) { + dst_address += registers[str_static->offset_reg_index]; } - /* Write value to memory. Gateway only writes on valid bitwidth. */ - switch (cur_opcode.str_static.bit_width) { + // Write value to memory. Gateway only writes on valid bitwidth. + switch (str_static->bit_width) { case 1: case 2: case 4: case 8: - callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_static.bit_width); + callbacks->MemoryWrite(dst_address, &dst_value, 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; + // Increment register if relevant. + if (str_static->increment_reg) { + registers[str_static->reg_index] += 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; + } else if (auto perform_math_static = + std::get_if(&cur_opcode.opcode)) { + // Do requested math. + switch (perform_math_static->math_type) { + case RegisterArithmeticType::Addition: + registers[perform_math_static->reg_index] += + static_cast(perform_math_static->value); break; - case RegisterArithmeticType_Subtraction: - this->registers[cur_opcode.perform_math_static.reg_index] -= - (u64)cur_opcode.perform_math_static.value; + case RegisterArithmeticType::Subtraction: + registers[perform_math_static->reg_index] -= + static_cast(perform_math_static->value); break; - case RegisterArithmeticType_Multiplication: - this->registers[cur_opcode.perform_math_static.reg_index] *= - (u64)cur_opcode.perform_math_static.value; + case RegisterArithmeticType::Multiplication: + registers[perform_math_static->reg_index] *= + static_cast(perform_math_static->value); break; - case RegisterArithmeticType_LeftShift: - this->registers[cur_opcode.perform_math_static.reg_index] <<= - (u64)cur_opcode.perform_math_static.value; + case RegisterArithmeticType::LeftShift: + registers[perform_math_static->reg_index] <<= + static_cast(perform_math_static->value); break; - case RegisterArithmeticType_RightShift: - this->registers[cur_opcode.perform_math_static.reg_index] >>= - (u64)cur_opcode.perform_math_static.value; + case RegisterArithmeticType::RightShift: + registers[perform_math_static->reg_index] >>= + static_cast(perform_math_static->value); break; default: - /* Do not handle extensions here. */ + // Do not handle extensions here. break; } - /* Apply bit width. */ - switch (cur_opcode.perform_math_static.bit_width) { + // Apply bit width. + switch (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]); + registers[perform_math_static->reg_index] = + static_cast(registers[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]); + registers[perform_math_static->reg_index] = + static_cast(registers[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]); + registers[perform_math_static->reg_index] = + static_cast(registers[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]); + registers[perform_math_static->reg_index] = + static_cast(registers[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(); + } else if (auto begin_keypress_cond = + std::get_if(&cur_opcode.opcode)) { + // Check for keypress. + if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { + // Keys not pressed. Skip conditional block. + SkipConditionalBlock(); } - break; - case CheatVmOpcodeType_PerformArithmeticRegister: { - const u64 operand_1_value = - this->registers[cur_opcode.perform_math_reg.src_reg_1_index]; + } else if (auto perform_math_reg = + std::get_if(&cur_opcode.opcode)) { + const u64 operand_1_value = registers[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]; + perform_math_reg->has_immediate + ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) + : registers[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: + // Do requested math. + switch (perform_math_reg->math_type) { + case RegisterArithmeticType::Addition: res_val = operand_1_value + operand_2_value; break; - case RegisterArithmeticType_Subtraction: + case RegisterArithmeticType::Subtraction: res_val = operand_1_value - operand_2_value; break; - case RegisterArithmeticType_Multiplication: + case RegisterArithmeticType::Multiplication: res_val = operand_1_value * operand_2_value; break; - case RegisterArithmeticType_LeftShift: + case RegisterArithmeticType::LeftShift: res_val = operand_1_value << operand_2_value; break; - case RegisterArithmeticType_RightShift: + case RegisterArithmeticType::RightShift: res_val = operand_1_value >> operand_2_value; break; - case RegisterArithmeticType_LogicalAnd: + case RegisterArithmeticType::LogicalAnd: res_val = operand_1_value & operand_2_value; break; - case RegisterArithmeticType_LogicalOr: + case RegisterArithmeticType::LogicalOr: res_val = operand_1_value | operand_2_value; break; - case RegisterArithmeticType_LogicalNot: + case RegisterArithmeticType::LogicalNot: res_val = ~operand_1_value; break; - case RegisterArithmeticType_LogicalXor: + case RegisterArithmeticType::LogicalXor: res_val = operand_1_value ^ operand_2_value; break; - case RegisterArithmeticType_None: + case RegisterArithmeticType::None: res_val = operand_1_value; break; } - /* Apply bit width. */ - switch (cur_opcode.perform_math_reg.bit_width) { + // Apply bit width. + switch (perform_math_reg->bit_width) { case 1: res_val = static_cast(res_val); break; @@ -923,282 +952,259 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { 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 */ + // Save to register. + registers[perform_math_reg->dst_reg_index] = res_val; + } else if (auto str_register = + std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_value = registers[str_register->str_reg_index]; + u64 dst_address = registers[str_register->addr_reg_index]; + switch (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]; + case StoreRegisterOffsetType::Reg: + dst_address += registers[str_register->ofs_reg_index]; break; - case StoreRegisterOffsetType_Imm: - dst_address += cur_opcode.str_register.rel_address; + case StoreRegisterOffsetType::Imm: + dst_address += 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]); + case StoreRegisterOffsetType::MemReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[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); + case StoreRegisterOffsetType::MemImm: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + 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); + case StoreRegisterOffsetType::MemImmReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index] + + str_register->rel_address); break; } - /* Write value to memory. Write only on valid bitwidth. */ - switch (cur_opcode.str_register.bit_width) { + // Write value to memory. Write only on valid bitwidth. + switch (str_register->bit_width) { case 1: case 2: case 4: case 8: - callbacks->MemoryWrite(dst_address, &dst_value, cur_opcode.str_register.bit_width); + callbacks->MemoryWrite(dst_address, &dst_value, 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; + // Increment register if relevant. + if (str_register->increment_reg) { + registers[str_register->addr_reg_index] += str_register->bit_width; } - } break; - case CheatVmOpcodeType_BeginRegisterConditionalBlock: { - /* Get value from register. */ + } else if (auto begin_reg_cond = + std::get_if(&cur_opcode.opcode)) { + // Get value from register. u64 src_value = 0; - switch (cur_opcode.begin_reg_cond.bit_width) { + switch (begin_reg_cond->bit_width) { case 1: - src_value = static_cast( - this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul); + src_value = static_cast(registers[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); + src_value = static_cast(registers[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); + src_value = + static_cast(registers[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); + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); break; } - /* Read value from memory. */ + // 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) { + if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { + cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); + } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { + switch (begin_reg_cond->bit_width) { case 1: - cond_value = static_cast( - this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul); + cond_value = + static_cast(registers[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); + cond_value = + static_cast(registers[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); + cond_value = + static_cast(registers[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); + cond_value = static_cast(registers[begin_reg_cond->other_reg_index] & + 0xFFFFFFFFFFFFFFFFul); break; } } else { u64 cond_address = 0; - switch (cur_opcode.begin_reg_cond.comp_type) { - case CompareRegisterValueType_MemoryRelAddr: + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + begin_reg_cond->rel_address); + break; + case CompareRegisterValueType::MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + registers[begin_reg_cond->ofs_reg_index]); + break; + case CompareRegisterValueType::RegisterRelAddr: cond_address = - GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, - cur_opcode.begin_reg_cond.rel_address); + registers[begin_reg_cond->addr_reg_index] + 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]; + case CompareRegisterValueType::RegisterOfsReg: + cond_address = registers[begin_reg_cond->addr_reg_index] + + registers[begin_reg_cond->ofs_reg_index]; break; default: break; } - switch (cur_opcode.begin_reg_cond.bit_width) { + switch (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); + callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width); break; } } - /* Check against condition. */ + // Check against condition. bool cond_met = false; - switch (cur_opcode.begin_reg_cond.cond_type) { - case ConditionalComparisonType_GT: + switch (begin_reg_cond->cond_type) { + case ConditionalComparisonType::GT: cond_met = src_value > cond_value; break; - case ConditionalComparisonType_GE: + case ConditionalComparisonType::GE: cond_met = src_value >= cond_value; break; - case ConditionalComparisonType_LT: + case ConditionalComparisonType::LT: cond_met = src_value < cond_value; break; - case ConditionalComparisonType_LE: + case ConditionalComparisonType::LE: cond_met = src_value <= cond_value; break; - case ConditionalComparisonType_EQ: + case ConditionalComparisonType::EQ: cond_met = src_value == cond_value; break; - case ConditionalComparisonType_NE: + case ConditionalComparisonType::NE: cond_met = src_value != cond_value; break; } - /* Skip conditional block if condition not met. */ + // Skip conditional block if condition not met. if (!cond_met) { - this->SkipConditionalBlock(); + 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; + } else if (auto save_restore_reg = + std::get_if(&cur_opcode.opcode)) { + // Save or restore a register. + switch (save_restore_reg->op_type) { + case SaveRestoreRegisterOpType::ClearRegs: + registers[save_restore_reg->dst_index] = 0ul; break; - case SaveRestoreRegisterOpType_ClearSaved: - this->saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul; + case SaveRestoreRegisterOpType::ClearSaved: + saved_values[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]; + case SaveRestoreRegisterOpType::Save: + saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; break; - case SaveRestoreRegisterOpType_Restore: + case SaveRestoreRegisterOpType::Restore: default: - this->registers[cur_opcode.save_restore_reg.dst_index] = - this->saved_values[cur_opcode.save_restore_reg.src_index]; + registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; break; } - break; - case CheatVmOpcodeType_SaveRestoreRegisterMask: - /* Save or restore register mask. */ + } else if (auto save_restore_regmask = + std::get_if(&cur_opcode.opcode)) { + // 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(); + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::Save: + src = registers.data(); + dst = saved_values.data(); break; - case SaveRestoreRegisterOpType_ClearRegs: - case SaveRestoreRegisterOpType_Restore: + case SaveRestoreRegisterOpType::ClearRegs: + case SaveRestoreRegisterOpType::Restore: default: - src = this->registers.data(); - dst = this->saved_values.data(); + src = registers.data(); + dst = 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: + for (std::size_t i = 0; i < NumRegisters; i++) { + if (save_restore_regmask->should_operate[i]) { + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::ClearRegs: dst[i] = 0ul; break; - case SaveRestoreRegisterOpType_Save: - case SaveRestoreRegisterOpType_Restore: + case SaveRestoreRegisterOpType::Save: + case SaveRestoreRegisterOpType::Restore: default: dst[i] = src[i]; break; } } } - break; - case CheatVmOpcodeType_DebugLog: { - /* Read value from memory. */ + } else if (auto debug_log = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. u64 log_value = 0; - if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { - switch (cur_opcode.debug_log.bit_width) { + if (debug_log->val_type == DebugLogValueType::RegisterValue) { + switch (debug_log->bit_width) { case 1: - log_value = static_cast( - this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFul); break; case 2: - log_value = static_cast( - this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFFFul); break; case 4: - log_value = static_cast( - this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); + log_value = + static_cast(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); break; case 8: - log_value = static_cast( - this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + log_value = static_cast(registers[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); + switch (debug_log->val_type) { + case DebugLogValueType::MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + debug_log->rel_address); break; - case DebugLogValueType_MemoryOfsReg: + case DebugLogValueType::MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + registers[debug_log->ofs_reg_index]); + break; + case DebugLogValueType::RegisterRelAddr: + val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; + break; + case DebugLogValueType::RegisterOfsReg: 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]; + registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; break; default: break; } - switch (cur_opcode.debug_log.bit_width) { + switch (debug_log->bit_width) { case 1: case 2: case 4: case 8: - callbacks->MemoryRead(val_address, &log_value, cur_opcode.debug_log.bit_width); + callbacks->MemoryRead(val_address, &log_value, 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; + // Log value. + DebugLog(debug_log->log_id, log_value); } } } diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h index bea451db4..d1580d7f6 100644 --- a/src/core/memory/dmnt_cheat_vm.h +++ b/src/core/memory/dmnt_cheat_vm.h @@ -24,6 +24,7 @@ #pragma once +#include #include #include #include "common/common_types.h" @@ -31,100 +32,100 @@ 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, +enum class CheatVmOpcodeType : u32 { + StoreStatic = 0, + BeginConditionalBlock = 1, + EndConditionalBlock = 2, + ControlLoop = 3, + LoadRegisterStatic = 4, + LoadRegisterMemory = 5, + StoreStaticToAddress = 6, + PerformArithmeticStatic = 7, + BeginKeypressConditionalBlock = 8, - /* These are not implemented by Gateway's VM. */ - CheatVmOpcodeType_PerformArithmeticRegister = 9, - CheatVmOpcodeType_StoreRegisterToAddress = 10, - CheatVmOpcodeType_Reserved11 = 11, + // These are not implemented by Gateway's VM. + PerformArithmeticRegister = 9, + StoreRegisterToAddress = 10, + Reserved11 = 11, - /* This is a meta entry, and not a real opcode. */ - /* This is to facilitate multi-nybble instruction decoding. */ - CheatVmOpcodeType_ExtendedWidth = 12, + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + ExtendedWidth = 12, - /* Extended width opcodes. */ - CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, - CheatVmOpcodeType_SaveRestoreRegister = 0xC1, - CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, + // Extended width opcodes. + BeginRegisterConditionalBlock = 0xC0, + SaveRestoreRegister = 0xC1, + SaveRestoreRegisterMask = 0xC2, - /* This is a meta entry, and not a real opcode. */ - /* This is to facilitate multi-nybble instruction decoding. */ - CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, + // This is a meta entry, and not a real opcode. + // This is to facilitate multi-nybble instruction decoding. + DoubleExtendedWidth = 0xF0, - /* Double-extended width opcodes. */ - CheatVmOpcodeType_DebugLog = 0xFFF, + // Double-extended width opcodes. + DebugLog = 0xFFF, }; -enum MemoryAccessType : u32 { - MemoryAccessType_MainNso = 0, - MemoryAccessType_Heap = 1, +enum class MemoryAccessType : u32 { + MainNso = 0, + Heap = 1, }; -enum ConditionalComparisonType : u32 { - ConditionalComparisonType_GT = 1, - ConditionalComparisonType_GE = 2, - ConditionalComparisonType_LT = 3, - ConditionalComparisonType_LE = 4, - ConditionalComparisonType_EQ = 5, - ConditionalComparisonType_NE = 6, +enum class ConditionalComparisonType : u32 { + GT = 1, + GE = 2, + LT = 3, + LE = 4, + EQ = 5, + NE = 6, }; -enum RegisterArithmeticType : u32 { - RegisterArithmeticType_Addition = 0, - RegisterArithmeticType_Subtraction = 1, - RegisterArithmeticType_Multiplication = 2, - RegisterArithmeticType_LeftShift = 3, - RegisterArithmeticType_RightShift = 4, +enum class RegisterArithmeticType : u32 { + Addition = 0, + Subtraction = 1, + Multiplication = 2, + LeftShift = 3, + RightShift = 4, - /* These are not supported by Gateway's VM. */ - RegisterArithmeticType_LogicalAnd = 5, - RegisterArithmeticType_LogicalOr = 6, - RegisterArithmeticType_LogicalNot = 7, - RegisterArithmeticType_LogicalXor = 8, + // These are not supported by Gateway's VM. + LogicalAnd = 5, + LogicalOr = 6, + LogicalNot = 7, + LogicalXor = 8, - RegisterArithmeticType_None = 9, + None = 9, }; -enum StoreRegisterOffsetType : u32 { - StoreRegisterOffsetType_None = 0, - StoreRegisterOffsetType_Reg = 1, - StoreRegisterOffsetType_Imm = 2, - StoreRegisterOffsetType_MemReg = 3, - StoreRegisterOffsetType_MemImm = 4, - StoreRegisterOffsetType_MemImmReg = 5, +enum class StoreRegisterOffsetType : u32 { + None = 0, + Reg = 1, + Imm = 2, + MemReg = 3, + MemImm = 4, + MemImmReg = 5, }; -enum CompareRegisterValueType : u32 { - CompareRegisterValueType_MemoryRelAddr = 0, - CompareRegisterValueType_MemoryOfsReg = 1, - CompareRegisterValueType_RegisterRelAddr = 2, - CompareRegisterValueType_RegisterOfsReg = 3, - CompareRegisterValueType_StaticValue = 4, - CompareRegisterValueType_OtherRegister = 5, +enum class CompareRegisterValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + StaticValue = 4, + OtherRegister = 5, }; -enum SaveRestoreRegisterOpType : u32 { - SaveRestoreRegisterOpType_Restore = 0, - SaveRestoreRegisterOpType_Save = 1, - SaveRestoreRegisterOpType_ClearSaved = 2, - SaveRestoreRegisterOpType_ClearRegs = 3, +enum class SaveRestoreRegisterOpType : u32 { + Restore = 0, + Save = 1, + ClearSaved = 2, + ClearRegs = 3, }; -enum DebugLogValueType : u32 { - DebugLogValueType_MemoryRelAddr = 0, - DebugLogValueType_MemoryOfsReg = 1, - DebugLogValueType_RegisterRelAddr = 2, - DebugLogValueType_RegisterOfsReg = 3, - DebugLogValueType_RegisterValue = 4, +enum class DebugLogValueType : u32 { + MemoryRelAddr = 0, + MemoryOfsReg = 1, + RegisterRelAddr = 2, + RegisterOfsReg = 3, + RegisterValue = 4, }; union VmInt { @@ -247,26 +248,19 @@ struct DebugLogOpcode { u64 rel_address; }; -struct CheatVmOpcode { +struct UnrecognizedInstruction { CheatVmOpcodeType opcode; +}; + +struct CheatVmOpcode { 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; - }; + std::variant + opcode; }; class DmntCheatVm { @@ -285,50 +279,43 @@ public: virtual void CommandLog(std::string_view data) = 0; }; - constexpr static size_t MaximumProgramOpcodeCount = 0x400; - constexpr static size_t NumRegisters = 0x10; + static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; + static constexpr std::size_t NumRegisters = 0x10; + + explicit DmntCheatVm(std::unique_ptr callbacks); + ~DmntCheatVm(); + + std::size_t GetProgramSize() const { + return this->num_opcodes; + } + + bool LoadProgram(const std::vector& cheats); + void Execute(const CheatProcessMetadata& metadata); private: std::unique_ptr callbacks; - size_t num_opcodes = 0; - size_t instruction_ptr = 0; - size_t condition_depth = 0; + std::size_t num_opcodes = 0; + std::size_t instruction_ptr = 0; + std::size_t condition_depth = 0; bool decode_success = false; std::array program{}; std::array registers{}; std::array saved_values{}; - std::array loop_tops{}; + std::array loop_tops{}; -private: bool DecodeNextOpcode(CheatVmOpcode& out); void SkipConditionalBlock(); void ResetState(); - /* For implementing the DebugLog opcode. */ + // 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 From 2bddc0346815bf5e893ad9611d89dbb0d511e32f Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Sat, 21 Sep 2019 22:43:49 -0400 Subject: [PATCH 9/9] dmnt_cheat_vm: Default initialize structure values --- src/core/file_sys/patch_manager.cpp | 3 +- src/core/memory/dmnt_cheat_types.h | 30 +++--- src/core/memory/dmnt_cheat_vm.h | 144 ++++++++++++++-------------- 3 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 90b537834..df0ecb15c 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -278,8 +278,7 @@ std::optional> ReadCheatFileFromFolder( std::vector PatchManager::CreateCheatList( const Core::System& system, const std::array& build_id_) const { - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); + const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h index aa1264c32..bf68fa0fe 100644 --- a/src/core/memory/dmnt_cheat_types.h +++ b/src/core/memory/dmnt_cheat_types.h @@ -29,30 +29,30 @@ namespace Memory { struct MemoryRegionExtents { - u64 base; - u64 size; + 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; + 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; + std::array readable_name{}; + u32 num_opcodes{}; + std::array opcodes{}; }; struct CheatEntry { - bool enabled; - u32 cheat_id; - CheatDefinition definition; + bool enabled{}; + u32 cheat_id{}; + CheatDefinition definition{}; }; } // namespace Memory diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h index d1580d7f6..c36212cf1 100644 --- a/src/core/memory/dmnt_cheat_vm.h +++ b/src/core/memory/dmnt_cheat_vm.h @@ -136,131 +136,131 @@ union VmInt { }; struct StoreStaticOpcode { - u32 bit_width; - MemoryAccessType mem_type; - u32 offset_register; - u64 rel_address; - VmInt value; + 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; + 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; + bool start_loop{}; + u32 reg_index{}; + u32 num_iters{}; }; struct LoadRegisterStaticOpcode { - u32 reg_index; - u64 value; + u32 reg_index{}; + u64 value{}; }; struct LoadRegisterMemoryOpcode { - u32 bit_width; - MemoryAccessType mem_type; - u32 reg_index; - bool load_from_reg; - u64 rel_address; + 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; + 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; + u32 bit_width{}; + u32 reg_index{}; + RegisterArithmeticType math_type{}; + u32 value{}; }; struct BeginKeypressConditionalOpcode { - u32 key_mask; + 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; + 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; + 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; + 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; + u32 dst_index{}; + u32 src_index{}; + SaveRestoreRegisterOpType op_type{}; }; struct SaveRestoreRegisterMaskOpcode { - SaveRestoreRegisterOpType op_type; - std::array should_operate; + 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; + 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 UnrecognizedInstruction { - CheatVmOpcodeType opcode; + CheatVmOpcodeType opcode{}; }; struct CheatVmOpcode { - bool begin_conditional_block; + bool begin_conditional_block{}; std::variant - opcode; + opcode{}; }; class DmntCheatVm {