mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #2535 from DarkLordZach/cheat-v2
cheat_engine: Use Atmosphere's Cheat VM and fix cheat crash
This commit is contained in:
		
						commit
						9187350b32
					
				@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) {
 | 
			
		||||
    CLS(Input)                                                                                     \
 | 
			
		||||
    CLS(Network)                                                                                   \
 | 
			
		||||
    CLS(Loader)                                                                                    \
 | 
			
		||||
    CLS(CheatEngine)                                                                               \
 | 
			
		||||
    CLS(Crypto)                                                                                    \
 | 
			
		||||
    CLS(WebService)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -117,6 +117,7 @@ enum class Class : ClassType {
 | 
			
		||||
    Audio_DSP,         ///< The HLE implementation of the DSP
 | 
			
		||||
    Audio_Sink,        ///< Emulator audio output backend
 | 
			
		||||
    Loader,            ///< ROM loader
 | 
			
		||||
    CheatEngine,       ///< Memory manipulation and engine VM functions
 | 
			
		||||
    Crypto,            ///< Cryptographic engine/functions
 | 
			
		||||
    Input,             ///< Input emulation
 | 
			
		||||
    Network,           ///< Network emulation
 | 
			
		||||
 | 
			
		||||
@ -33,8 +33,6 @@ add_library(core STATIC
 | 
			
		||||
    file_sys/bis_factory.h
 | 
			
		||||
    file_sys/card_image.cpp
 | 
			
		||||
    file_sys/card_image.h
 | 
			
		||||
    file_sys/cheat_engine.cpp
 | 
			
		||||
    file_sys/cheat_engine.h
 | 
			
		||||
    file_sys/content_archive.cpp
 | 
			
		||||
    file_sys/content_archive.h
 | 
			
		||||
    file_sys/control_metadata.cpp
 | 
			
		||||
@ -491,6 +489,11 @@ add_library(core STATIC
 | 
			
		||||
    loader/nsp.h
 | 
			
		||||
    loader/xci.cpp
 | 
			
		||||
    loader/xci.h
 | 
			
		||||
    memory/cheat_engine.cpp
 | 
			
		||||
    memory/cheat_engine.h
 | 
			
		||||
    memory/dmnt_cheat_types.h
 | 
			
		||||
    memory/dmnt_cheat_vm.cpp
 | 
			
		||||
    memory/dmnt_cheat_vm.h
 | 
			
		||||
    memory.cpp
 | 
			
		||||
    memory.h
 | 
			
		||||
    memory_setup.h
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
#include "core/file_sys/bis_factory.h"
 | 
			
		||||
#include "core/file_sys/card_image.h"
 | 
			
		||||
#include "core/file_sys/mode.h"
 | 
			
		||||
#include "core/file_sys/patch_manager.h"
 | 
			
		||||
#include "core/file_sys/registered_cache.h"
 | 
			
		||||
#include "core/file_sys/romfs_factory.h"
 | 
			
		||||
#include "core/file_sys/savedata_factory.h"
 | 
			
		||||
@ -37,13 +38,12 @@
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
#include "core/hle/service/sm/sm.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/memory/cheat_engine.h"
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "core/reporter.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "core/tools/freezer.h"
 | 
			
		||||
#include "file_sys/cheat_engine.h"
 | 
			
		||||
#include "file_sys/patch_manager.h"
 | 
			
		||||
#include "video_core/debug_utils/debug_utils.h"
 | 
			
		||||
#include "video_core/renderer_base.h"
 | 
			
		||||
#include "video_core/video_core.h"
 | 
			
		||||
@ -204,6 +204,11 @@ struct System::Impl {
 | 
			
		||||
        gpu_core->Start();
 | 
			
		||||
        cpu_core_manager.StartThreads();
 | 
			
		||||
 | 
			
		||||
        // Initialize cheat engine
 | 
			
		||||
        if (cheat_engine) {
 | 
			
		||||
            cheat_engine->Initialize();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // All threads are started, begin main process execution, now that we're in the clear.
 | 
			
		||||
        main_process->Run(load_parameters->main_thread_priority,
 | 
			
		||||
                          load_parameters->main_thread_stack_size);
 | 
			
		||||
@ -329,7 +334,7 @@ struct System::Impl {
 | 
			
		||||
    CpuCoreManager cpu_core_manager;
 | 
			
		||||
    bool is_powered_on = false;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<FileSys::CheatEngine> cheat_engine;
 | 
			
		||||
    std::unique_ptr<Memory::CheatEngine> cheat_engine;
 | 
			
		||||
    std::unique_ptr<Tools::Freezer> memory_freezer;
 | 
			
		||||
 | 
			
		||||
    /// Frontend applets
 | 
			
		||||
@ -544,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
 | 
			
		||||
    return impl->debug_context.get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
 | 
			
		||||
                               const std::string& build_id, VAddr code_region_start,
 | 
			
		||||
                               VAddr code_region_end) {
 | 
			
		||||
    impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
 | 
			
		||||
                                                                code_region_start, code_region_end);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
 | 
			
		||||
    impl->virtual_filesystem = std::move(vfs);
 | 
			
		||||
}
 | 
			
		||||
@ -559,6 +557,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
 | 
			
		||||
    return impl->virtual_filesystem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
 | 
			
		||||
                               const std::array<u8, 32>& build_id, VAddr main_region_begin,
 | 
			
		||||
                               u64 main_region_size) {
 | 
			
		||||
    impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
 | 
			
		||||
    impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
 | 
			
		||||
    impl->applet_manager.SetAppletFrontendSet(std::move(set));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@ class EmuWindow;
 | 
			
		||||
} // namespace Core::Frontend
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
class CheatList;
 | 
			
		||||
class ContentProvider;
 | 
			
		||||
class ContentProviderUnion;
 | 
			
		||||
enum class ContentProviderUnionSlot;
 | 
			
		||||
@ -36,6 +35,10 @@ class AppLoader;
 | 
			
		||||
enum class ResultStatus : u16;
 | 
			
		||||
} // namespace Loader
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
struct CheatEntry;
 | 
			
		||||
} // namespace Memory
 | 
			
		||||
 | 
			
		||||
namespace Service {
 | 
			
		||||
 | 
			
		||||
namespace AM::Applets {
 | 
			
		||||
@ -286,8 +289,9 @@ public:
 | 
			
		||||
 | 
			
		||||
    std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
 | 
			
		||||
 | 
			
		||||
    void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
 | 
			
		||||
                           VAddr code_region_start, VAddr code_region_end);
 | 
			
		||||
    void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
 | 
			
		||||
                           const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
 | 
			
		||||
                           u64 main_region_size);
 | 
			
		||||
 | 
			
		||||
    void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,492 +0,0 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <locale>
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/microprofile.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/core_timing_util.h"
 | 
			
		||||
#include "core/file_sys/cheat_engine.h"
 | 
			
		||||
#include "core/hle/kernel/process.h"
 | 
			
		||||
#include "core/hle/service/hid/controllers/npad.h"
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
#include "core/hle/service/sm/sm.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
 | 
			
		||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
 | 
			
		||||
 | 
			
		||||
u64 Cheat::Address() const {
 | 
			
		||||
    u64 out;
 | 
			
		||||
    std::memcpy(&out, raw.data(), sizeof(u64));
 | 
			
		||||
    return Common::swap64(out) & 0xFFFFFFFFFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 Cheat::ValueWidth(u64 offset) const {
 | 
			
		||||
    return Value(offset, width);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 Cheat::Value(u64 offset, u64 width) const {
 | 
			
		||||
    u64 out;
 | 
			
		||||
    std::memcpy(&out, raw.data() + offset, sizeof(u64));
 | 
			
		||||
    out = Common::swap64(out);
 | 
			
		||||
    if (width == 8)
 | 
			
		||||
        return out;
 | 
			
		||||
    return out & ((1ull << (width * CHAR_BIT)) - 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32 Cheat::KeypadValue() const {
 | 
			
		||||
    u32 out;
 | 
			
		||||
    std::memcpy(&out, raw.data(), sizeof(u32));
 | 
			
		||||
    return Common::swap32(out) & 0x0FFFFFFF;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
 | 
			
		||||
                                    VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
 | 
			
		||||
    this->main_region_begin = main_begin;
 | 
			
		||||
    this->main_region_end = main_end;
 | 
			
		||||
    this->heap_region_begin = heap_begin;
 | 
			
		||||
    this->heap_region_end = heap_end;
 | 
			
		||||
    this->writer = writer;
 | 
			
		||||
    this->reader = reader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
 | 
			
		||||
 | 
			
		||||
void CheatList::Execute() {
 | 
			
		||||
    MICROPROFILE_SCOPE(Cheat_Engine);
 | 
			
		||||
 | 
			
		||||
    std::fill(scratch.begin(), scratch.end(), 0);
 | 
			
		||||
    in_standard = false;
 | 
			
		||||
    for (std::size_t i = 0; i < master_list.size(); ++i) {
 | 
			
		||||
        LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
 | 
			
		||||
        current_block = i;
 | 
			
		||||
        ExecuteBlock(master_list[i].second);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    in_standard = true;
 | 
			
		||||
    for (std::size_t i = 0; i < standard_list.size(); ++i) {
 | 
			
		||||
        LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
 | 
			
		||||
        current_block = i;
 | 
			
		||||
        ExecuteBlock(standard_list[i].second);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
 | 
			
		||||
    : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
 | 
			
		||||
 | 
			
		||||
bool CheatList::EvaluateConditional(const Cheat& cheat) const {
 | 
			
		||||
    using ComparisonFunction = bool (*)(u64, u64);
 | 
			
		||||
    constexpr std::array<ComparisonFunction, 6> comparison_functions{
 | 
			
		||||
        [](u64 a, u64 b) { return a > b; },  [](u64 a, u64 b) { return a >= b; },
 | 
			
		||||
        [](u64 a, u64 b) { return a < b; },  [](u64 a, u64 b) { return a <= b; },
 | 
			
		||||
        [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (cheat.type == CodeType::ConditionalInput) {
 | 
			
		||||
        const auto applet_resource =
 | 
			
		||||
            system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
 | 
			
		||||
        if (applet_resource == nullptr) {
 | 
			
		||||
            LOG_WARNING(
 | 
			
		||||
                Common_Filesystem,
 | 
			
		||||
                "Attempted to evaluate input conditional, but applet resource is not initialized!");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const auto press_state =
 | 
			
		||||
            applet_resource
 | 
			
		||||
                ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
 | 
			
		||||
                .GetAndResetPressState();
 | 
			
		||||
        return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ASSERT(cheat.type == CodeType::Conditional);
 | 
			
		||||
 | 
			
		||||
    const auto offset =
 | 
			
		||||
        cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
 | 
			
		||||
    ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
 | 
			
		||||
    auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
 | 
			
		||||
    const auto addr = cheat.Address() + offset;
 | 
			
		||||
 | 
			
		||||
    return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::ProcessBlockPairs(const Block& block) {
 | 
			
		||||
    block_pairs.clear();
 | 
			
		||||
 | 
			
		||||
    u64 scope = 0;
 | 
			
		||||
    std::map<u64, u64> pairs;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < block.size(); ++i) {
 | 
			
		||||
        const auto& cheat = block[i];
 | 
			
		||||
 | 
			
		||||
        switch (cheat.type) {
 | 
			
		||||
        case CodeType::Conditional:
 | 
			
		||||
        case CodeType::ConditionalInput:
 | 
			
		||||
            pairs.insert_or_assign(scope, i);
 | 
			
		||||
            ++scope;
 | 
			
		||||
            break;
 | 
			
		||||
        case CodeType::EndConditional: {
 | 
			
		||||
            --scope;
 | 
			
		||||
            const auto idx = pairs.at(scope);
 | 
			
		||||
            block_pairs.insert_or_assign(idx, i);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case CodeType::Loop: {
 | 
			
		||||
            if (cheat.end_of_loop) {
 | 
			
		||||
                --scope;
 | 
			
		||||
                const auto idx = pairs.at(scope);
 | 
			
		||||
                block_pairs.insert_or_assign(idx, i);
 | 
			
		||||
            } else {
 | 
			
		||||
                pairs.insert_or_assign(scope, i);
 | 
			
		||||
                ++scope;
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::WriteImmediate(const Cheat& cheat) {
 | 
			
		||||
    const auto offset =
 | 
			
		||||
        cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
 | 
			
		||||
    const auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
 | 
			
		||||
    const auto addr = cheat.Address() + offset + register_3;
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
 | 
			
		||||
              cheat.Value(8, cheat.width));
 | 
			
		||||
    writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::BeginConditional(const Cheat& cheat) {
 | 
			
		||||
    if (EvaluateConditional(cheat)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto iter = block_pairs.find(current_index);
 | 
			
		||||
    ASSERT(iter != block_pairs.end());
 | 
			
		||||
    current_index = iter->second - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::EndConditional(const Cheat& cheat) {
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::Loop(const Cheat& cheat) {
 | 
			
		||||
    if (cheat.end_of_loop.Value())
 | 
			
		||||
        ASSERT(!cheat.end_of_loop.Value());
 | 
			
		||||
 | 
			
		||||
    auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
    const auto iter = block_pairs.find(current_index);
 | 
			
		||||
    ASSERT(iter != block_pairs.end());
 | 
			
		||||
    ASSERT(iter->first < iter->second);
 | 
			
		||||
 | 
			
		||||
    const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
 | 
			
		||||
    for (s32 i = initial_value; i >= 0; --i) {
 | 
			
		||||
        register_3 = static_cast<u64>(i);
 | 
			
		||||
        for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
 | 
			
		||||
            current_index = c;
 | 
			
		||||
            ExecuteSingleCheat(
 | 
			
		||||
                (in_standard ? standard_list : master_list)[current_block].second[c]);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    current_index = iter->second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::LoadImmediate(const Cheat& cheat) {
 | 
			
		||||
    auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
 | 
			
		||||
              cheat.Value(4, 8));
 | 
			
		||||
    register_3 = cheat.Value(4, 8);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::LoadIndexed(const Cheat& cheat) {
 | 
			
		||||
    const auto offset =
 | 
			
		||||
        cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
 | 
			
		||||
    auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
 | 
			
		||||
    const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
 | 
			
		||||
              cheat.register_3, addr);
 | 
			
		||||
    register_3 = reader(cheat.width, SanitizeAddress(addr));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::StoreIndexed(const Cheat& cheat) {
 | 
			
		||||
    const auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
 | 
			
		||||
    const auto addr =
 | 
			
		||||
        register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
 | 
			
		||||
              cheat.Value(4, cheat.width), addr);
 | 
			
		||||
    writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::RegisterArithmetic(const Cheat& cheat) {
 | 
			
		||||
    using ArithmeticFunction = u64 (*)(u64, u64);
 | 
			
		||||
    constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
 | 
			
		||||
        [](u64 a, u64 b) { return a + b; },  [](u64 a, u64 b) { return a - b; },
 | 
			
		||||
        [](u64 a, u64 b) { return a * b; },  [](u64 a, u64 b) { return a << b; },
 | 
			
		||||
        [](u64 a, u64 b) { return a >> b; },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    using ArithmeticOverflowCheck = bool (*)(u64, u64);
 | 
			
		||||
    constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
 | 
			
		||||
        [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); },       // a + b
 | 
			
		||||
        [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); },       // a - b
 | 
			
		||||
        [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); },       // a * b
 | 
			
		||||
        [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
 | 
			
		||||
        [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; },         // a >> b
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
 | 
			
		||||
                  "Missing or have extra arithmetic overflow checks compared to functions!");
 | 
			
		||||
 | 
			
		||||
    auto& register_3 = scratch.at(cheat.register_3);
 | 
			
		||||
 | 
			
		||||
    ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
 | 
			
		||||
    auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
 | 
			
		||||
    auto* overflow_function =
 | 
			
		||||
        arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
 | 
			
		||||
    LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
 | 
			
		||||
              cheat.register_3, cheat.ValueWidth(4));
 | 
			
		||||
 | 
			
		||||
    if (overflow_function(register_3, cheat.ValueWidth(4))) {
 | 
			
		||||
        LOG_WARNING(Common_Filesystem,
 | 
			
		||||
                    "overflow will occur when performing arithmetic operation={:02X} with operands "
 | 
			
		||||
                    "a={:016X}, b={:016X}!",
 | 
			
		||||
                    static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    register_3 = function(register_3, cheat.ValueWidth(4));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::BeginConditionalInput(const Cheat& cheat) {
 | 
			
		||||
    if (EvaluateConditional(cheat))
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    const auto iter = block_pairs.find(current_index);
 | 
			
		||||
    ASSERT(iter != block_pairs.end());
 | 
			
		||||
    current_index = iter->second - 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VAddr CheatList::SanitizeAddress(VAddr in) const {
 | 
			
		||||
    if ((in < main_region_begin || in >= main_region_end) &&
 | 
			
		||||
        (in < heap_region_begin || in >= heap_region_end)) {
 | 
			
		||||
        LOG_ERROR(Common_Filesystem,
 | 
			
		||||
                  "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
 | 
			
		||||
                  "the cheat may be incorrect. However, this may be normal early in execution if "
 | 
			
		||||
                  "the game has not properly set up yet.",
 | 
			
		||||
                  in);
 | 
			
		||||
        return 0; ///< Invalid addresses will hard crash
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
 | 
			
		||||
    using CheatOperationFunction = void (CheatList::*)(const Cheat&);
 | 
			
		||||
    constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
 | 
			
		||||
        &CheatList::WriteImmediate,        &CheatList::BeginConditional,
 | 
			
		||||
        &CheatList::EndConditional,        &CheatList::Loop,
 | 
			
		||||
        &CheatList::LoadImmediate,         &CheatList::LoadIndexed,
 | 
			
		||||
        &CheatList::StoreIndexed,          &CheatList::RegisterArithmetic,
 | 
			
		||||
        &CheatList::BeginConditionalInput,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const auto index = static_cast<u8>(cheat.type.Value());
 | 
			
		||||
    ASSERT(index < sizeof(cheat_operation_functions));
 | 
			
		||||
    const auto op = cheat_operation_functions[index];
 | 
			
		||||
    (this->*op)(cheat);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatList::ExecuteBlock(const Block& block) {
 | 
			
		||||
    encountered_loops.clear();
 | 
			
		||||
 | 
			
		||||
    ProcessBlockPairs(block);
 | 
			
		||||
    for (std::size_t i = 0; i < block.size(); ++i) {
 | 
			
		||||
        current_index = i;
 | 
			
		||||
        ExecuteSingleCheat(block[i]);
 | 
			
		||||
        i = current_index;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatParser::~CheatParser() = default;
 | 
			
		||||
 | 
			
		||||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
 | 
			
		||||
                                     CheatList::ProgramSegment standard) const {
 | 
			
		||||
    return {system, std::move(master), std::move(standard)};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TextCheatParser::~TextCheatParser() = default;
 | 
			
		||||
 | 
			
		||||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
 | 
			
		||||
    std::stringstream ss;
 | 
			
		||||
    ss.write(reinterpret_cast<const char*>(data.data()), data.size());
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> lines;
 | 
			
		||||
    std::string stream_line;
 | 
			
		||||
    while (std::getline(ss, stream_line)) {
 | 
			
		||||
        // Remove a trailing \r
 | 
			
		||||
        if (!stream_line.empty() && stream_line.back() == '\r')
 | 
			
		||||
            stream_line.pop_back();
 | 
			
		||||
        lines.push_back(std::move(stream_line));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CheatList::ProgramSegment master_list;
 | 
			
		||||
    CheatList::ProgramSegment standard_list;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < lines.size(); ++i) {
 | 
			
		||||
        auto line = lines[i];
 | 
			
		||||
 | 
			
		||||
        if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
 | 
			
		||||
            const auto master = line[0] == '{';
 | 
			
		||||
            const auto begin = master ? line.find('{') : line.find('[');
 | 
			
		||||
            const auto end = master ? line.rfind('}') : line.rfind(']');
 | 
			
		||||
 | 
			
		||||
            ASSERT(begin != std::string::npos && end != std::string::npos);
 | 
			
		||||
 | 
			
		||||
            const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
 | 
			
		||||
            CheatList::Block block{};
 | 
			
		||||
 | 
			
		||||
            while (i < lines.size() - 1) {
 | 
			
		||||
                line = lines[++i];
 | 
			
		||||
                if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
 | 
			
		||||
                    --i;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (line.size() < 8)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                Cheat out{};
 | 
			
		||||
                out.raw = ParseSingleLineCheat(line);
 | 
			
		||||
                block.push_back(out);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            (master ? master_list : standard_list).emplace_back(patch_name, block);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return MakeCheatList(system, master_list, standard_list);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
 | 
			
		||||
    std::array<u8, 16> out{};
 | 
			
		||||
 | 
			
		||||
    if (line.size() < 8)
 | 
			
		||||
        return out;
 | 
			
		||||
 | 
			
		||||
    const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
 | 
			
		||||
    std::memcpy(out.data(), word1.data(), sizeof(u32));
 | 
			
		||||
 | 
			
		||||
    if (line.size() < 17 || line[8] != ' ')
 | 
			
		||||
        return out;
 | 
			
		||||
 | 
			
		||||
    const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
 | 
			
		||||
    std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
 | 
			
		||||
 | 
			
		||||
    if (line.size() < 26 || line[17] != ' ') {
 | 
			
		||||
        // Perform shifting in case value is truncated early.
 | 
			
		||||
        const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
 | 
			
		||||
        if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
 | 
			
		||||
            type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
 | 
			
		||||
            std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
 | 
			
		||||
            std::memset(out.data() + 4, 0, sizeof(u32));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
 | 
			
		||||
    std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
 | 
			
		||||
 | 
			
		||||
    if (line.size() < 35 || line[26] != ' ') {
 | 
			
		||||
        // Perform shifting in case value is truncated early.
 | 
			
		||||
        const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
 | 
			
		||||
        if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
 | 
			
		||||
            std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
 | 
			
		||||
            std::memset(out.data() + 8, 0, sizeof(u32));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return out;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
 | 
			
		||||
    std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
u64 MemoryReadImpl(u32 width, VAddr addr) {
 | 
			
		||||
    switch (width) {
 | 
			
		||||
    case 1:
 | 
			
		||||
        return Memory::Read8(addr);
 | 
			
		||||
    case 2:
 | 
			
		||||
        return Memory::Read16(addr);
 | 
			
		||||
    case 4:
 | 
			
		||||
        return Memory::Read32(addr);
 | 
			
		||||
    case 8:
 | 
			
		||||
        return Memory::Read64(addr);
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE();
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
 | 
			
		||||
    switch (width) {
 | 
			
		||||
    case 1:
 | 
			
		||||
        Memory::Write8(addr, static_cast<u8>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 2:
 | 
			
		||||
        Memory::Write16(addr, static_cast<u16>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 4:
 | 
			
		||||
        Memory::Write32(addr, static_cast<u32>(value));
 | 
			
		||||
        break;
 | 
			
		||||
    case 8:
 | 
			
		||||
        Memory::Write64(addr, value);
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
 | 
			
		||||
                         const std::string& build_id, VAddr code_region_start,
 | 
			
		||||
                         VAddr code_region_end)
 | 
			
		||||
    : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
 | 
			
		||||
    event = core_timing.RegisterEvent(
 | 
			
		||||
        "CheatEngine::FrameCallback::" + build_id,
 | 
			
		||||
        [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
 | 
			
		||||
    core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
 | 
			
		||||
 | 
			
		||||
    const auto& vm_manager = system.CurrentProcess()->VMManager();
 | 
			
		||||
    for (auto& list : this->cheats) {
 | 
			
		||||
        list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
 | 
			
		||||
                                 code_region_end, vm_manager.GetHeapRegionEndAddress(),
 | 
			
		||||
                                 &MemoryWriteImpl, &MemoryReadImpl);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatEngine::~CheatEngine() {
 | 
			
		||||
    core_timing.UnscheduleEvent(event, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
 | 
			
		||||
    for (auto& list : cheats) {
 | 
			
		||||
        list.Execute();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -1,234 +0,0 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <set>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/bit_field.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
class System;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Core::Timing {
 | 
			
		||||
class CoreTiming;
 | 
			
		||||
struct EventType;
 | 
			
		||||
} // namespace Core::Timing
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
 | 
			
		||||
enum class CodeType : u32 {
 | 
			
		||||
    // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
 | 
			
		||||
    // Writes a T sized value Y to the address A added to the value of register R in memory domain M
 | 
			
		||||
    WriteImmediate = 0,
 | 
			
		||||
 | 
			
		||||
    // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
 | 
			
		||||
    // Compares the T sized value Y to the value at address A in memory domain M using the
 | 
			
		||||
    // conditional function C. If success, continues execution. If failure, jumps to the matching
 | 
			
		||||
    // EndConditional statement.
 | 
			
		||||
    Conditional = 1,
 | 
			
		||||
 | 
			
		||||
    // 20000000
 | 
			
		||||
    // Terminates a Conditional or ConditionalInput block.
 | 
			
		||||
    EndConditional = 2,
 | 
			
		||||
 | 
			
		||||
    // 300R0000 VVVVVVVV
 | 
			
		||||
    // Starts looping V times, storing the current count in register R.
 | 
			
		||||
    // Loop block is terminated with a matching 310R0000.
 | 
			
		||||
    Loop = 3,
 | 
			
		||||
 | 
			
		||||
    // 400R0000 VVVVVVVV VVVVVVVV
 | 
			
		||||
    // Sets the value of register R to the value V.
 | 
			
		||||
    LoadImmediate = 4,
 | 
			
		||||
 | 
			
		||||
    // 5TMRI0AA AAAAAAAA
 | 
			
		||||
    // Sets the value of register R to the value of width T at address A in memory domain M, with
 | 
			
		||||
    // the current value of R added to the address if I == 1.
 | 
			
		||||
    LoadIndexed = 5,
 | 
			
		||||
 | 
			
		||||
    // 6T0RIFG0 VVVVVVVV VVVVVVVV
 | 
			
		||||
    // Writes the value V of width T to the memory address stored in register R. Adds the value of
 | 
			
		||||
    // register G to the final calculation if F is nonzero. Increments the value of register R by T
 | 
			
		||||
    // after operation if I is nonzero.
 | 
			
		||||
    StoreIndexed = 6,
 | 
			
		||||
 | 
			
		||||
    // 7T0RA000 VVVVVVVV
 | 
			
		||||
    // Performs the arithmetic operation A on the value in register R and the value V of width T,
 | 
			
		||||
    // storing the result in register R.
 | 
			
		||||
    RegisterArithmetic = 7,
 | 
			
		||||
 | 
			
		||||
    // 8KKKKKKK
 | 
			
		||||
    // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
 | 
			
		||||
    // execution continues. If none are, execution skips to the next EndConditional command.
 | 
			
		||||
    ConditionalInput = 8,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class MemoryType : u32 {
 | 
			
		||||
    // Addressed relative to start of main NSO
 | 
			
		||||
    MainNSO = 0,
 | 
			
		||||
 | 
			
		||||
    // Addressed relative to start of heap
 | 
			
		||||
    Heap = 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class ArithmeticOp : u32 {
 | 
			
		||||
    Add = 0,
 | 
			
		||||
    Sub = 1,
 | 
			
		||||
    Mult = 2,
 | 
			
		||||
    LShift = 3,
 | 
			
		||||
    RShift = 4,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class ComparisonOp : u32 {
 | 
			
		||||
    GreaterThan = 1,
 | 
			
		||||
    GreaterThanEqual = 2,
 | 
			
		||||
    LessThan = 3,
 | 
			
		||||
    LessThanEqual = 4,
 | 
			
		||||
    Equal = 5,
 | 
			
		||||
    Inequal = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
union Cheat {
 | 
			
		||||
    std::array<u8, 16> raw;
 | 
			
		||||
 | 
			
		||||
    BitField<4, 4, CodeType> type;
 | 
			
		||||
    BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
 | 
			
		||||
    BitField<0, 4, u32> end_of_loop;
 | 
			
		||||
    BitField<12, 4, MemoryType> memory_type;
 | 
			
		||||
    BitField<8, 4, u32> register_3;
 | 
			
		||||
    BitField<8, 4, ComparisonOp> comparison_op;
 | 
			
		||||
    BitField<20, 4, u32> load_from_register;
 | 
			
		||||
    BitField<20, 4, u32> increment_register;
 | 
			
		||||
    BitField<20, 4, ArithmeticOp> arithmetic_op;
 | 
			
		||||
    BitField<16, 4, u32> add_additional_register;
 | 
			
		||||
    BitField<28, 4, u32> register_6;
 | 
			
		||||
 | 
			
		||||
    u64 Address() const;
 | 
			
		||||
    u64 ValueWidth(u64 offset) const;
 | 
			
		||||
    u64 Value(u64 offset, u64 width) const;
 | 
			
		||||
    u32 KeypadValue() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class CheatParser;
 | 
			
		||||
 | 
			
		||||
// Represents a full collection of cheats for a game. The Execute function should be called every
 | 
			
		||||
// interval that all cheats should be executed. Clients should not directly instantiate this class
 | 
			
		||||
// (hence private constructor), they should instead receive an instance from CheatParser, which
 | 
			
		||||
// guarantees the list is always in an acceptable state.
 | 
			
		||||
class CheatList {
 | 
			
		||||
public:
 | 
			
		||||
    friend class CheatParser;
 | 
			
		||||
 | 
			
		||||
    using Block = std::vector<Cheat>;
 | 
			
		||||
    using ProgramSegment = std::vector<std::pair<std::string, Block>>;
 | 
			
		||||
 | 
			
		||||
    // (width in bytes, address, value)
 | 
			
		||||
    using MemoryWriter = void (*)(u32, VAddr, u64);
 | 
			
		||||
    // (width in bytes, address) -> value
 | 
			
		||||
    using MemoryReader = u64 (*)(u32, VAddr);
 | 
			
		||||
 | 
			
		||||
    void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
 | 
			
		||||
                             MemoryWriter writer, MemoryReader reader);
 | 
			
		||||
 | 
			
		||||
    void Execute();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
 | 
			
		||||
 | 
			
		||||
    void ProcessBlockPairs(const Block& block);
 | 
			
		||||
    void ExecuteSingleCheat(const Cheat& cheat);
 | 
			
		||||
 | 
			
		||||
    void ExecuteBlock(const Block& block);
 | 
			
		||||
 | 
			
		||||
    bool EvaluateConditional(const Cheat& cheat) const;
 | 
			
		||||
 | 
			
		||||
    // Individual cheat operations
 | 
			
		||||
    void WriteImmediate(const Cheat& cheat);
 | 
			
		||||
    void BeginConditional(const Cheat& cheat);
 | 
			
		||||
    void EndConditional(const Cheat& cheat);
 | 
			
		||||
    void Loop(const Cheat& cheat);
 | 
			
		||||
    void LoadImmediate(const Cheat& cheat);
 | 
			
		||||
    void LoadIndexed(const Cheat& cheat);
 | 
			
		||||
    void StoreIndexed(const Cheat& cheat);
 | 
			
		||||
    void RegisterArithmetic(const Cheat& cheat);
 | 
			
		||||
    void BeginConditionalInput(const Cheat& cheat);
 | 
			
		||||
 | 
			
		||||
    VAddr SanitizeAddress(VAddr in) const;
 | 
			
		||||
 | 
			
		||||
    // Master Codes are defined as codes that cannot be disabled and are run prior to all
 | 
			
		||||
    // others.
 | 
			
		||||
    ProgramSegment master_list;
 | 
			
		||||
    // All other codes
 | 
			
		||||
    ProgramSegment standard_list;
 | 
			
		||||
 | 
			
		||||
    bool in_standard = false;
 | 
			
		||||
 | 
			
		||||
    // 16 (0x0-0xF) scratch registers that can be used by cheats
 | 
			
		||||
    std::array<u64, 16> scratch{};
 | 
			
		||||
 | 
			
		||||
    MemoryWriter writer = nullptr;
 | 
			
		||||
    MemoryReader reader = nullptr;
 | 
			
		||||
 | 
			
		||||
    u64 main_region_begin{};
 | 
			
		||||
    u64 heap_region_begin{};
 | 
			
		||||
    u64 main_region_end{};
 | 
			
		||||
    u64 heap_region_end{};
 | 
			
		||||
 | 
			
		||||
    u64 current_block{};
 | 
			
		||||
    // The current index of the cheat within the current Block
 | 
			
		||||
    u64 current_index{};
 | 
			
		||||
 | 
			
		||||
    // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
 | 
			
		||||
    // pushed onto this queue. When a end block is encountered, the condition is checked.
 | 
			
		||||
    std::map<u64, u64> block_pairs;
 | 
			
		||||
 | 
			
		||||
    std::set<u64> encountered_loops;
 | 
			
		||||
 | 
			
		||||
    const Core::System* system;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
 | 
			
		||||
// CheatList object, that can be used for execution.
 | 
			
		||||
class CheatParser {
 | 
			
		||||
public:
 | 
			
		||||
    virtual ~CheatParser();
 | 
			
		||||
 | 
			
		||||
    virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
 | 
			
		||||
                            CheatList::ProgramSegment standard) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CheatParser implementation that parses text files
 | 
			
		||||
class TextCheatParser final : public CheatParser {
 | 
			
		||||
public:
 | 
			
		||||
    ~TextCheatParser() override;
 | 
			
		||||
 | 
			
		||||
    CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
 | 
			
		||||
class CheatEngine final {
 | 
			
		||||
public:
 | 
			
		||||
    CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
 | 
			
		||||
                VAddr code_region_start, VAddr code_region_end);
 | 
			
		||||
    ~CheatEngine();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void FrameCallback(u64 userdata, s64 cycles_late);
 | 
			
		||||
 | 
			
		||||
    std::vector<CheatList> cheats;
 | 
			
		||||
 | 
			
		||||
    Core::Timing::EventType* event;
 | 
			
		||||
    Core::Timing::CoreTiming& core_timing;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -22,6 +22,7 @@
 | 
			
		||||
#include "core/hle/service/filesystem/filesystem.h"
 | 
			
		||||
#include "core/loader/loader.h"
 | 
			
		||||
#include "core/loader/nso.h"
 | 
			
		||||
#include "core/memory/cheat_engine.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
@ -247,9 +248,10 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
 | 
			
		||||
    return !CollectPatches(patch_dirs, build_id).empty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
 | 
			
		||||
                                                        const std::array<u8, 0x20>& build_id_,
 | 
			
		||||
                                                        const VirtualDir& base_path, bool upper) {
 | 
			
		||||
namespace {
 | 
			
		||||
std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
 | 
			
		||||
    const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
 | 
			
		||||
    const VirtualDir& base_path, bool upper) {
 | 
			
		||||
    const auto build_id_raw = Common::HexToString(build_id_, upper);
 | 
			
		||||
    const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
 | 
			
		||||
    const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
 | 
			
		||||
@ -267,14 +269,16 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TextCheatParser parser;
 | 
			
		||||
    return parser.Parse(system, data);
 | 
			
		||||
    Memory::TextCheatParser parser;
 | 
			
		||||
    return parser.Parse(
 | 
			
		||||
        system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
 | 
			
		||||
                                                     const std::array<u8, 32>& build_id_) const {
 | 
			
		||||
    const auto load_dir =
 | 
			
		||||
        Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
 | 
			
		||||
    const Core::System& system, const std::array<u8, 32>& build_id_) const {
 | 
			
		||||
    const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
 | 
			
		||||
    if (load_dir == nullptr) {
 | 
			
		||||
        LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
 | 
			
		||||
        return {};
 | 
			
		||||
@ -284,20 +288,20 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
 | 
			
		||||
    std::sort(patch_dirs.begin(), patch_dirs.end(),
 | 
			
		||||
              [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
 | 
			
		||||
 | 
			
		||||
    std::vector<CheatList> out;
 | 
			
		||||
    out.reserve(patch_dirs.size());
 | 
			
		||||
    std::vector<Memory::CheatEntry> out;
 | 
			
		||||
    for (const auto& subdir : patch_dirs) {
 | 
			
		||||
        auto cheats_dir = subdir->GetSubdirectory("cheats");
 | 
			
		||||
        if (cheats_dir != nullptr) {
 | 
			
		||||
            auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
 | 
			
		||||
            if (res.has_value()) {
 | 
			
		||||
                out.push_back(std::move(*res));
 | 
			
		||||
                std::copy(res->begin(), res->end(), std::back_inserter(out));
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
 | 
			
		||||
            if (res.has_value())
 | 
			
		||||
                out.push_back(std::move(*res));
 | 
			
		||||
            if (res.has_value()) {
 | 
			
		||||
                std::copy(res->begin(), res->end(), std::back_inserter(out));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,9 @@
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/file_sys/cheat_engine.h"
 | 
			
		||||
#include "core/file_sys/nca_metadata.h"
 | 
			
		||||
#include "core/file_sys/vfs.h"
 | 
			
		||||
#include "core/memory/dmnt_cheat_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
class System;
 | 
			
		||||
@ -51,8 +51,8 @@ public:
 | 
			
		||||
    bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
 | 
			
		||||
 | 
			
		||||
    // Creates a CheatList object with all
 | 
			
		||||
    std::vector<CheatList> CreateCheatList(const Core::System& system,
 | 
			
		||||
                                           const std::array<u8, 0x20>& build_id) const;
 | 
			
		||||
    std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
 | 
			
		||||
                                                    const std::array<u8, 0x20>& build_id) const;
 | 
			
		||||
 | 
			
		||||
    // Currently tracked RomFS patches:
 | 
			
		||||
    // - Game Updates
 | 
			
		||||
 | 
			
		||||
@ -152,8 +152,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
 | 
			
		||||
        auto& system = Core::System::GetInstance();
 | 
			
		||||
        const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
 | 
			
		||||
        if (!cheats.empty()) {
 | 
			
		||||
            system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base,
 | 
			
		||||
                                     load_base + program_image.size());
 | 
			
		||||
            system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										234
									
								
								src/core/memory/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/core/memory/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,234 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <locale>
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/microprofile.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/core_timing.h"
 | 
			
		||||
#include "core/core_timing_util.h"
 | 
			
		||||
#include "core/hle/kernel/process.h"
 | 
			
		||||
#include "core/hle/service/hid/controllers/npad.h"
 | 
			
		||||
#include "core/hle/service/hid/hid.h"
 | 
			
		||||
#include "core/hle/service/sm/sm.h"
 | 
			
		||||
#include "core/memory/cheat_engine.h"
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
 | 
			
		||||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
 | 
			
		||||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
 | 
			
		||||
 | 
			
		||||
StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
 | 
			
		||||
                                         const CheatProcessMetadata& metadata)
 | 
			
		||||
    : system(system), metadata(metadata) {}
 | 
			
		||||
 | 
			
		||||
StandardVmCallbacks::~StandardVmCallbacks() = default;
 | 
			
		||||
 | 
			
		||||
void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
 | 
			
		||||
    ReadBlock(SanitizeAddress(address), data, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
 | 
			
		||||
    WriteBlock(SanitizeAddress(address), data, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u64 StandardVmCallbacks::HidKeysDown() {
 | 
			
		||||
    const auto applet_resource =
 | 
			
		||||
        system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
 | 
			
		||||
    if (applet_resource == nullptr) {
 | 
			
		||||
        LOG_WARNING(CheatEngine,
 | 
			
		||||
                    "Attempted to read input state, but applet resource is not initialized!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto press_state =
 | 
			
		||||
        applet_resource
 | 
			
		||||
            ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
 | 
			
		||||
            .GetAndResetPressState();
 | 
			
		||||
    return press_state & KEYPAD_BITMASK;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
 | 
			
		||||
    LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void StandardVmCallbacks::CommandLog(std::string_view data) {
 | 
			
		||||
    LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
 | 
			
		||||
              data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
 | 
			
		||||
    if ((in < metadata.main_nso_extents.base ||
 | 
			
		||||
         in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
 | 
			
		||||
        (in < metadata.heap_extents.base ||
 | 
			
		||||
         in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
 | 
			
		||||
        LOG_ERROR(CheatEngine,
 | 
			
		||||
                  "Cheat attempting to access memory at invalid address={:016X}, if this "
 | 
			
		||||
                  "persists, "
 | 
			
		||||
                  "the cheat may be incorrect. However, this may be normal early in execution if "
 | 
			
		||||
                  "the game has not properly set up yet.",
 | 
			
		||||
                  in);
 | 
			
		||||
        return 0; ///< Invalid addresses will hard crash
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatParser::~CheatParser() = default;
 | 
			
		||||
 | 
			
		||||
TextCheatParser::~TextCheatParser() = default;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
template <char match>
 | 
			
		||||
std::string_view ExtractName(std::string_view data, std::size_t start_index) {
 | 
			
		||||
    auto end_index = start_index;
 | 
			
		||||
    while (data[end_index] != match) {
 | 
			
		||||
        ++end_index;
 | 
			
		||||
        if (end_index > data.size() ||
 | 
			
		||||
            (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return data.substr(start_index, end_index - start_index);
 | 
			
		||||
}
 | 
			
		||||
} // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
 | 
			
		||||
                                               std::string_view data) const {
 | 
			
		||||
    std::vector<CheatEntry> out(1);
 | 
			
		||||
    std::optional<u64> current_entry = std::nullopt;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < data.size(); ++i) {
 | 
			
		||||
        if (::isspace(data[i])) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (data[i] == '{') {
 | 
			
		||||
            current_entry = 0;
 | 
			
		||||
 | 
			
		||||
            if (out[*current_entry].definition.num_opcodes > 0) {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const auto name = ExtractName<'}'>(data, i + 1);
 | 
			
		||||
            if (name.empty()) {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
 | 
			
		||||
                        std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
 | 
			
		||||
                                              name.size()));
 | 
			
		||||
            out[*current_entry]
 | 
			
		||||
                .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
 | 
			
		||||
                '\0';
 | 
			
		||||
 | 
			
		||||
            i += name.length() + 1;
 | 
			
		||||
        } else if (data[i] == '[') {
 | 
			
		||||
            current_entry = out.size();
 | 
			
		||||
            out.emplace_back();
 | 
			
		||||
 | 
			
		||||
            const auto name = ExtractName<']'>(data, i + 1);
 | 
			
		||||
            if (name.empty()) {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
 | 
			
		||||
                        std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
 | 
			
		||||
                                              name.size()));
 | 
			
		||||
            out[*current_entry]
 | 
			
		||||
                .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
 | 
			
		||||
                '\0';
 | 
			
		||||
 | 
			
		||||
            i += name.length() + 1;
 | 
			
		||||
        } else if (::isxdigit(data[i])) {
 | 
			
		||||
            if (!current_entry || out[*current_entry].definition.num_opcodes >=
 | 
			
		||||
                                      out[*current_entry].definition.opcodes.size()) {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const auto hex = std::string(data.substr(i, 8));
 | 
			
		||||
            if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
 | 
			
		||||
                return {};
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
 | 
			
		||||
                std::stoul(hex, nullptr, 0x10);
 | 
			
		||||
 | 
			
		||||
            i += 8;
 | 
			
		||||
        } else {
 | 
			
		||||
            return {};
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    out[0].enabled = out[0].definition.num_opcodes > 0;
 | 
			
		||||
    out[0].cheat_id = 0;
 | 
			
		||||
 | 
			
		||||
    for (u32 i = 1; i < out.size(); ++i) {
 | 
			
		||||
        out[i].enabled = out[i].definition.num_opcodes > 0;
 | 
			
		||||
        out[i].cheat_id = i;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
 | 
			
		||||
                         const std::array<u8, 0x20>& build_id)
 | 
			
		||||
    : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>(
 | 
			
		||||
                                                            system, metadata)},
 | 
			
		||||
      cheats(std::move(cheats)) {
 | 
			
		||||
    metadata.main_nso_build_id = build_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatEngine::~CheatEngine() {
 | 
			
		||||
    core_timing.UnscheduleEvent(event, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatEngine::Initialize() {
 | 
			
		||||
    event = core_timing.RegisterEvent(
 | 
			
		||||
        "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
 | 
			
		||||
        [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
 | 
			
		||||
    core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
 | 
			
		||||
 | 
			
		||||
    metadata.process_id = system.CurrentProcess()->GetProcessID();
 | 
			
		||||
    metadata.title_id = system.CurrentProcess()->GetTitleID();
 | 
			
		||||
 | 
			
		||||
    const auto& vm_manager = system.CurrentProcess()->VMManager();
 | 
			
		||||
    metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
 | 
			
		||||
    metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
 | 
			
		||||
                                      vm_manager.GetAddressSpaceSize()};
 | 
			
		||||
    metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
 | 
			
		||||
 | 
			
		||||
    is_pending_reload.exchange(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
 | 
			
		||||
    metadata.main_nso_extents = {main_region_begin, main_region_size};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
 | 
			
		||||
    this->cheats = std::move(cheats);
 | 
			
		||||
    is_pending_reload.exchange(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
 | 
			
		||||
 | 
			
		||||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
 | 
			
		||||
    if (is_pending_reload.exchange(false)) {
 | 
			
		||||
        vm.LoadProgram(cheats);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (vm.GetProgramSize() == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    MICROPROFILE_SCOPE(Cheat_Engine);
 | 
			
		||||
 | 
			
		||||
    vm.Execute(metadata);
 | 
			
		||||
 | 
			
		||||
    core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace Memory
 | 
			
		||||
							
								
								
									
										86
									
								
								src/core/memory/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								src/core/memory/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
			
		||||
// Copyright 2018 yuzu emulator team
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <atomic>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/memory/dmnt_cheat_types.h"
 | 
			
		||||
#include "core/memory/dmnt_cheat_vm.h"
 | 
			
		||||
 | 
			
		||||
namespace Core {
 | 
			
		||||
class System;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace Core::Timing {
 | 
			
		||||
class CoreTiming;
 | 
			
		||||
struct EventType;
 | 
			
		||||
} // namespace Core::Timing
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
 | 
			
		||||
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
 | 
			
		||||
public:
 | 
			
		||||
    StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata);
 | 
			
		||||
    ~StandardVmCallbacks() override;
 | 
			
		||||
 | 
			
		||||
    void MemoryRead(VAddr address, void* data, u64 size) override;
 | 
			
		||||
    void MemoryWrite(VAddr address, const void* data, u64 size) override;
 | 
			
		||||
    u64 HidKeysDown() override;
 | 
			
		||||
    void DebugLog(u8 id, u64 value) override;
 | 
			
		||||
    void CommandLog(std::string_view data) override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    VAddr SanitizeAddress(VAddr address) const;
 | 
			
		||||
 | 
			
		||||
    const CheatProcessMetadata& metadata;
 | 
			
		||||
    const Core::System& system;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Intermediary class that parses a text file or other disk format for storing cheats into a
 | 
			
		||||
// CheatList object, that can be used for execution.
 | 
			
		||||
class CheatParser {
 | 
			
		||||
public:
 | 
			
		||||
    virtual ~CheatParser();
 | 
			
		||||
 | 
			
		||||
    virtual std::vector<CheatEntry> Parse(const Core::System& system,
 | 
			
		||||
                                          std::string_view data) const = 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CheatParser implementation that parses text files
 | 
			
		||||
class TextCheatParser final : public CheatParser {
 | 
			
		||||
public:
 | 
			
		||||
    ~TextCheatParser() override;
 | 
			
		||||
 | 
			
		||||
    std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
 | 
			
		||||
class CheatEngine final {
 | 
			
		||||
public:
 | 
			
		||||
    CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_,
 | 
			
		||||
                const std::array<u8, 0x20>& build_id);
 | 
			
		||||
    ~CheatEngine();
 | 
			
		||||
 | 
			
		||||
    void Initialize();
 | 
			
		||||
    void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
 | 
			
		||||
 | 
			
		||||
    void Reload(std::vector<CheatEntry> cheats);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void FrameCallback(u64 userdata, s64 cycles_late);
 | 
			
		||||
 | 
			
		||||
    DmntCheatVm vm;
 | 
			
		||||
    CheatProcessMetadata metadata;
 | 
			
		||||
 | 
			
		||||
    std::vector<CheatEntry> cheats;
 | 
			
		||||
    std::atomic_bool is_pending_reload{false};
 | 
			
		||||
 | 
			
		||||
    Core::Timing::EventType* event{};
 | 
			
		||||
    Core::Timing::CoreTiming& core_timing;
 | 
			
		||||
    Core::System& system;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace Memory
 | 
			
		||||
							
								
								
									
										58
									
								
								src/core/memory/dmnt_cheat_types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/memory/dmnt_cheat_types.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2018-2019 Atmosphère-NX
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify it
 | 
			
		||||
 * under the terms and conditions of the GNU General Public License,
 | 
			
		||||
 * version 2, as published by the Free Software Foundation.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope it will be useful, but WITHOUT
 | 
			
		||||
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
			
		||||
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
			
		||||
 * more details.
 | 
			
		||||
 *
 | 
			
		||||
 * You should have received a copy of the GNU General Public License
 | 
			
		||||
 * along with this program.  If not, see <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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Memory {
 | 
			
		||||
 | 
			
		||||
struct MemoryRegionExtents {
 | 
			
		||||
    u64 base{};
 | 
			
		||||
    u64 size{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct CheatProcessMetadata {
 | 
			
		||||
    u64 process_id{};
 | 
			
		||||
    u64 title_id{};
 | 
			
		||||
    MemoryRegionExtents main_nso_extents{};
 | 
			
		||||
    MemoryRegionExtents heap_extents{};
 | 
			
		||||
    MemoryRegionExtents alias_extents{};
 | 
			
		||||
    MemoryRegionExtents address_space_extents{};
 | 
			
		||||
    std::array<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 Memory
 | 
			
		||||
							
								
								
									
										1212
									
								
								src/core/memory/dmnt_cheat_vm.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1212
									
								
								src/core/memory/dmnt_cheat_vm.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										321
									
								
								src/core/memory/dmnt_cheat_vm.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										321
									
								
								src/core/memory/dmnt_cheat_vm.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,321 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <variant>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <fmt/printf.h>
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "core/memory/dmnt_cheat_types.h"
 | 
			
		||||
 | 
			
		||||
namespace 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,
 | 
			
		||||
 | 
			
		||||
    // This is a meta entry, and not a real opcode.
 | 
			
		||||
    // This is to facilitate multi-nybble instruction decoding.
 | 
			
		||||
    DoubleExtendedWidth = 0xF0,
 | 
			
		||||
 | 
			
		||||
    // Double-extended width opcodes.
 | 
			
		||||
    DebugLog = 0xFFF,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class MemoryAccessType : u32 {
 | 
			
		||||
    MainNso = 0,
 | 
			
		||||
    Heap = 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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 {};
 | 
			
		||||
 | 
			
		||||
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 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, DebugLogOpcode, UnrecognizedInstruction>
 | 
			
		||||
        opcode{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DmntCheatVm {
 | 
			
		||||
public:
 | 
			
		||||
    /// Helper Type for DmntCheatVm <=> yuzu Interface
 | 
			
		||||
    class Callbacks {
 | 
			
		||||
    public:
 | 
			
		||||
        virtual ~Callbacks();
 | 
			
		||||
 | 
			
		||||
        virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
 | 
			
		||||
        virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
 | 
			
		||||
 | 
			
		||||
        virtual u64 HidKeysDown() = 0;
 | 
			
		||||
 | 
			
		||||
        virtual void DebugLog(u8 id, u64 value) = 0;
 | 
			
		||||
        virtual void CommandLog(std::string_view data) = 0;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
 | 
			
		||||
    static constexpr std::size_t NumRegisters = 0x10;
 | 
			
		||||
 | 
			
		||||
    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<std::size_t, NumRegisters> loop_tops{};
 | 
			
		||||
 | 
			
		||||
    bool DecodeNextOpcode(CheatVmOpcode& out);
 | 
			
		||||
    void SkipConditionalBlock();
 | 
			
		||||
    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 Memory
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user