mirror of
				https://github.com/yuzu-emu/yuzu.git
				synced 2025-11-04 08:13:41 +00:00 
			
		
		
		
	Merge pull request #1933 from DarkLordZach/cheat-engine
file_sys: Implement parser and interpreter for game memory cheats
This commit is contained in:
		
						commit
						639f0c524d
					
				@ -31,6 +31,8 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@
 | 
			
		||||
#include "core/perf_stats.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
#include "core/telemetry_session.h"
 | 
			
		||||
#include "file_sys/cheat_engine.h"
 | 
			
		||||
#include "frontend/applets/profile_select.h"
 | 
			
		||||
#include "frontend/applets/software_keyboard.h"
 | 
			
		||||
#include "frontend/applets/web_browser.h"
 | 
			
		||||
@ -205,6 +206,7 @@ struct System::Impl {
 | 
			
		||||
        GDBStub::Shutdown();
 | 
			
		||||
        Service::Shutdown();
 | 
			
		||||
        service_manager.reset();
 | 
			
		||||
        cheat_engine.reset();
 | 
			
		||||
        telemetry_session.reset();
 | 
			
		||||
        gpu_core.reset();
 | 
			
		||||
 | 
			
		||||
@ -255,6 +257,8 @@ struct System::Impl {
 | 
			
		||||
    CpuCoreManager cpu_core_manager;
 | 
			
		||||
    bool is_powered_on = false;
 | 
			
		||||
 | 
			
		||||
    std::unique_ptr<FileSys::CheatEngine> cheat_engine;
 | 
			
		||||
 | 
			
		||||
    /// Frontend applets
 | 
			
		||||
    std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
 | 
			
		||||
    std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
 | 
			
		||||
@ -453,6 +457,13 @@ 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>(list, build_id, code_region_start, code_region_end);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
 | 
			
		||||
    impl->virtual_filesystem = std::move(vfs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ class WebBrowserApplet;
 | 
			
		||||
} // namespace Core::Frontend
 | 
			
		||||
 | 
			
		||||
namespace FileSys {
 | 
			
		||||
class CheatList;
 | 
			
		||||
class VfsFilesystem;
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
 | 
			
		||||
@ -253,6 +254,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 SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
 | 
			
		||||
 | 
			
		||||
    const Frontend::ProfileSelectApplet& GetProfileSelector() const;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										493
									
								
								src/core/file_sys/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								src/core/file_sys/cheat_engine.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,493 @@
 | 
			
		||||
// 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/controller_base.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 u64 CHEAT_ENGINE_TICKS = 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(ProgramSegment master, ProgramSegment standard)
 | 
			
		||||
    : master_list(master), standard_list(standard) {}
 | 
			
		||||
 | 
			
		||||
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 = Core::System::GetInstance()
 | 
			
		||||
                                         .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);
 | 
			
		||||
 | 
			
		||||
    for (int i = cheat.Value(4, 4); i >= 0; --i) {
 | 
			
		||||
        register_3 = 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(CheatList::ProgramSegment master,
 | 
			
		||||
                                     CheatList::ProgramSegment standard) const {
 | 
			
		||||
    return {master, standard};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TextCheatParser::~TextCheatParser() = default;
 | 
			
		||||
 | 
			
		||||
CheatList TextCheatParser::Parse(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(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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
CheatEngine::CheatEngine(std::vector<CheatList> cheats, const std::string& build_id,
 | 
			
		||||
                         VAddr code_region_start, VAddr code_region_end)
 | 
			
		||||
    : cheats(std::move(cheats)) {
 | 
			
		||||
    auto& core_timing{Core::System::GetInstance().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 = Core::System::GetInstance().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() {
 | 
			
		||||
    auto& core_timing{Core::System::GetInstance().CoreTiming()};
 | 
			
		||||
    core_timing.UnscheduleEvent(event, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CheatEngine::FrameCallback(u64 userdata, int cycles_late) {
 | 
			
		||||
    for (auto& list : cheats)
 | 
			
		||||
        list.Execute();
 | 
			
		||||
 | 
			
		||||
    auto& core_timing{Core::System::GetInstance().CoreTiming()};
 | 
			
		||||
    core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
							
								
								
									
										227
									
								
								src/core/file_sys/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								src/core/file_sys/cheat_engine.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,227 @@
 | 
			
		||||
// 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 <queue>
 | 
			
		||||
#include "common/bit_field.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Core::Timing {
 | 
			
		||||
struct EventType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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(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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 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 std::vector<u8>& data) const = 0;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    CheatList MakeCheatList(CheatList::ProgramSegment master,
 | 
			
		||||
                            CheatList::ProgramSegment standard) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CheatParser implementation that parses text files
 | 
			
		||||
class TextCheatParser final : public CheatParser {
 | 
			
		||||
public:
 | 
			
		||||
    ~TextCheatParser() override;
 | 
			
		||||
 | 
			
		||||
    CheatList Parse(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(std::vector<CheatList> cheats, const std::string& build_id, VAddr code_region_start,
 | 
			
		||||
                VAddr code_region_end);
 | 
			
		||||
    ~CheatEngine();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void FrameCallback(u64 userdata, int cycles_late);
 | 
			
		||||
 | 
			
		||||
    Core::Timing::EventType* event;
 | 
			
		||||
 | 
			
		||||
    std::vector<CheatList> cheats;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace FileSys
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
#include <cstddef>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/file_sys/content_archive.h"
 | 
			
		||||
@ -232,6 +233,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
 | 
			
		||||
    return !CollectPatches(patch_dirs, build_id).empty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static std::optional<CheatList> ReadCheatFileFromFolder(u64 title_id,
 | 
			
		||||
                                                        const std::array<u8, 0x20>& build_id_,
 | 
			
		||||
                                                        const VirtualDir& base_path, bool upper) {
 | 
			
		||||
    const auto build_id_raw = Common::HexArrayToString(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));
 | 
			
		||||
 | 
			
		||||
    if (file == nullptr) {
 | 
			
		||||
        LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
 | 
			
		||||
                 title_id, build_id);
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> data(file->GetSize());
 | 
			
		||||
    if (file->Read(data.data(), data.size()) != data.size()) {
 | 
			
		||||
        LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
 | 
			
		||||
                 title_id, build_id);
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    TextCheatParser parser;
 | 
			
		||||
    return parser.Parse(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<CheatList> PatchManager::CreateCheatList(const std::array<u8, 32>& build_id_) const {
 | 
			
		||||
    std::vector<CheatList> out;
 | 
			
		||||
 | 
			
		||||
    const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
 | 
			
		||||
    auto patch_dirs = load_dir->GetSubdirectories();
 | 
			
		||||
    std::sort(patch_dirs.begin(), patch_dirs.end(),
 | 
			
		||||
              [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
 | 
			
		||||
 | 
			
		||||
    out.reserve(patch_dirs.size());
 | 
			
		||||
    for (const auto& subdir : patch_dirs) {
 | 
			
		||||
        auto cheats_dir = subdir->GetSubdirectory("cheats");
 | 
			
		||||
        if (cheats_dir != nullptr) {
 | 
			
		||||
            auto res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, true);
 | 
			
		||||
            if (res.has_value()) {
 | 
			
		||||
                out.push_back(std::move(*res));
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            res = ReadCheatFileFromFolder(title_id, build_id_, cheats_dir, false);
 | 
			
		||||
            if (res.has_value())
 | 
			
		||||
                out.push_back(std::move(*res));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
 | 
			
		||||
    const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
 | 
			
		||||
    if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
 | 
			
		||||
@ -403,6 +455,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
 | 
			
		||||
            }
 | 
			
		||||
            if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
 | 
			
		||||
                AppendCommaIfNotEmpty(types, "LayeredFS");
 | 
			
		||||
            if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
 | 
			
		||||
                AppendCommaIfNotEmpty(types, "Cheats");
 | 
			
		||||
 | 
			
		||||
            if (types.empty())
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@
 | 
			
		||||
#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"
 | 
			
		||||
 | 
			
		||||
@ -45,6 +46,9 @@ public:
 | 
			
		||||
    // Used to prevent expensive copies in NSO loader.
 | 
			
		||||
    bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
 | 
			
		||||
 | 
			
		||||
    // Creates a CheatList object with all
 | 
			
		||||
    std::vector<CheatList> CreateCheatList(const std::array<u8, 0x20>& build_id) const;
 | 
			
		||||
 | 
			
		||||
    // Currently tracked RomFS patches:
 | 
			
		||||
    // - Game Updates
 | 
			
		||||
    // - LayeredFS
 | 
			
		||||
 | 
			
		||||
@ -617,6 +617,9 @@ private:
 | 
			
		||||
    VAddr new_map_region_base = 0;
 | 
			
		||||
    VAddr new_map_region_end = 0;
 | 
			
		||||
 | 
			
		||||
    VAddr main_code_region_base = 0;
 | 
			
		||||
    VAddr main_code_region_end = 0;
 | 
			
		||||
 | 
			
		||||
    VAddr tls_io_region_base = 0;
 | 
			
		||||
    VAddr tls_io_region_end = 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,9 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "core/hle/service/hid/controllers/controller_base.h"
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
 | 
			
		||||
#include "controllers/controller_base.h"
 | 
			
		||||
#include "core/hle/service/service.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,8 +7,10 @@
 | 
			
		||||
#include <lz4.h>
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/file_util.h"
 | 
			
		||||
#include "common/hex_util.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/file_sys/patch_manager.h"
 | 
			
		||||
#include "core/gdbstub/gdbstub.h"
 | 
			
		||||
#include "core/hle/kernel/code_set.h"
 | 
			
		||||
@ -165,6 +167,16 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
 | 
			
		||||
        std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Apply cheats if they exist and the program has a valid title ID
 | 
			
		||||
    if (pm) {
 | 
			
		||||
        const auto cheats = pm->CreateCheatList(nso_header.build_id);
 | 
			
		||||
        if (!cheats.empty()) {
 | 
			
		||||
            Core::System::GetInstance().RegisterCheatList(
 | 
			
		||||
                cheats, Common::HexArrayToString(nso_header.build_id), load_base,
 | 
			
		||||
                load_base + program_image.size());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Load codeset for current process
 | 
			
		||||
    codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
 | 
			
		||||
    process.LoadModule(std::move(codeset), load_base);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user