From 470466b31b3ddc198482041e34e78095d467054f Mon Sep 17 00:00:00 2001
From: Zach Hilman
Date: Thu, 30 May 2019 19:32:46 -0400
Subject: 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(+)
(limited to 'src')
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
--
cgit v1.2.3
From 12aa127df3826857149bfc4b787cfb7df3fdcafe Mon Sep 17 00:00:00 2001
From: Zach Hilman
Date: Thu, 30 May 2019 19:34:02 -0400
Subject: 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
(limited to 'src')
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
--
cgit v1.2.3
From 7d41c1f52390abb47e67d3fc43310e9d87fbd862 Mon Sep 17 00:00:00 2001
From: Zach Hilman
Date: Thu, 30 May 2019 19:35:03 -0400
Subject: 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
(limited to 'src')
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