From 853a57e4d4496727415964a35c4c8377e63959c3 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 5 Mar 2019 01:39:20 -0800 Subject: [PATCH] dmnt-cheat: Support nested conditionals in VM --- stratosphere/dmnt/source/dmnt_cheat_vm.cpp | 57 ++++++++++++++++++---- stratosphere/dmnt/source/dmnt_cheat_vm.hpp | 2 + stratosphere/dmnt/source/dmnt_results.hpp | 4 +- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp index 269b4a437..eded284cf 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp @@ -204,6 +204,17 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); } + /* detect condition start. */ + switch (opcode.opcode) { + case CheatVmOpcodeType_BeginConditionalBlock: + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + switch (opcode.opcode) { case CheatVmOpcodeType_StoreStatic: { @@ -357,16 +368,32 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { } void DmntCheatVm::SkipConditionalBlock() { - CheatVmOpcode skip_opcode; - while (this->DecodeNextOpcode(&skip_opcode)) { - /* Decode instructions until we see end of 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... */ - if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) { - break; + if (this->condition_depth > 0) { + /* We want to continue until we're out of the current block. */ + size_t desired_depth = this->condition_depth - 1; + + CheatVmOpcode skip_opcode; + while (this->DecodeNextOpcode(&skip_opcode) && this->condition_depth > desired_depth) { + /* 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. */ + fatalSimple(ResultDmntCheatVmInvalidCondDepth); } } @@ -402,6 +429,7 @@ void DmntCheatVm::ResetState() { this->loop_tops[i] = 0; } this->instruction_ptr = 0; + this->condition_depth = 0; this->decode_success = true; } @@ -452,6 +480,11 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { } 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: { @@ -511,7 +544,11 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { } break; case CheatVmOpcodeType_EndConditionalBlock: - /* There is nothing to do here. Just move on to the next instruction. */ + /* 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) { diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp b/stratosphere/dmnt/source/dmnt_cheat_vm.hpp index e9cffa8bc..469e36699 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp +++ b/stratosphere/dmnt/source/dmnt_cheat_vm.hpp @@ -164,6 +164,7 @@ struct StoreRegisterToAddressOpcode { struct CheatVmOpcode { CheatVmOpcodeType opcode; + bool begin_conditional_block; union { StoreStaticOpcode store_static; BeginConditionalOpcode begin_cond; @@ -186,6 +187,7 @@ class DmntCheatVm { 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}; diff --git a/stratosphere/dmnt/source/dmnt_results.hpp b/stratosphere/dmnt/source/dmnt_results.hpp index f4e908a12..42b43df89 100644 --- a/stratosphere/dmnt/source/dmnt_results.hpp +++ b/stratosphere/dmnt/source/dmnt_results.hpp @@ -33,4 +33,6 @@ static constexpr Result ResultDmntCheatInvalidCheat = MAKERESULT(Module_Dmnt, 6 static constexpr Result ResultDmntCheatInvalidFreezeWidth = MAKERESULT(Module_Dmnt, 6600); static constexpr Result ResultDmntCheatAddressAlreadyFrozen = MAKERESULT(Module_Dmnt, 6601); static constexpr Result ResultDmntCheatAddressNotFrozen = MAKERESULT(Module_Dmnt, 6602); -static constexpr Result ResultDmntCheatTooManyFrozenAddresses = MAKERESULT(Module_Dmnt, 6603); \ No newline at end of file +static constexpr Result ResultDmntCheatTooManyFrozenAddresses = MAKERESULT(Module_Dmnt, 6603); + +static constexpr Result ResultDmntCheatVmInvalidCondDepth = MAKERESULT(Module_Dmnt, 6700); \ No newline at end of file