/* * Copyright (c) 2018-2020 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 <http://www.gnu.org/licenses/>. */ #pragma once #include <stratosphere.hpp> namespace ams::dmnt::cheat::impl { 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, CheatVmOpcodeType_ReadWriteStaticRegister = 0xC3, /* 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_PauseProcess = 0xFF0, CheatVmOpcodeType_ResumeProcess = 0xFF1, 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; bool should_operate[0x10]; }; struct ReadWriteStaticRegisterOpcode { u32 static_idx; u32 idx; }; 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; ReadWriteStaticRegisterOpcode rw_static_reg; DebugLogOpcode debug_log; }; }; class CheatVirtualMachine { public: constexpr static size_t MaximumProgramOpcodeCount = 0x400; constexpr static size_t NumRegisters = 0x10; constexpr static size_t NumReadableStaticRegisters = 0x80; constexpr static size_t NumWritableStaticRegisters = 0x80; constexpr static size_t NumStaticRegisters = NumReadableStaticRegisters + NumWritableStaticRegisters; private: size_t num_opcodes = 0; size_t instruction_ptr = 0; size_t condition_depth = 0; bool decode_success = false; u32 program[MaximumProgramOpcodeCount] = {0}; u64 registers[NumRegisters] = {0}; u64 saved_values[NumRegisters] = {0}; u64 static_registers[NumStaticRegisters] = {0}; size_t loop_tops[NumRegisters] = {0}; 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. */ void OpenDebugLogFile(); void CloseDebugLogFile(); void LogToDebugFile(const char *format, ...); 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: CheatVirtualMachine() { } size_t GetProgramSize() { return this->num_opcodes; } bool LoadProgram(const CheatEntry *cheats, size_t num_cheats); void Execute(const CheatProcessMetadata *metadata); u64 GetStaticRegister(size_t which) const { return this->static_registers[which]; } void SetStaticRegister(size_t which, u64 value) { this->static_registers[which] = value; } void ResetStaticRegisters() { std::memset(this->static_registers, 0, sizeof(this->static_registers)); } #ifdef DMNT_CHEAT_VM_DEBUG_LOG private: fs::FileHandle debug_log_file; s64 debug_log_file_offset; bool has_debug_log_file; char debug_log_format_buf[0x100]; #endif }; }