code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "common/common_types.h"

namespace Core::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 aslr_extents{};
    std::array<u8, 0x20> main_nso_build_id{};
};

struct CheatDefinition {
    std::array<char, 0x40> readable_name{};
    u32 num_opcodes{};
    std::array<u32, 0x100> opcodes{};
};

struct CheatEntry {
    bool enabled{};
    u32 cheat_id{};
    CheatDefinition definition{};
};

} // namespace Core::Memory


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/memory/dmnt_cheat_vm.h"

namespace Core::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 (std::holds_alternative<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 rw_static_reg = std::get_if<ReadWriteStaticRegisterOpcode>(&opcode.opcode)) {
        callbacks->CommandLog("Opcode: Read/Write Static Register");
        if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
            callbacks->CommandLog("Op Type: ReadStaticRegister");
        } else {
            callbacks->CommandLog("Op Type: WriteStaticRegister");
        }
        callbacks->CommandLog(fmt::format("Reg Idx   {:X}", rw_static_reg->idx));
        callbacks->CommandLog(fmt::format("Stc Idx   {:X}", rw_static_reg->static_idx));
    } 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: {
        // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
        // Read additional words.
        const u32 second_dword = GetNextDword();
        const u32 bit_width = (first_dword >> 24) & 0xF;

        opcode.opcode = StoreStaticOpcode{
            .bit_width = bit_width,
            .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
            .offset_register = (first_dword >> 16) & 0xF,
            .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
            .value = GetNextVmInt(bit_width),
        };
    } break;
    case CheatVmOpcodeType::BeginConditionalBlock: {
        // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
        // Read additional words.
        const u32 second_dword = GetNextDword();
        const u32 bit_width = (first_dword >> 24) & 0xF;

        opcode.opcode = BeginConditionalOpcode{
            .bit_width = bit_width,
            .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
            .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
            .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
            .value = GetNextVmInt(bit_width),
        };
    } break;
    case CheatVmOpcodeType::EndConditionalBlock: {
        // 20000000
        opcode.opcode = EndConditionalOpcode{
            .is_else = ((first_dword >> 24) & 0xf) == 1,
        };
    } break;
    case CheatVmOpcodeType::ControlLoop: {
        // 300R0000 VVVVVVVV
        // 310R0000
        // Parse register, whether loop start or loop end.
        ControlLoopOpcode ctrl_loop{
            .start_loop = ((first_dword >> 24) & 0xF) == 0,
            .reg_index = (first_dword >> 20) & 0xF,
            .num_iters = 0,
        };

        // Read number of iters if loop start.
        if (ctrl_loop.start_loop) {
            ctrl_loop.num_iters = GetNextDword();
        }
        opcode.opcode = ctrl_loop;
    } break;
    case CheatVmOpcodeType::LoadRegisterStatic: {
        // 400R0000 VVVVVVVV VVVVVVVV
        // Read additional words.
        opcode.opcode = LoadRegisterStaticOpcode{
            .reg_index = (first_dword >> 16) & 0xF,
            .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
        };
    } break;
    case CheatVmOpcodeType::LoadRegisterMemory: {
        // 5TMRI0AA AAAAAAAA
        // Read additional words.
        const u32 second_dword = GetNextDword();
        opcode.opcode = LoadRegisterMemoryOpcode{
            .bit_width = (first_dword >> 24) & 0xF,
            .mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF),
            .reg_index = ((first_dword >> 16) & 0xF),
            .load_from_reg = ((first_dword >> 12) & 0xF) != 0,
            .rel_address = (static_cast<u64>(first_dword & 0xFF) << 32) | second_dword,
        };
    } break;
    case CheatVmOpcodeType::StoreStaticToAddress: {
        // 6T0RIor0 VVVVVVVV VVVVVVVV
        // Read additional words.
        opcode.opcode = StoreStaticToAddressOpcode{
            .bit_width = (first_dword >> 24) & 0xF,
            .reg_index = (first_dword >> 16) & 0xF,
            .increment_reg = ((first_dword >> 12) & 0xF) != 0,
            .add_offset_reg = ((first_dword >> 8) & 0xF) != 0,
            .offset_reg_index = (first_dword >> 4) & 0xF,
            .value = (static_cast<u64>(GetNextDword()) << 32) | GetNextDword(),
        };
    } break;
    case CheatVmOpcodeType::PerformArithmeticStatic: {
        // 7T0RC000 VVVVVVVV
        // Read additional words.
        opcode.opcode = PerformArithmeticStaticOpcode{
            .bit_width = (first_dword >> 24) & 0xF,
            .reg_index = ((first_dword >> 16) & 0xF),
            .math_type = static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF),
            .value = GetNextDword(),
        };
    } break;
    case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
        // 8kkkkkkk
        // Just parse the mask.
        opcode.opcode = BeginKeypressConditionalOpcode{
            .key_mask = first_dword & 0x0FFFFFFF,
        };
    } break;
    case CheatVmOpcodeType::PerformArithmeticRegister: {
        // 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
        PerformArithmeticRegisterOpcode perform_math_reg{
            .bit_width = (first_dword >> 24) & 0xF,
            .math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF),
            .dst_reg_index = (first_dword >> 16) & 0xF,
            .src_reg_1_index = (first_dword >> 12) & 0xF,
            .src_reg_2_index = 0,
            .has_immediate = ((first_dword >> 8) & 0xF) != 0,
            .value = {},
        };
        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: {
        // 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)
        StoreRegisterToAddressOpcode str_register{
            .bit_width = (first_dword >> 24) & 0xF,
            .str_reg_index = (first_dword >> 20) & 0xF,
            .addr_reg_index = (first_dword >> 16) & 0xF,
            .increment_reg = ((first_dword >> 12) & 0xF) != 0,
            .ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF)),
            .mem_type = MemoryAccessType::MainNso,
            .ofs_reg_index = (first_dword >> 4) & 0xF,
            .rel_address = 0,
        };
        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) << 32) | 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) << 32) | GetNextDword();
            break;
        default:
            str_register.ofs_type = StoreRegisterOffsetType::None;
            break;
        }
        opcode.opcode = str_register;
    } break;
    case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
        // C0TcSX##
        // C0TcS0Ma aaaaaaaa
        // C0TcS1Mr
        // C0TcS2Ra aaaaaaaa
        // C0TcS3Rr
        // C0TcS400 VVVVVVVV (VVVVVVVV)
        // C0TcS5X0
        // C0 = opcode 0xC0
        // T = bit width
        // c = condition type.
        // S = source register.
        // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
        // register,
        //     2 = register with relative offset, 3 = register with offset register, 4 = static
        // value, 5 = other register.
        // M = memory type.
        // R = address register.
        // a = relative address.
        // r = offset register.
        // X = other register.
        // V = value.

        BeginRegisterConditionalOpcode begin_reg_cond{
            .bit_width = (first_dword >> 20) & 0xF,
            .cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF),
            .val_reg_index = (first_dword >> 12) & 0xF,
            .comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF),
            .mem_type = MemoryAccessType::MainNso,
            .addr_reg_index = 0,
            .other_reg_index = 0,
            .ofs_reg_index = 0,
            .rel_address = 0,
            .value = {},
        };

        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) << 32) | 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) << 32) | 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: {
        // 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.opcode = SaveRestoreRegisterOpcode{
            .dst_index = (first_dword >> 16) & 0xF,
            .src_index = (first_dword >> 8) & 0xF,
            .op_type = static_cast<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.
        SaveRestoreRegisterMaskOpcode save_restore_regmask{
            .op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF),
            .should_operate = {},
        };
        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::ReadWriteStaticRegister: {
        // C3000XXx
        // C3 = opcode 0xC3.
        // XX = static register index.
        // x  = register index.
        opcode.opcode = ReadWriteStaticRegisterOpcode{
            .static_idx = (first_dword >> 4) & 0xFF,
            .idx = first_dword & 0xF,
        };
    } break;
    case CheatVmOpcodeType::PauseProcess: {
        /* FF0????? */
        /* FF0 = opcode 0xFF0 */
        /* Pauses the current process. */
        opcode.opcode = PauseProcessOpcode{};
    } break;
    case CheatVmOpcodeType::ResumeProcess: {
        /* FF0????? */
        /* FF0 = opcode 0xFF0 */
        /* Pauses the current process. */
        opcode.opcode = ResumeProcessOpcode{};
    } 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.
        DebugLogOpcode debug_log{
            .bit_width = (first_dword >> 16) & 0xF,
            .log_id = (first_dword >> 12) & 0xF,
            .val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF),
            .mem_type = MemoryAccessType::MainNso,
            .addr_reg_index = 0,
            .val_reg_index = 0,
            .ofs_reg_index = 0,
            .rel_address = 0,
        };

        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) << 32) | 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) << 32) | 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(bool is_if) {
    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 (auto end_cond = std::get_if<EndConditionalOpcode>(&skip_opcode.opcode)) {
                if (!end_cond->is_else) {
                    condition_depth--;
                } else if (is_if && condition_depth - 1 == desired_depth) {
                    break;
                }
            }
        }
    } 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;
    case MemoryAccessType::Alias:
        return metadata.alias_extents.base + rel_address;
    case MemoryAccessType::Aslr:
        return metadata.aslr_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->MemoryWriteUnsafe(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 (begin_cond->bit_width) {
            case 1:
            case 2:
            case 4:
            case 8:
                callbacks->MemoryReadUnsafe(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(true);
            }
        } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
            if (end_cond->is_else) {
                /* Skip to the end of the conditional block. */
                this->SkipConditionalBlock(false);
            } else {
                /* 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->MemoryReadUnsafe(src_address, &registers[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->MemoryWriteUnsafe(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(true);
            }
        } 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->MemoryWriteUnsafe(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->MemoryReadUnsafe(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(true);
            }
        } 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 = saved_values.data();
                dst = registers.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 rw_static_reg =
                       std::get_if<ReadWriteStaticRegisterOpcode>(&cur_opcode.opcode)) {
            if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
                // Load a register with a static register.
                registers[rw_static_reg->idx] = static_registers[rw_static_reg->static_idx];
            } else {
                // Store a register to a static register.
                static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
            }
        } else if (std::holds_alternative<PauseProcessOpcode>(cur_opcode.opcode)) {
            callbacks->PauseProcess();
        } else if (std::holds_alternative<ResumeProcessOpcode>(cur_opcode.opcode)) {
            callbacks->ResumeProcess();
        } 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->MemoryReadUnsafe(val_address, &log_value, debug_log->bit_width);
                    break;
                }
            }

            // Log value.
            DebugLog(debug_log->log_id, log_value);
        }
    }
}

} // namespace Core::Memory

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <variant>
#include <vector>
#include <fmt/printf.h>
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"

namespace Core::Memory {

enum class CheatVmOpcodeType : u32 {
    StoreStatic = 0,
    BeginConditionalBlock = 1,
    EndConditionalBlock = 2,
    ControlLoop = 3,
    LoadRegisterStatic = 4,
    LoadRegisterMemory = 5,
    StoreStaticToAddress = 6,
    PerformArithmeticStatic = 7,
    BeginKeypressConditionalBlock = 8,

    // These are not implemented by Gateway's VM.
    PerformArithmeticRegister = 9,
    StoreRegisterToAddress = 10,
    Reserved11 = 11,

    // This is a meta entry, and not a real opcode.
    // This is to facilitate multi-nybble instruction decoding.
    ExtendedWidth = 12,

    // Extended width opcodes.
    BeginRegisterConditionalBlock = 0xC0,
    SaveRestoreRegister = 0xC1,
    SaveRestoreRegisterMask = 0xC2,
    ReadWriteStaticRegister = 0xC3,

    // This is a meta entry, and not a real opcode.
    // This is to facilitate multi-nybble instruction decoding.
    DoubleExtendedWidth = 0xF0,

    // Double-extended width opcodes.
    PauseProcess = 0xFF0,
    ResumeProcess = 0xFF1,
    DebugLog = 0xFFF,
};

enum class MemoryAccessType : u32 {
    MainNso = 0,
    Heap = 1,
    Alias = 2,
    Aslr = 3,
};

enum class ConditionalComparisonType : u32 {
    GT = 1,
    GE = 2,
    LT = 3,
    LE = 4,
    EQ = 5,
    NE = 6,
};

enum class RegisterArithmeticType : u32 {
    Addition = 0,
    Subtraction = 1,
    Multiplication = 2,
    LeftShift = 3,
    RightShift = 4,

    // These are not supported by Gateway's VM.
    LogicalAnd = 5,
    LogicalOr = 6,
    LogicalNot = 7,
    LogicalXor = 8,

    None = 9,
};

enum class StoreRegisterOffsetType : u32 {
    None = 0,
    Reg = 1,
    Imm = 2,
    MemReg = 3,
    MemImm = 4,
    MemImmReg = 5,
};

enum class CompareRegisterValueType : u32 {
    MemoryRelAddr = 0,
    MemoryOfsReg = 1,
    RegisterRelAddr = 2,
    RegisterOfsReg = 3,
    StaticValue = 4,
    OtherRegister = 5,
};

enum class SaveRestoreRegisterOpType : u32 {
    Restore = 0,
    Save = 1,
    ClearSaved = 2,
    ClearRegs = 3,
};

enum class DebugLogValueType : u32 {
    MemoryRelAddr = 0,
    MemoryOfsReg = 1,
    RegisterRelAddr = 2,
    RegisterOfsReg = 3,
    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 {
    bool is_else;
};

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<bool, 0x10> should_operate{};
};

struct ReadWriteStaticRegisterOpcode {
    u32 static_idx{};
    u32 idx{};
};

struct PauseProcessOpcode {};

struct ResumeProcessOpcode {};

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 UnrecognizedInstruction {
    CheatVmOpcodeType opcode{};
};

struct CheatVmOpcode {
    bool begin_conditional_block{};
    std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
                 LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
                 PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
                 PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
                 BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
                 SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode,
                 ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction>
        opcode{};
};

class DmntCheatVm {
public:
    /// Helper Type for DmntCheatVm <=> yuzu Interface
    class Callbacks {
    public:
        virtual ~Callbacks();

        virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
        virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;

        virtual u64 HidKeysDown() = 0;

        virtual void PauseProcess() = 0;
        virtual void ResumeProcess() = 0;

        virtual void DebugLog(u8 id, u64 value) = 0;
        virtual void CommandLog(std::string_view data) = 0;
    };

    static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
    static constexpr std::size_t NumRegisters = 0x10;
    static constexpr std::size_t NumReadableStaticRegisters = 0x80;
    static constexpr std::size_t NumWritableStaticRegisters = 0x80;
    static constexpr std::size_t NumStaticRegisters =
        NumReadableStaticRegisters + NumWritableStaticRegisters;

    explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_);
    ~DmntCheatVm();

    std::size_t GetProgramSize() const {
        return this->num_opcodes;
    }

    bool LoadProgram(const std::vector<CheatEntry>& cheats);
    void Execute(const CheatProcessMetadata& metadata);

private:
    std::unique_ptr<Callbacks> callbacks;

    std::size_t num_opcodes = 0;
    std::size_t instruction_ptr = 0;
    std::size_t condition_depth = 0;
    bool decode_success = false;
    std::array<u32, MaximumProgramOpcodeCount> program{};
    std::array<u64, NumRegisters> registers{};
    std::array<u64, NumRegisters> saved_values{};
    std::array<u64, NumStaticRegisters> static_registers{};
    std::array<std::size_t, NumRegisters> loop_tops{};

    bool DecodeNextOpcode(CheatVmOpcode& out);
    void SkipConditionalBlock(bool is_if);
    void ResetState();

    // For implementing the DebugLog opcode.
    void DebugLog(u32 log_id, u64 value);

    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);
};

}; // namespace Core::Memory


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <chrono>
#include <iterator>
#include <mutex>
#include <numeric>
#include <sstream>
#include <thread>
#include <fmt/chrono.h>
#include <fmt/format.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/settings.h"
#include "core/perf_stats.h"

using namespace std::chrono_literals;
using DoubleSecs = std::chrono::duration<double, std::chrono::seconds::period>;
using std::chrono::duration_cast;
using std::chrono::microseconds;

// Purposefully ignore the first five frames, as there's a significant amount of overhead in
// booting that we shouldn't account for
constexpr std::size_t IgnoreFrames = 5;

namespace Core {

PerfStats::PerfStats(u64 title_id_) : title_id(title_id_) {}

PerfStats::~PerfStats() {
    if (!Settings::values.record_frame_times || title_id == 0) {
        return;
    }

    const std::time_t t = std::time(nullptr);
    std::ostringstream stream;
    std::copy(perf_history.begin() + IgnoreFrames, perf_history.begin() + current_index,
              std::ostream_iterator<double>(stream, "\n"));

    const auto path = Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir);
    // %F Date format expanded is "%Y-%m-%d"
    const auto filename = fmt::format("{:%F-%H-%M}_{:016X}.csv", *std::localtime(&t), title_id);
    const auto filepath = path / filename;

    if (Common::FS::CreateParentDir(filepath)) {
        Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write,
                                Common::FS::FileType::TextFile);
        void(file.WriteString(stream.str()));
    }
}

void PerfStats::BeginSystemFrame() {
    std::scoped_lock lock{object_mutex};

    frame_begin = Clock::now();
}

void PerfStats::EndSystemFrame() {
    std::scoped_lock lock{object_mutex};

    auto frame_end = Clock::now();
    const auto frame_time = frame_end - frame_begin;
    if (current_index < perf_history.size()) {
        perf_history[current_index++] =
            std::chrono::duration<double, std::milli>(frame_time).count();
    }
    accumulated_frametime += frame_time;
    system_frames += 1;

    previous_frame_length = frame_end - previous_frame_end;
    previous_frame_end = frame_end;
}

void PerfStats::EndGameFrame() {
    game_frames.fetch_add(1, std::memory_order_relaxed);
}

double PerfStats::GetMeanFrametime() const {
    std::scoped_lock lock{object_mutex};

    if (current_index <= IgnoreFrames) {
        return 0;
    }

    const double sum = std::accumulate(perf_history.begin() + IgnoreFrames,
                                       perf_history.begin() + current_index, 0.0);
    return sum / static_cast<double>(current_index - IgnoreFrames);
}

PerfStatsResults PerfStats::GetAndResetStats(microseconds current_system_time_us) {
    std::scoped_lock lock{object_mutex};

    const auto now = Clock::now();
    // Walltime elapsed since stats were reset
    const auto interval = duration_cast<DoubleSecs>(now - reset_point).count();

    const auto system_us_per_second = (current_system_time_us - reset_point_system_us) / interval;
    const auto current_frames = static_cast<double>(game_frames.load(std::memory_order_relaxed));
    const auto current_fps = current_frames / interval;
    const PerfStatsResults results{
        .system_fps = static_cast<double>(system_frames) / interval,
        .average_game_fps = (current_fps + previous_fps) / 2.0,
        .frametime = duration_cast<DoubleSecs>(accumulated_frametime).count() /
                     static_cast<double>(system_frames),
        .emulation_speed = system_us_per_second.count() / 1'000'000.0,
    };

    // Reset counters
    reset_point = now;
    reset_point_system_us = current_system_time_us;
    accumulated_frametime = Clock::duration::zero();
    system_frames = 0;
    game_frames.store(0, std::memory_order_relaxed);
    previous_fps = current_fps;

    return results;
}

double PerfStats::GetLastFrameTimeScale() const {
    std::scoped_lock lock{object_mutex};

    constexpr double FRAME_LENGTH = 1.0 / 60;
    return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH;
}

void SpeedLimiter::DoSpeedLimiting(microseconds current_system_time_us) {
    if (Settings::values.use_multi_core.GetValue() ||
        !Settings::values.use_speed_limit.GetValue()) {
        return;
    }

    auto now = Clock::now();

    const double sleep_scale = Settings::values.speed_limit.GetValue() / 100.0;

    // Max lag caused by slow frames. Shouldn't be more than the length of a frame at the current
    // speed percent or it will clamp too much and prevent this from properly limiting to that
    // percent. High values means it'll take longer after a slow frame to recover and start
    // limiting
    const microseconds max_lag_time_us = duration_cast<microseconds>(
        std::chrono::duration<double, std::chrono::microseconds::period>(25ms / sleep_scale));
    speed_limiting_delta_err += duration_cast<microseconds>(
        std::chrono::duration<double, std::chrono::microseconds::period>(
            (current_system_time_us - previous_system_time_us) / sleep_scale));
    speed_limiting_delta_err -= duration_cast<microseconds>(now - previous_walltime);
    speed_limiting_delta_err =
        std::clamp(speed_limiting_delta_err, -max_lag_time_us, max_lag_time_us);

    if (speed_limiting_delta_err > microseconds::zero()) {
        std::this_thread::sleep_for(speed_limiting_delta_err);
        auto now_after_sleep = Clock::now();
        speed_limiting_delta_err -= duration_cast<microseconds>(now_after_sleep - now);
        now = now_after_sleep;
    }

    previous_system_time_us = current_system_time_us;
    previous_walltime = now;
}

} // namespace Core


code:
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <mutex>
#include "common/common_types.h"

namespace Core {

struct PerfStatsResults {
    /// System FPS (LCD VBlanks) in Hz
    double system_fps;
    /// Average game FPS (GPU frame renders) in Hz
    double average_game_fps;
    /// Walltime per system frame, in seconds, excluding any waits
    double frametime;
    /// Ratio of walltime / emulated time elapsed
    double emulation_speed;
};

/**
 * Class to manage and query performance/timing statistics. All public functions of this class are
 * thread-safe unless stated otherwise.
 */
class PerfStats {
public:
    explicit PerfStats(u64 title_id_);
    ~PerfStats();

    using Clock = std::chrono::steady_clock;

    void BeginSystemFrame();
    void EndSystemFrame();
    void EndGameFrame();

    PerfStatsResults GetAndResetStats(std::chrono::microseconds current_system_time_us);

    /**
     * Returns the arithmetic mean of all frametime values stored in the performance history.
     */
    double GetMeanFrametime() const;

    /**
     * Gets the ratio between walltime and the emulated time of the previous system frame. This is
     * useful for scaling inputs or outputs moving between the two time domains.
     */
    double GetLastFrameTimeScale() const;

private:
    mutable std::mutex object_mutex;

    /// Title ID for the game that is running. 0 if there is no game running yet
    u64 title_id{0};
    /// Current index for writing to the perf_history array
    std::size_t current_index{0};
    /// Stores an hour of historical frametime data useful for processing and tracking performance
    /// regressions with code changes.
    std::array<double, 216000> perf_history{};

    /// Point when the cumulative counters were reset
    Clock::time_point reset_point = Clock::now();
    /// System time when the cumulative counters were reset
    std::chrono::microseconds reset_point_system_us{0};

    /// Cumulative duration (excluding v-sync/frame-limiting) of frames since last reset
    Clock::duration accumulated_frametime = Clock::duration::zero();
    /// Cumulative number of system frames (LCD VBlanks) presented since last reset
    u32 system_frames = 0;
    /// Cumulative number of game frames (GSP frame submissions) since last reset
    std::atomic<u32> game_frames = 0;

    /// Point when the previous system frame ended
    Clock::time_point previous_frame_end = reset_point;
    /// Point when the current system frame began
    Clock::time_point frame_begin = reset_point;
    /// Total visible duration (including frame-limiting, etc.) of the previous system frame
    Clock::duration previous_frame_length = Clock::duration::zero();
    /// Previously computed fps
    double previous_fps = 0;
};

class SpeedLimiter {
public:
    using Clock = std::chrono::steady_clock;

    void DoSpeedLimiting(std::chrono::microseconds current_system_time_us);

private:
    /// Emulated system time (in microseconds) at the last limiter invocation
    std::chrono::microseconds previous_system_time_us{0};
    /// Walltime at the last limiter invocation
    Clock::time_point previous_walltime = Clock::now();

    /// Accumulated difference between walltime and emulated time
    std::chrono::microseconds speed_limiting_delta_err{0};
};

} // namespace Core


code:
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <boost/container/flat_map.hpp> // used by service.h which is heavily included
#include <boost/intrusive/rbtree.hpp>   // used by k_auto_object.h which is heavily included

#include "common/common_precompiled_headers.h"

#include "core/hle/kernel/k_process.h"


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <ctime>
#include <fstream>
#include <iomanip>

#include <fmt/chrono.h>
#include <fmt/format.h>
#include <nlohmann/json.hpp>

#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/hex_util.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/result.h"
#include "core/hle/service/hle_ipc.h"
#include "core/memory.h"
#include "core/reporter.h"

namespace {

std::filesystem::path GetPath(std::string_view type, u64 title_id, std::string_view timestamp) {
    return Common::FS::GetYuzuPath(Common::FS::YuzuPath::LogDir) / type /
           fmt::format("{:016X}_{}.json", title_id, timestamp);
}

std::string GetTimestamp() {
    const auto time = std::time(nullptr);
    return fmt::format("{:%FT%H-%M-%S}", *std::localtime(&time));
}

using namespace nlohmann;

void SaveToFile(const json& json, const std::filesystem::path& filename) {
    if (!Common::FS::CreateParentDirs(filename)) {
        LOG_ERROR(Core, "Failed to create path for '{}' to save report!",
                  Common::FS::PathToUTF8String(filename));
        return;
    }

    std::ofstream file;
    Common::FS::OpenFileStream(file, filename, std::ios_base::out | std::ios_base::trunc);

    file << std::setw(4) << json << std::endl;
}

json GetYuzuVersionData() {
    return {
        {"scm_rev", std::string(Common::g_scm_rev)},
        {"scm_branch", std::string(Common::g_scm_branch)},
        {"scm_desc", std::string(Common::g_scm_desc)},
        {"build_name", std::string(Common::g_build_name)},
        {"build_date", std::string(Common::g_build_date)},
        {"build_fullname", std::string(Common::g_build_fullname)},
        {"build_version", std::string(Common::g_build_version)},
    };
}

json GetReportCommonData(u64 title_id, Result result, const std::string& timestamp,
                         std::optional<u128> user_id = {}) {
    auto out = json{
        {"title_id", fmt::format("{:016X}", title_id)},
        {"result_raw", fmt::format("{:08X}", result.raw)},
        {"result_module", fmt::format("{:08X}", static_cast<u32>(result.GetModule()))},
        {"result_description", fmt::format("{:08X}", result.GetDescription())},
        {"timestamp", timestamp},
    };

    if (user_id.has_value()) {
        out["user_id"] = fmt::format("{:016X}{:016X}", (*user_id)[1], (*user_id)[0]);
    }

    return out;
}

json GetProcessorStateData(const std::string& architecture, u64 entry_point, u64 sp, u64 pc,
                           u64 pstate, const std::array<u64, 31>& registers,
                           const std::optional<std::array<u64, 32>>& backtrace = {}) {
    auto out = json{
        {"entry_point", fmt::format("{:016X}", entry_point)},
        {"sp", fmt::format("{:016X}", sp)},
        {"pc", fmt::format("{:016X}", pc)},
        {"pstate", fmt::format("{:016X}", pstate)},
        {"architecture", architecture},
    };

    auto registers_out = json::object();
    for (std::size_t i = 0; i < registers.size(); ++i) {
        registers_out[fmt::format("X{:02d}", i)] = fmt::format("{:016X}", registers[i]);
    }

    out["registers"] = std::move(registers_out);

    if (backtrace.has_value()) {
        auto backtrace_out = json::array();
        for (const auto& entry : *backtrace) {
            backtrace_out.push_back(fmt::format("{:016X}", entry));
        }
        out["backtrace"] = std::move(backtrace_out);
    }

    return out;
}

json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& system) {
    json out;

    out["yuzu_version"] = GetYuzuVersionData();
    out["report_common"] = GetReportCommonData(title_id, ResultSuccess, timestamp);

    return out;
}

template <bool read_value, typename DescriptorType>
json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer,
                                Core::Memory::Memory& memory) {
    auto buffer_out = json::array();
    for (const auto& desc : buffer) {
        auto entry = json{
            {"address", fmt::format("{:016X}", desc.Address())},
            {"size", fmt::format("{:016X}", desc.Size())},
        };

        if constexpr (read_value) {
            std::vector<u8> data(desc.Size());
            memory.ReadBlock(desc.Address(), data.data(), desc.Size());
            entry["data"] = Common::HexToString(data);
        }

        buffer_out.push_back(std::move(entry));
    }

    return buffer_out;
}

json GetHLERequestContextData(Service::HLERequestContext& ctx, Core::Memory::Memory& memory) {
    json out;

    auto cmd_buf = json::array();
    for (std::size_t i = 0; i < IPC::COMMAND_BUFFER_LENGTH; ++i) {
        cmd_buf.push_back(fmt::format("{:08X}", ctx.CommandBuffer()[i]));
    }

    out["command_buffer"] = std::move(cmd_buf);

    out["buffer_descriptor_a"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorA(), memory);
    out["buffer_descriptor_b"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorB(), memory);
    out["buffer_descriptor_c"] = GetHLEBufferDescriptorData<false>(ctx.BufferDescriptorC(), memory);
    out["buffer_descriptor_x"] = GetHLEBufferDescriptorData<true>(ctx.BufferDescriptorX(), memory);

    return out;
}

} // Anonymous namespace

namespace Core {

Reporter::Reporter(System& system_) : system(system_) {
    ClearFSAccessLog();
}

Reporter::~Reporter() = default;

void Reporter::SaveCrashReport(u64 title_id, Result result, u64 set_flags, u64 entry_point, u64 sp,
                               u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
                               const std::array<u64, 31>& registers,
                               const std::array<u64, 32>& backtrace, u32 backtrace_size,
                               const std::string& arch, u32 unk10) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    json out;

    out["yuzu_version"] = GetYuzuVersionData();
    out["report_common"] = GetReportCommonData(title_id, result, timestamp);

    auto proc_out = GetProcessorStateData(arch, entry_point, sp, pc, pstate, registers, backtrace);
    proc_out["set_flags"] = fmt::format("{:016X}", set_flags);
    proc_out["afsr0"] = fmt::format("{:016X}", afsr0);
    proc_out["afsr1"] = fmt::format("{:016X}", afsr1);
    proc_out["esr"] = fmt::format("{:016X}", esr);
    proc_out["far"] = fmt::format("{:016X}", far);
    proc_out["backtrace_size"] = fmt::format("{:08X}", backtrace_size);
    proc_out["unknown_10"] = fmt::format("{:08X}", unk10);

    out["processor_state"] = std::move(proc_out);

    SaveToFile(out, GetPath("crash_report", title_id, timestamp));
}

void Reporter::SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
                                  const std::optional<std::vector<u8>>& resolved_buffer) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    const auto title_id = system.GetApplicationProcessProgramID();
    auto out = GetFullDataAuto(timestamp, title_id, system);

    auto break_out = json{
        {"type", fmt::format("{:08X}", type)},
        {"signal_debugger", fmt::format("{}", signal_debugger)},
        {"info1", fmt::format("{:016X}", info1)},
        {"info2", fmt::format("{:016X}", info2)},
    };

    if (resolved_buffer.has_value()) {
        break_out["debug_buffer"] = Common::HexToString(*resolved_buffer);
    }

    out["svc_break"] = std::move(break_out);

    SaveToFile(out, GetPath("svc_break_report", title_id, timestamp));
}

void Reporter::SaveUnimplementedFunctionReport(Service::HLERequestContext& ctx, u32 command_id,
                                               const std::string& name,
                                               const std::string& service_name) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    const auto title_id = system.GetApplicationProcessProgramID();
    auto out = GetFullDataAuto(timestamp, title_id, system);

    auto function_out = GetHLERequestContextData(ctx, system.ApplicationMemory());
    function_out["command_id"] = command_id;
    function_out["function_name"] = name;
    function_out["service_name"] = service_name;

    out["function"] = std::move(function_out);

    SaveToFile(out, GetPath("unimpl_func_report", title_id, timestamp));
}

void Reporter::SaveUnimplementedAppletReport(
    u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
    bool startup_sound, u64 system_tick, const std::vector<std::vector<u8>>& normal_channel,
    const std::vector<std::vector<u8>>& interactive_channel) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    const auto title_id = system.GetApplicationProcessProgramID();
    auto out = GetFullDataAuto(timestamp, title_id, system);

    out["applet_common_args"] = {
        {"applet_id", fmt::format("{:02X}", applet_id)},
        {"common_args_version", fmt::format("{:08X}", common_args_version)},
        {"library_version", fmt::format("{:08X}", library_version)},
        {"theme_color", fmt::format("{:08X}", theme_color)},
        {"startup_sound", fmt::format("{}", startup_sound)},
        {"system_tick", fmt::format("{:016X}", system_tick)},
    };

    auto normal_out = json::array();
    for (const auto& data : normal_channel) {
        normal_out.push_back(Common::HexToString(data));
    }

    auto interactive_out = json::array();
    for (const auto& data : interactive_channel) {
        interactive_out.push_back(Common::HexToString(data));
    }

    out["applet_normal_data"] = std::move(normal_out);
    out["applet_interactive_data"] = std::move(interactive_out);

    SaveToFile(out, GetPath("unimpl_applet_report", title_id, timestamp));
}

void Reporter::SavePlayReport(PlayReportType type, u64 title_id,
                              const std::vector<std::span<const u8>>& data,
                              std::optional<u64> process_id, std::optional<u128> user_id) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    json out;

    out["yuzu_version"] = GetYuzuVersionData();
    out["report_common"] = GetReportCommonData(title_id, ResultSuccess, timestamp, user_id);

    auto data_out = json::array();
    for (const auto& d : data) {
        data_out.push_back(Common::HexToString(d));
    }

    if (process_id.has_value()) {
        out["play_report_process_id"] = fmt::format("{:016X}", *process_id);
    }

    out["play_report_type"] = fmt::format("{:02}", static_cast<u8>(type));
    out["play_report_data"] = std::move(data_out);

    SaveToFile(out, GetPath("play_report", title_id, timestamp));
}

void Reporter::SaveErrorReport(u64 title_id, Result result,
                               const std::optional<std::string>& custom_text_main,
                               const std::optional<std::string>& custom_text_detail) const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    json out;

    out["yuzu_version"] = GetYuzuVersionData();
    out["report_common"] = GetReportCommonData(title_id, result, timestamp);

    out["error_custom_text"] = {
        {"main", custom_text_main.value_or("")},
        {"detail", custom_text_detail.value_or("")},
    };

    SaveToFile(out, GetPath("error_report", title_id, timestamp));
}

void Reporter::SaveFSAccessLog(std::string_view log_message) const {
    const auto access_log_path =
        Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt";

    void(Common::FS::AppendStringToFile(access_log_path, Common::FS::FileType::TextFile,
                                        log_message));
}

void Reporter::SaveUserReport() const {
    if (!IsReportingEnabled()) {
        return;
    }

    const auto timestamp = GetTimestamp();
    const auto title_id = system.GetApplicationProcessProgramID();

    SaveToFile(GetFullDataAuto(timestamp, title_id, system),
               GetPath("user_report", title_id, timestamp));
}

void Reporter::ClearFSAccessLog() const {
    const auto access_log_path =
        Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt";

    Common::FS::IOFile access_log_file{access_log_path, Common::FS::FileAccessMode::Write,
                                       Common::FS::FileType::TextFile};

    if (!access_log_file.IsOpen()) {
        LOG_ERROR(Common_Filesystem, "Failed to clear the filesystem access log.");
    }
}

bool Reporter::IsReportingEnabled() const {
    return Settings::values.reporting_services.GetValue();
}

} // namespace Core


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <optional>
#include <span>
#include <string>
#include <vector>
#include "common/common_types.h"

union Result;

namespace Service {
class HLERequestContext;
} // namespace Service

namespace Service::LM {
struct LogMessage;
} // namespace Service::LM

namespace Core {

class System;

class Reporter {
public:
    explicit Reporter(System& system_);
    ~Reporter();

    // Used by fatal services
    void SaveCrashReport(u64 title_id, Result result, u64 set_flags, u64 entry_point, u64 sp,
                         u64 pc, u64 pstate, u64 afsr0, u64 afsr1, u64 esr, u64 far,
                         const std::array<u64, 31>& registers, const std::array<u64, 32>& backtrace,
                         u32 backtrace_size, const std::string& arch, u32 unk10) const;

    // Used by syscall svcBreak
    void SaveSvcBreakReport(u32 type, bool signal_debugger, u64 info1, u64 info2,
                            const std::optional<std::vector<u8>>& resolved_buffer = {}) const;

    // Used by HLE service handler
    void SaveUnimplementedFunctionReport(Service::HLERequestContext& ctx, u32 command_id,
                                         const std::string& name,
                                         const std::string& service_name) const;

    // Used by stub applet implementation
    void SaveUnimplementedAppletReport(
        u32 applet_id, u32 common_args_version, u32 library_version, u32 theme_color,
        bool startup_sound, u64 system_tick, const std::vector<std::vector<u8>>& normal_channel,
        const std::vector<std::vector<u8>>& interactive_channel) const;

    enum class PlayReportType {
        Old,
        Old2,
        New,
        System,
    };

    void SavePlayReport(PlayReportType type, u64 title_id,
                        const std::vector<std::span<const u8>>& data,
                        std::optional<u64> process_id = {}, std::optional<u128> user_id = {}) const;

    // Used by error applet
    void SaveErrorReport(u64 title_id, Result result,
                         const std::optional<std::string>& custom_text_main = {},
                         const std::optional<std::string>& custom_text_detail = {}) const;

    void SaveFSAccessLog(std::string_view log_message) const;

    // Can be used anywhere to generate a backtrace and general info report at any point during
    // execution. Not intended to be used for anything other than debugging or testing.
    void SaveUserReport() const;

private:
    void ClearFSAccessLog() const;

    bool IsReportingEnabled() const;

    System& system;
};

} // namespace Core