|
|
@@ -0,0 +1,1212 @@
|
|
|
+/*
|
|
|
+ * 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 <http://www.gnu.org/licenses/>.
|
|
|
+ */
|
|
|
+
|
|
|
+/*
|
|
|
+ * 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 {
|
|
|
+
|
|
|
+DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
|
|
|
+
|
|
|
+DmntCheatVm::~DmntCheatVm() = default;
|
|
|
+
|
|
|
+void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
|
|
|
+ callbacks->DebugLog(static_cast<u8>(log_id), value);
|
|
|
+}
|
|
|
+
|
|
|
+void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
|
|
|
+ if (auto store_static = std::get_if<StoreStaticOpcode>(&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<u32>(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<BeginConditionalOpcode>(&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<u32>(begin_cond->mem_type)));
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Cond Type: {:X}", static_cast<u32>(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<EndConditionalOpcode>(&opcode.opcode)) {
|
|
|
+ callbacks->CommandLog("Opcode: End Conditional");
|
|
|
+ } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&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 {
|
|
|
+ callbacks->CommandLog("Opcode: End Loop");
|
|
|
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
|
|
|
+ }
|
|
|
+ } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&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<LoadRegisterMemoryOpcode>(&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<u32>(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<StoreStaticToAddressOpcode>(&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));
|
|
|
+ }
|
|
|
+ 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<PerformArithmeticStaticOpcode>(&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<u32>(perform_math_static->math_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
|
|
|
+ } else if (auto begin_keypress_cond =
|
|
|
+ std::get_if<BeginKeypressConditionalOpcode>(&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<PerformArithmeticRegisterOpcode>(&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 {
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
|
|
|
+ }
|
|
|
+ } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&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:
|
|
|
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::Imm:
|
|
|
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::MemReg:
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::MemImm:
|
|
|
+ case StoreRegisterOffsetType::MemImmReg:
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&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<u32>(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:
|
|
|
+ callbacks->CommandLog("Comp Type: Other Register");
|
|
|
+ callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::MemoryRelAddr:
|
|
|
+ callbacks->CommandLog("Comp Type: Memory Relative Address");
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::MemoryOfsReg:
|
|
|
+ callbacks->CommandLog("Comp Type: Memory Offset Register");
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
|
|
|
+ break;
|
|
|
+ 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:
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&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<u32>(save_restore_reg->op_type)));
|
|
|
+ } else if (auto save_restore_regmask =
|
|
|
+ std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
|
|
|
+ callbacks->CommandLog("Opcode: Save or Restore Register Mask");
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Op Type: {:d}", static_cast<u32>(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]));
|
|
|
+ }
|
|
|
+ } else if (auto debug_log = std::get_if<DebugLogOpcode>(&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<u32>(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:
|
|
|
+ callbacks->CommandLog("Val Type: Memory Relative Address");
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
|
|
|
+ break;
|
|
|
+ case DebugLogValueType::MemoryOfsReg:
|
|
|
+ callbacks->CommandLog("Val Type: Memory Offset Register");
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
|
|
|
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
|
|
|
+ break;
|
|
|
+ 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:
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+ } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
|
|
|
+ callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+DmntCheatVm::Callbacks::~Callbacks() = default;
|
|
|
+
|
|
|
+bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
|
|
|
+ // If we've ever seen a decode failure, return false.
|
|
|
+ bool valid = decode_success;
|
|
|
+ CheatVmOpcode opcode = {};
|
|
|
+ SCOPE_EXIT({
|
|
|
+ decode_success &= valid;
|
|
|
+ if (valid) {
|
|
|
+ out = opcode;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Helper function for getting instruction dwords.
|
|
|
+ const auto GetNextDword = [&] {
|
|
|
+ if (instruction_ptr >= num_opcodes) {
|
|
|
+ valid = false;
|
|
|
+ return static_cast<u32>(0);
|
|
|
+ }
|
|
|
+ return program[instruction_ptr++];
|
|
|
+ };
|
|
|
+
|
|
|
+ // 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 = static_cast<u8>(first_dword);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ val.bit16 = static_cast<u16>(first_dword);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ val.bit32 = first_dword;
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword());
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return val;
|
|
|
+ };
|
|
|
+
|
|
|
+ // Read opcode.
|
|
|
+ const u32 first_dword = GetNextDword();
|
|
|
+ if (!valid) {
|
|
|
+ return valid;
|
|
|
+ }
|
|
|
+
|
|
|
+ auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF));
|
|
|
+ if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) {
|
|
|
+ opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
|
|
|
+ ((first_dword >> 24) & 0xF));
|
|
|
+ }
|
|
|
+ if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) {
|
|
|
+ opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
|
|
|
+ ((first_dword >> 20) & 0xF));
|
|
|
+ }
|
|
|
+
|
|
|
+ // detect condition start.
|
|
|
+ switch (opcode_type) {
|
|
|
+ case CheatVmOpcodeType::BeginConditionalBlock:
|
|
|
+ case CheatVmOpcodeType::BeginKeypressConditionalBlock:
|
|
|
+ case CheatVmOpcodeType::BeginRegisterConditionalBlock:
|
|
|
+ opcode.begin_conditional_block = true;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ opcode.begin_conditional_block = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (opcode_type) {
|
|
|
+ case CheatVmOpcodeType::StoreStatic: {
|
|
|
+ StoreStaticOpcode store_static{};
|
|
|
+ // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
|
|
|
+ // Read additional words.
|
|
|
+ const u32 second_dword = GetNextDword();
|
|
|
+ store_static.bit_width = (first_dword >> 24) & 0xF;
|
|
|
+ store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
|
|
|
+ store_static.offset_register = ((first_dword >> 16) & 0xF);
|
|
|
+ store_static.rel_address =
|
|
|
+ (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
|
|
|
+ store_static.value = GetNextVmInt(store_static.bit_width);
|
|
|
+ opcode.opcode = store_static;
|
|
|
+ } break;
|
|
|
+ case CheatVmOpcodeType::BeginConditionalBlock: {
|
|
|
+ BeginConditionalOpcode begin_cond{};
|
|
|
+ // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
|
|
|
+ // Read additional words.
|
|
|
+ const u32 second_dword = GetNextDword();
|
|
|
+ begin_cond.bit_width = (first_dword >> 24) & 0xF;
|
|
|
+ begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
|
|
|
+ begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
|
|
|
+ begin_cond.rel_address =
|
|
|
+ (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(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!
|
|
|
+ opcode.opcode = EndConditionalOpcode{};
|
|
|
+ } break;
|
|
|
+ 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 (ctrl_loop.start_loop) {
|
|
|
+ ctrl_loop.num_iters = GetNextDword();
|
|
|
+ }
|
|
|
+ opcode.opcode = ctrl_loop;
|
|
|
+ } break;
|
|
|
+ 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<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
|
|
|
+ opcode.opcode = ldr_static;
|
|
|
+ } break;
|
|
|
+ case CheatVmOpcodeType::LoadRegisterMemory: {
|
|
|
+ LoadRegisterMemoryOpcode ldr_memory{};
|
|
|
+ // 5TMRI0AA AAAAAAAA
|
|
|
+ // Read additional words.
|
|
|
+ const u32 second_dword = GetNextDword();
|
|
|
+ ldr_memory.bit_width = (first_dword >> 24) & 0xF;
|
|
|
+ ldr_memory.mem_type = static_cast<MemoryAccessType>((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<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
|
|
|
+ opcode.opcode = ldr_memory;
|
|
|
+ } break;
|
|
|
+ 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<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
|
|
|
+ opcode.opcode = str_static;
|
|
|
+ } break;
|
|
|
+ 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<RegisterArithmeticType>((first_dword >> 12) & 0xF);
|
|
|
+ perform_math_static.value = GetNextDword();
|
|
|
+ opcode.opcode = perform_math_static;
|
|
|
+ } break;
|
|
|
+ case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
|
|
|
+ BeginKeypressConditionalOpcode begin_keypress_cond{};
|
|
|
+ // 8kkkkkkk
|
|
|
+ // Just parse the mask.
|
|
|
+ begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
|
|
|
+ } break;
|
|
|
+ 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<RegisterArithmeticType>((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 {
|
|
|
+ perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
|
|
|
+ }
|
|
|
+ opcode.opcode = perform_math_reg;
|
|
|
+ } break;
|
|
|
+ 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<StoreRegisterOffsetType>(((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:
|
|
|
+ str_register.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::MemReg:
|
|
|
+ str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::MemImm:
|
|
|
+ case StoreRegisterOffsetType::MemImmReg:
|
|
|
+ str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ str_register.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ str_register.ofs_type = StoreRegisterOffsetType::None;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ opcode.opcode = str_register;
|
|
|
+ } break;
|
|
|
+ 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<ConditionalComparisonType>((first_dword >> 16) & 0xF);
|
|
|
+ begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
|
|
|
+ begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
|
|
|
+
|
|
|
+ switch (begin_reg_cond.comp_type) {
|
|
|
+ case CompareRegisterValueType::StaticValue:
|
|
|
+ begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width);
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::OtherRegister:
|
|
|
+ begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::MemoryRelAddr:
|
|
|
+ begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ begin_reg_cond.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::MemoryOfsReg:
|
|
|
+ begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::RegisterRelAddr:
|
|
|
+ begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
|
|
|
+ begin_reg_cond.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ 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: {
|
|
|
+ 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<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
|
|
|
+ opcode.opcode = save_restore_reg;
|
|
|
+ } break;
|
|
|
+ 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<SaveRestoreRegisterOpType>((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: {
|
|
|
+ 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<DebugLogValueType>((first_dword >> 8) & 0xF);
|
|
|
+
|
|
|
+ switch (debug_log.val_type) {
|
|
|
+ case DebugLogValueType::RegisterValue:
|
|
|
+ debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
|
|
|
+ break;
|
|
|
+ case DebugLogValueType::MemoryRelAddr:
|
|
|
+ debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ debug_log.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ case DebugLogValueType::MemoryOfsReg:
|
|
|
+ debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
|
|
|
+ debug_log.ofs_reg_index = (first_dword & 0xF);
|
|
|
+ break;
|
|
|
+ case DebugLogValueType::RegisterRelAddr:
|
|
|
+ debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
|
|
|
+ debug_log.rel_address =
|
|
|
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
|
|
|
+ break;
|
|
|
+ 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:
|
|
|
+ default:
|
|
|
+ // Unrecognized instruction cannot be decoded.
|
|
|
+ valid = false;
|
|
|
+ opcode.opcode = UnrecognizedInstruction{opcode_type};
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // End decoding.
|
|
|
+ return valid;
|
|
|
+}
|
|
|
+
|
|
|
+void DmntCheatVm::SkipConditionalBlock() {
|
|
|
+ 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 (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.
|
|
|
+ if (skip_opcode.begin_conditional_block) {
|
|
|
+ condition_depth++;
|
|
|
+ } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) {
|
|
|
+ condition_depth--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 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");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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() {
|
|
|
+ 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<CheatEntry>& entries) {
|
|
|
+ // Reset opcode count.
|
|
|
+ num_opcodes = 0;
|
|
|
+
|
|
|
+ for (std::size_t i = 0; i < entries.size(); i++) {
|
|
|
+ if (entries[i].enabled) {
|
|
|
+ // Bounds check.
|
|
|
+ if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
|
|
|
+ num_opcodes = 0;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
|
|
|
+ program[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();
|
|
|
+
|
|
|
+ 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<u32>(kDown & 0x0FFFFFFF)));
|
|
|
+
|
|
|
+ // Clear VM state.
|
|
|
+ ResetState();
|
|
|
+
|
|
|
+ // Loop until program finishes.
|
|
|
+ while (DecodeNextOpcode(cur_opcode)) {
|
|
|
+ callbacks->CommandLog(
|
|
|
+ fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
|
|
|
+
|
|
|
+ for (std::size_t i = 0; i < NumRegisters; i++) {
|
|
|
+ callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (std::size_t i = 0; i < NumRegisters; i++) {
|
|
|
+ callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
|
|
|
+ }
|
|
|
+ LogOpcode(cur_opcode);
|
|
|
+
|
|
|
+ // Increment conditional depth, if relevant.
|
|
|
+ if (cur_opcode.begin_conditional_block) {
|
|
|
+ condition_depth++;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (auto store_static = std::get_if<StoreStaticOpcode>(&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, store_static->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&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 (store_static->bit_width) {
|
|
|
+ case 1:
|
|
|
+ case 2:
|
|
|
+ case 4:
|
|
|
+ case 8:
|
|
|
+ callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // Check against condition.
|
|
|
+ u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width);
|
|
|
+ bool cond_met = false;
|
|
|
+ switch (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) {
|
|
|
+ SkipConditionalBlock();
|
|
|
+ }
|
|
|
+ } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&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--;
|
|
|
+ }
|
|
|
+ } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&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.
|
|
|
+ registers[ctrl_loop->reg_index]--;
|
|
|
+ if (registers[ctrl_loop->reg_index] != 0) {
|
|
|
+ instruction_ptr = loop_tops[ctrl_loop->reg_index];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&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<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) {
|
|
|
+ // Choose source address.
|
|
|
+ u64 src_address;
|
|
|
+ if (ldr_memory->load_from_reg) {
|
|
|
+ src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address;
|
|
|
+ } else {
|
|
|
+ src_address =
|
|
|
+ GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address);
|
|
|
+ }
|
|
|
+ // 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, ®isters[ldr_memory->reg_index],
|
|
|
+ ldr_memory->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&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 (str_static->bit_width) {
|
|
|
+ case 1:
|
|
|
+ case 2:
|
|
|
+ case 4:
|
|
|
+ case 8:
|
|
|
+ callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // Increment register if relevant.
|
|
|
+ if (str_static->increment_reg) {
|
|
|
+ registers[str_static->reg_index] += str_static->bit_width;
|
|
|
+ }
|
|
|
+ } else if (auto perform_math_static =
|
|
|
+ std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) {
|
|
|
+ // Do requested math.
|
|
|
+ switch (perform_math_static->math_type) {
|
|
|
+ case RegisterArithmeticType::Addition:
|
|
|
+ registers[perform_math_static->reg_index] +=
|
|
|
+ static_cast<u64>(perform_math_static->value);
|
|
|
+ break;
|
|
|
+ case RegisterArithmeticType::Subtraction:
|
|
|
+ registers[perform_math_static->reg_index] -=
|
|
|
+ static_cast<u64>(perform_math_static->value);
|
|
|
+ break;
|
|
|
+ case RegisterArithmeticType::Multiplication:
|
|
|
+ registers[perform_math_static->reg_index] *=
|
|
|
+ static_cast<u64>(perform_math_static->value);
|
|
|
+ break;
|
|
|
+ case RegisterArithmeticType::LeftShift:
|
|
|
+ registers[perform_math_static->reg_index] <<=
|
|
|
+ static_cast<u64>(perform_math_static->value);
|
|
|
+ break;
|
|
|
+ case RegisterArithmeticType::RightShift:
|
|
|
+ registers[perform_math_static->reg_index] >>=
|
|
|
+ static_cast<u64>(perform_math_static->value);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ // Do not handle extensions here.
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ // Apply bit width.
|
|
|
+ switch (perform_math_static->bit_width) {
|
|
|
+ case 1:
|
|
|
+ registers[perform_math_static->reg_index] =
|
|
|
+ static_cast<u8>(registers[perform_math_static->reg_index]);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ registers[perform_math_static->reg_index] =
|
|
|
+ static_cast<u16>(registers[perform_math_static->reg_index]);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ registers[perform_math_static->reg_index] =
|
|
|
+ static_cast<u32>(registers[perform_math_static->reg_index]);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ registers[perform_math_static->reg_index] =
|
|
|
+ static_cast<u64>(registers[perform_math_static->reg_index]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (auto begin_keypress_cond =
|
|
|
+ std::get_if<BeginKeypressConditionalOpcode>(&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();
|
|
|
+ }
|
|
|
+ } else if (auto perform_math_reg =
|
|
|
+ std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
|
|
|
+ const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index];
|
|
|
+ const u64 operand_2_value =
|
|
|
+ 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 (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 (perform_math_reg->bit_width) {
|
|
|
+ case 1:
|
|
|
+ res_val = static_cast<u8>(res_val);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ res_val = static_cast<u16>(res_val);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ res_val = static_cast<u32>(res_val);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ res_val = static_cast<u64>(res_val);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save to register.
|
|
|
+ registers[perform_math_reg->dst_reg_index] = res_val;
|
|
|
+ } else if (auto str_register =
|
|
|
+ std::get_if<StoreRegisterToAddressOpcode>(&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 += registers[str_register->ofs_reg_index];
|
|
|
+ break;
|
|
|
+ case StoreRegisterOffsetType::Imm:
|
|
|
+ dst_address += str_register->rel_address;
|
|
|
+ break;
|
|
|
+ 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, str_register->mem_type,
|
|
|
+ str_register->rel_address);
|
|
|
+ break;
|
|
|
+ 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 (str_register->bit_width) {
|
|
|
+ case 1:
|
|
|
+ case 2:
|
|
|
+ case 4:
|
|
|
+ case 8:
|
|
|
+ callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Increment register if relevant.
|
|
|
+ if (str_register->increment_reg) {
|
|
|
+ registers[str_register->addr_reg_index] += str_register->bit_width;
|
|
|
+ }
|
|
|
+ } else if (auto begin_reg_cond =
|
|
|
+ std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) {
|
|
|
+ // Get value from register.
|
|
|
+ u64 src_value = 0;
|
|
|
+ switch (begin_reg_cond->bit_width) {
|
|
|
+ case 1:
|
|
|
+ src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ src_value =
|
|
|
+ static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] &
|
|
|
+ 0xFFFFFFFFFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Read value from memory.
|
|
|
+ u64 cond_value = 0;
|
|
|
+ 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<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ cond_value =
|
|
|
+ static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ cond_value =
|
|
|
+ static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] &
|
|
|
+ 0xFFFFFFFFFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ u64 cond_address = 0;
|
|
|
+ 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 =
|
|
|
+ registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address;
|
|
|
+ break;
|
|
|
+ case CompareRegisterValueType::RegisterOfsReg:
|
|
|
+ cond_address = registers[begin_reg_cond->addr_reg_index] +
|
|
|
+ registers[begin_reg_cond->ofs_reg_index];
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ switch (begin_reg_cond->bit_width) {
|
|
|
+ case 1:
|
|
|
+ case 2:
|
|
|
+ case 4:
|
|
|
+ case 8:
|
|
|
+ callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check against condition.
|
|
|
+ bool cond_met = false;
|
|
|
+ switch (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) {
|
|
|
+ SkipConditionalBlock();
|
|
|
+ }
|
|
|
+ } else if (auto save_restore_reg =
|
|
|
+ std::get_if<SaveRestoreRegisterOpcode>(&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:
|
|
|
+ saved_values[save_restore_reg->dst_index] = 0ul;
|
|
|
+ break;
|
|
|
+ case SaveRestoreRegisterOpType::Save:
|
|
|
+ saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index];
|
|
|
+ break;
|
|
|
+ case SaveRestoreRegisterOpType::Restore:
|
|
|
+ default:
|
|
|
+ registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else if (auto save_restore_regmask =
|
|
|
+ std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) {
|
|
|
+ // Save or restore register mask.
|
|
|
+ u64* src;
|
|
|
+ u64* dst;
|
|
|
+ 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:
|
|
|
+ default:
|
|
|
+ src = registers.data();
|
|
|
+ dst = saved_values.data();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ 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:
|
|
|
+ default:
|
|
|
+ dst[i] = src[i];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
|
|
|
+ // Read value from memory.
|
|
|
+ u64 log_value = 0;
|
|
|
+ if (debug_log->val_type == DebugLogValueType::RegisterValue) {
|
|
|
+ switch (debug_log->bit_width) {
|
|
|
+ case 1:
|
|
|
+ log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul);
|
|
|
+ break;
|
|
|
+ case 2:
|
|
|
+ log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul);
|
|
|
+ break;
|
|
|
+ case 4:
|
|
|
+ log_value =
|
|
|
+ static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ case 8:
|
|
|
+ log_value = static_cast<u64>(registers[debug_log->val_reg_index] &
|
|
|
+ 0xFFFFFFFFFFFFFFFFul);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ u64 val_address = 0;
|
|
|
+ switch (debug_log->val_type) {
|
|
|
+ case DebugLogValueType::MemoryRelAddr:
|
|
|
+ val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
|
|
|
+ debug_log->rel_address);
|
|
|
+ break;
|
|
|
+ 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 =
|
|
|
+ registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index];
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ switch (debug_log->bit_width) {
|
|
|
+ case 1:
|
|
|
+ case 2:
|
|
|
+ case 4:
|
|
|
+ case 8:
|
|
|
+ callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Log value.
|
|
|
+ DebugLog(debug_log->log_id, log_value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace Memory
|