diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index a03179520..1111cfbad 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -255,6 +255,7 @@ void DebuggerBackend::Write(const Entry& entry) { CLS(Input) \ CLS(Network) \ CLS(Loader) \ + CLS(CheatEngine) \ CLS(Crypto) \ CLS(WebService) diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 8ed6d5050..259708116 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -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 diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 7c142c011..2deaf560c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/core.cpp b/src/core/core.cpp index f22244cf7..76bb2bae9 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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 cheat_engine; + std::unique_ptr cheat_engine; std::unique_ptr memory_freezer; /// Frontend applets @@ -544,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const { return impl->debug_context.get(); } -void System::RegisterCheatList(const std::vector& list, - const std::string& build_id, VAddr code_region_start, - VAddr code_region_end) { - impl->cheat_engine = std::make_unique(*this, list, build_id, - code_region_start, code_region_end); -} - void System::SetFilesystem(std::shared_ptr vfs) { impl->virtual_filesystem = std::move(vfs); } @@ -559,6 +557,13 @@ std::shared_ptr System::GetFilesystem() const { return impl->virtual_filesystem; } +void System::RegisterCheatList(const std::vector& list, + const std::array& build_id, VAddr main_region_begin, + u64 main_region_size) { + impl->cheat_engine = std::make_unique(*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)); } diff --git a/src/core/core.h b/src/core/core.h index bb2962fdd..d2a3c82d8 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -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 GetFilesystem() const; - void RegisterCheatList(const std::vector& list, const std::string& build_id, - VAddr code_region_start, VAddr code_region_end); + void RegisterCheatList(const std::vector& list, + const std::array& build_id, VAddr main_region_begin, + u64 main_region_size); void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set); diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp deleted file mode 100644 index b06c2f20a..000000000 --- a/src/core/file_sys/cheat_engine.cpp +++ /dev/null @@ -1,492 +0,0 @@ -// Copyright 2018 yuzu emulator team -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#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(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 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("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::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(cheat.comparison_op.Value()) < 6); - auto* function = comparison_functions[static_cast(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 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(cheat.Value(4, sizeof(s32))); - for (s32 i = initial_value; i >= 0; --i) { - register_3 = static_cast(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 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 arithmetic_overflow_checks{ - [](u64 a, u64 b) { return a > (std::numeric_limits::max() - b); }, // a + b - [](u64 a, u64 b) { return a > (std::numeric_limits::max() + b); }, // a - b - [](u64 a, u64 b) { return a > (std::numeric_limits::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(cheat.arithmetic_op.Value()) < 5); - auto* function = arithmetic_functions[static_cast(cheat.arithmetic_op.Value())]; - auto* overflow_function = - arithmetic_overflow_checks[static_cast(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(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 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(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& data) const { - std::stringstream ss; - ss.write(reinterpret_cast(data.data()), data.size()); - - std::vector 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 TextCheatParser::ParseSingleLineCheat(const std::string& line) const { - std::array out{}; - - if (line.size() < 8) - return out; - - const auto word1 = Common::HexStringToArray(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(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((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(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((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(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(value)); - break; - case 2: - Memory::Write16(addr, static_cast(value)); - break; - case 4: - Memory::Write32(addr, static_cast(value)); - break; - case 8: - Memory::Write64(addr, value); - break; - default: - UNREACHABLE(); - } -} -} // Anonymous namespace - -CheatEngine::CheatEngine(Core::System& system, std::vector 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 diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h deleted file mode 100644 index ac22a82cb..000000000 --- a/src/core/file_sys/cheat_engine.h +++ /dev/null @@ -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 -#include -#include -#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 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; - using ProgramSegment = std::vector>; - - // (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 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 block_pairs; - - std::set 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& 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& data) const override; - -private: - std::array 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 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 cheats; - - Core::Timing::EventType* event; - Core::Timing::CoreTiming& core_timing; -}; - -} // namespace FileSys diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index c1dd0c6d7..df0ecb15c 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -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& build_id_) const { return !CollectPatches(patch_dirs, build_id).empty(); } -static std::optional ReadCheatFileFromFolder(const Core::System& system, u64 title_id, - const std::array& build_id_, - const VirtualDir& base_path, bool upper) { +namespace { +std::optional> ReadCheatFileFromFolder( + const Core::System& system, u64 title_id, const std::array& 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 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(data.data()), data.size())); } -std::vector PatchManager::CreateCheatList(const Core::System& system, - const std::array& build_id_) const { - const auto load_dir = - Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id); +} // Anonymous namespace + +std::vector PatchManager::CreateCheatList( + const Core::System& system, const std::array& 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 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 out; - out.reserve(patch_dirs.size()); + std::vector 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)); + } } } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index a363c6577..e857e6e82 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -8,9 +8,9 @@ #include #include #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& build_id) const; // Creates a CheatList object with all - std::vector CreateCheatList(const Core::System& system, - const std::array& build_id) const; + std::vector CreateCheatList(const Core::System& system, + const std::array& build_id) const; // Currently tracked RomFS patches: // - Game Updates diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp index 70c90109f..e75c700ad 100644 --- a/src/core/loader/nso.cpp +++ b/src/core/loader/nso.cpp @@ -152,8 +152,7 @@ std::optional 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); } } diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp new file mode 100644 index 000000000..b56cb0627 --- /dev/null +++ b/src/core/memory/cheat_engine.cpp @@ -0,0 +1,234 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#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(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("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::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 +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 TextCheatParser::Parse(const Core::System& system, + std::string_view data) const { + std::vector out(1); + std::optional 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(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(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 cheats, + const std::array& build_id) + : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique( + 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 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 diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h new file mode 100644 index 000000000..0f012e9b5 --- /dev/null +++ b/src/core/memory/cheat_engine.h @@ -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 +#include +#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 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 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 cheats_, + const std::array& build_id); + ~CheatEngine(); + + void Initialize(); + void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); + + void Reload(std::vector cheats); + +private: + void FrameCallback(u64 userdata, s64 cycles_late); + + DmntCheatVm vm; + CheatProcessMetadata metadata; + + std::vector cheats; + std::atomic_bool is_pending_reload{false}; + + Core::Timing::EventType* event{}; + Core::Timing::CoreTiming& core_timing; + Core::System& system; +}; + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h new file mode 100644 index 000000000..bf68fa0fe --- /dev/null +++ b/src/core/memory/dmnt_cheat_types.h @@ -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 . + */ + +/* + * 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 main_nso_build_id{}; +}; + +struct CheatDefinition { + std::array readable_name{}; + u32 num_opcodes{}; + std::array opcodes{}; +}; + +struct CheatEntry { + bool enabled{}; + u32 cheat_id{}; + CheatDefinition definition{}; +}; + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/memory/dmnt_cheat_vm.cpp new file mode 100644 index 000000000..cc16d15a4 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.cpp @@ -0,0 +1,1212 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* + * Adapted by DarkLordZach for use/interaction with yuzu + * + * Modifications Copyright 2019 yuzu emulator team + * Licensed under GPLv2 or any later version + * Refer to the license.txt file included. + */ + +#include "common/assert.h" +#include "common/scope_exit.h" +#include "core/memory/dmnt_cheat_types.h" +#include "core/memory/dmnt_cheat_vm.h" + +namespace Memory { + +DmntCheatVm::DmntCheatVm(std::unique_ptr callbacks) : callbacks(std::move(callbacks)) {} + +DmntCheatVm::~DmntCheatVm() = default; + +void DmntCheatVm::DebugLog(u32 log_id, u64 value) { + callbacks->DebugLog(static_cast(log_id), value); +} + +void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { + if (auto store_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(store_static->mem_type))); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64)); + } else if (auto begin_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_cond->mem_type))); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast(begin_cond->cond_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address)); + callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64)); + } else if (auto end_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: End Conditional"); + } else if (auto ctrl_loop = std::get_if(&opcode.opcode)) { + if (ctrl_loop->start_loop) { + callbacks->CommandLog("Opcode: Start Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters)); + } else { + callbacks->CommandLog("Opcode: End Loop"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index)); + } + } else if (auto ldr_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Static"); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index)); + callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value)); + } else if (auto ldr_memory = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Load Register Memory"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index)); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(ldr_memory->mem_type))); + callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address)); + } else if (auto str_static = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Static to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index)); + if (str_static->add_offset_reg) { + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index)); + } + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg)); + callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value)); + } else if (auto perform_math_static = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Static Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width)); + callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index)); + callbacks->CommandLog( + fmt::format("Math Type: {:X}", static_cast(perform_math_static->math_type))); + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value)); + } else if (auto begin_keypress_cond = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Keypress Conditional"); + callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask)); + } else if (auto perform_math_reg = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Perform Register Arithmetic"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width)); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index)); + callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index)); + if (perform_math_reg->has_immediate) { + callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64)); + } else { + callbacks->CommandLog( + fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index)); + } + } else if (auto str_register = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Store Register to Address"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width)); + callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index)); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index)); + callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg)); + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + break; + case StoreRegisterOffsetType::Reg: + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index)); + break; + case StoreRegisterOffsetType::Imm: + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + case StoreRegisterOffsetType::MemReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(str_register->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address)); + break; + } + } else if (auto begin_reg_cond = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Begin Register Conditional"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width)); + callbacks->CommandLog( + fmt::format("Cond Type: {:X}", static_cast(begin_reg_cond->cond_type))); + callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index)); + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::StaticValue: + callbacks->CommandLog("Comp Type: Static Value"); + callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64)); + break; + case CompareRegisterValueType::OtherRegister: + callbacks->CommandLog("Comp Type: Other Register"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index)); + break; + case CompareRegisterValueType::MemoryRelAddr: + callbacks->CommandLog("Comp Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::MemoryOfsReg: + callbacks->CommandLog("Comp Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(begin_reg_cond->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + case CompareRegisterValueType::RegisterRelAddr: + callbacks->CommandLog("Comp Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address)); + break; + case CompareRegisterValueType::RegisterOfsReg: + callbacks->CommandLog("Comp Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index)); + break; + } + } else if (auto save_restore_reg = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register"); + callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index)); + callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index)); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_reg->op_type))); + } else if (auto save_restore_regmask = + std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Save or Restore Register Mask"); + callbacks->CommandLog( + fmt::format("Op Type: {:d}", static_cast(save_restore_regmask->op_type))); + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog( + fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i])); + } + } else if (auto debug_log = std::get_if(&opcode.opcode)) { + callbacks->CommandLog("Opcode: Debug Log"); + callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width)); + callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id)); + callbacks->CommandLog( + fmt::format("Val Type: {:X}", static_cast(debug_log->val_type))); + switch (debug_log->val_type) { + case DebugLogValueType::RegisterValue: + callbacks->CommandLog("Val Type: Register Value"); + callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index)); + break; + case DebugLogValueType::MemoryRelAddr: + callbacks->CommandLog("Val Type: Memory Relative Address"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::MemoryOfsReg: + callbacks->CommandLog("Val Type: Memory Offset Register"); + callbacks->CommandLog( + fmt::format("Mem Type: {:X}", static_cast(debug_log->mem_type))); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + case DebugLogValueType::RegisterRelAddr: + callbacks->CommandLog("Val Type: Register Relative Address"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address)); + break; + case DebugLogValueType::RegisterOfsReg: + callbacks->CommandLog("Val Type: Register Offset Register"); + callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index)); + callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index)); + break; + } + } else if (auto instr = std::get_if(&opcode.opcode)) { + callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(instr->opcode))); + } +} + +DmntCheatVm::Callbacks::~Callbacks() = default; + +bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) { + // If we've ever seen a decode failure, return false. + bool valid = decode_success; + CheatVmOpcode opcode = {}; + SCOPE_EXIT({ + decode_success &= valid; + if (valid) { + out = opcode; + } + }); + + // Helper function for getting instruction dwords. + const auto GetNextDword = [&] { + if (instruction_ptr >= num_opcodes) { + valid = false; + return static_cast(0); + } + return program[instruction_ptr++]; + }; + + // Helper function for parsing a VmInt. + const auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val{}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = static_cast(first_dword); + break; + case 2: + val.bit16 = static_cast(first_dword); + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (static_cast(first_dword) << 32ul) | static_cast(GetNextDword()); + break; + } + + return val; + }; + + // Read opcode. + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + auto opcode_type = static_cast(((first_dword >> 28) & 0xF)); + if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 24) & 0xF)); + } + if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) { + opcode_type = static_cast((static_cast(opcode_type) << 4) | + ((first_dword >> 20) & 0xF)); + } + + // detect condition start. + switch (opcode_type) { + case CheatVmOpcodeType::BeginConditionalBlock: + case CheatVmOpcodeType::BeginKeypressConditionalBlock: + case CheatVmOpcodeType::BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode_type) { + case CheatVmOpcodeType::StoreStatic: { + StoreStaticOpcode store_static{}; + // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + store_static.bit_width = (first_dword >> 24) & 0xF; + store_static.mem_type = static_cast((first_dword >> 20) & 0xF); + store_static.offset_register = ((first_dword >> 16) & 0xF); + store_static.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + store_static.value = GetNextVmInt(store_static.bit_width); + opcode.opcode = store_static; + } break; + case CheatVmOpcodeType::BeginConditionalBlock: { + BeginConditionalOpcode begin_cond{}; + // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) + // Read additional words. + const u32 second_dword = GetNextDword(); + begin_cond.bit_width = (first_dword >> 24) & 0xF; + begin_cond.mem_type = static_cast((first_dword >> 20) & 0xF); + begin_cond.cond_type = static_cast((first_dword >> 16) & 0xF); + begin_cond.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + begin_cond.value = GetNextVmInt(begin_cond.bit_width); + opcode.opcode = begin_cond; + } break; + case CheatVmOpcodeType::EndConditionalBlock: { + // 20000000 + // There's actually nothing left to process here! + opcode.opcode = EndConditionalOpcode{}; + } break; + case CheatVmOpcodeType::ControlLoop: { + ControlLoopOpcode ctrl_loop{}; + // 300R0000 VVVVVVVV + // 310R0000 + // Parse register, whether loop start or loop end. + ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); + + // Read number of iters if loop start. + if (ctrl_loop.start_loop) { + ctrl_loop.num_iters = GetNextDword(); + } + opcode.opcode = ctrl_loop; + } break; + case CheatVmOpcodeType::LoadRegisterStatic: { + LoadRegisterStaticOpcode ldr_static{}; + // 400R0000 VVVVVVVV VVVVVVVV + // Read additional words. + ldr_static.reg_index = ((first_dword >> 16) & 0xF); + ldr_static.value = + (static_cast(GetNextDword()) << 32ul) | static_cast(GetNextDword()); + opcode.opcode = ldr_static; + } break; + case CheatVmOpcodeType::LoadRegisterMemory: { + LoadRegisterMemoryOpcode ldr_memory{}; + // 5TMRI0AA AAAAAAAA + // Read additional words. + const u32 second_dword = GetNextDword(); + ldr_memory.bit_width = (first_dword >> 24) & 0xF; + ldr_memory.mem_type = static_cast((first_dword >> 20) & 0xF); + ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; + ldr_memory.rel_address = + (static_cast(first_dword & 0xFF) << 32ul) | static_cast(second_dword); + opcode.opcode = ldr_memory; + } break; + case CheatVmOpcodeType::StoreStaticToAddress: { + StoreStaticToAddressOpcode str_static{}; + // 6T0RIor0 VVVVVVVV VVVVVVVV + // Read additional words. + str_static.bit_width = (first_dword >> 24) & 0xF; + str_static.reg_index = ((first_dword >> 16) & 0xF); + str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + str_static.value = + (static_cast(GetNextDword()) << 32ul) | static_cast(GetNextDword()); + opcode.opcode = str_static; + } break; + case CheatVmOpcodeType::PerformArithmeticStatic: { + PerformArithmeticStaticOpcode perform_math_static{}; + // 7T0RC000 VVVVVVVV + // Read additional words. + perform_math_static.bit_width = (first_dword >> 24) & 0xF; + perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + perform_math_static.math_type = + static_cast((first_dword >> 12) & 0xF); + perform_math_static.value = GetNextDword(); + opcode.opcode = perform_math_static; + } break; + case CheatVmOpcodeType::BeginKeypressConditionalBlock: { + BeginKeypressConditionalOpcode begin_keypress_cond{}; + // 8kkkkkkk + // Just parse the mask. + begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + } break; + case CheatVmOpcodeType::PerformArithmeticRegister: { + PerformArithmeticRegisterOpcode perform_math_reg{}; + // 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) + perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + perform_math_reg.math_type = static_cast((first_dword >> 20) & 0xF); + perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (perform_math_reg.has_immediate) { + perform_math_reg.src_reg_2_index = 0; + perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width); + } else { + perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + opcode.opcode = perform_math_reg; + } break; + case CheatVmOpcodeType::StoreRegisterToAddress: { + StoreRegisterToAddressOpcode str_register{}; + // ATSRIOxa (aaaaaaaa) + // A = opcode 10 + // T = bit width + // S = src register index + // R = address register index + // I = 1 if increment address register, 0 if not increment address register + // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + + // Relative Address + // x = offset register (for offset type 1), memory type (for offset type 3) + // a = relative address (for offset type 2+3) + str_register.bit_width = (first_dword >> 24) & 0xF; + str_register.str_reg_index = ((first_dword >> 20) & 0xF); + str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + str_register.ofs_type = static_cast(((first_dword >> 8) & 0xF)); + str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (str_register.ofs_type) { + case StoreRegisterOffsetType::None: + case StoreRegisterOffsetType::Reg: + // Nothing more to do + break; + case StoreRegisterOffsetType::Imm: + str_register.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + case StoreRegisterOffsetType::MemReg: + str_register.mem_type = static_cast((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType::MemImm: + case StoreRegisterOffsetType::MemImmReg: + str_register.mem_type = static_cast((first_dword >> 4) & 0xF); + str_register.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + default: + str_register.ofs_type = StoreRegisterOffsetType::None; + break; + } + opcode.opcode = str_register; + } break; + case CheatVmOpcodeType::BeginRegisterConditionalBlock: { + BeginRegisterConditionalOpcode begin_reg_cond{}; + // C0TcSX## + // C0TcS0Ma aaaaaaaa + // C0TcS1Mr + // C0TcS2Ra aaaaaaaa + // C0TcS3Rr + // C0TcS400 VVVVVVVV (VVVVVVVV) + // C0TcS5X0 + // C0 = opcode 0xC0 + // T = bit width + // c = condition type. + // S = source register. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = static + // value, 5 = other register. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = other register. + // V = value. + begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + begin_reg_cond.cond_type = + static_cast((first_dword >> 16) & 0xF); + begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + begin_reg_cond.comp_type = static_cast((first_dword >> 8) & 0xF); + + switch (begin_reg_cond.comp_type) { + case CompareRegisterValueType::StaticValue: + begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width); + break; + case CompareRegisterValueType::OtherRegister: + begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType::MemoryRelAddr: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + case CompareRegisterValueType::MemoryOfsReg: + begin_reg_cond.mem_type = static_cast((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType::RegisterRelAddr: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + case CompareRegisterValueType::RegisterOfsReg: + begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + } + opcode.opcode = begin_reg_cond; + } break; + case CheatVmOpcodeType::SaveRestoreRegister: { + SaveRestoreRegisterOpcode save_restore_reg{}; + // C10D0Sx0 + // C1 = opcode 0xC1 + // D = destination index. + // S = source index. + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring + // a register. + // NOTE: If we add more save slots later, current encoding is backwards compatible. + save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + save_restore_reg.src_index = (first_dword >> 8) & 0xF; + save_restore_reg.op_type = static_cast((first_dword >> 4) & 0xF); + opcode.opcode = save_restore_reg; + } break; + case CheatVmOpcodeType::SaveRestoreRegisterMask: { + SaveRestoreRegisterMaskOpcode save_restore_regmask{}; + // C2x0XXXX + // C2 = opcode 0xC2 + // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. + // X = 16-bit bitmask, bit i --> save or restore register i. + save_restore_regmask.op_type = + static_cast((first_dword >> 20) & 0xF); + for (std::size_t i = 0; i < NumRegisters; i++) { + save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + } + opcode.opcode = save_restore_regmask; + } break; + case CheatVmOpcodeType::DebugLog: { + DebugLogOpcode debug_log{}; + // FFFTIX## + // FFFTI0Ma aaaaaaaa + // FFFTI1Mr + // FFFTI2Ra aaaaaaaa + // FFFTI3Rr + // FFFTI4X0 + // FFF = opcode 0xFFF + // T = bit width. + // I = log id. + // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset + // register, + // 2 = register with relative offset, 3 = register with offset register, 4 = register + // value. + // M = memory type. + // R = address register. + // a = relative address. + // r = offset register. + // X = value register. + debug_log.bit_width = (first_dword >> 16) & 0xF; + debug_log.log_id = ((first_dword >> 12) & 0xF); + debug_log.val_type = static_cast((first_dword >> 8) & 0xF); + + switch (debug_log.val_type) { + case DebugLogValueType::RegisterValue: + debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType::MemoryRelAddr: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + case DebugLogValueType::MemoryOfsReg: + debug_log.mem_type = static_cast((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType::RegisterRelAddr: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.rel_address = + ((static_cast(first_dword & 0xF) << 32ul) | static_cast(GetNextDword())); + break; + case DebugLogValueType::RegisterOfsReg: + debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + opcode.opcode = debug_log; + } break; + case CheatVmOpcodeType::ExtendedWidth: + case CheatVmOpcodeType::DoubleExtendedWidth: + default: + // Unrecognized instruction cannot be decoded. + valid = false; + opcode.opcode = UnrecognizedInstruction{opcode_type}; + break; + } + + // End decoding. + return valid; +} + +void DmntCheatVm::SkipConditionalBlock() { + if (condition_depth > 0) { + // We want to continue until we're out of the current block. + const std::size_t desired_depth = condition_depth - 1; + + CheatVmOpcode skip_opcode{}; + while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) { + // Decode instructions until we see end of the current conditional block. + // NOTE: This is broken in gateway's implementation. + // Gateway currently checks for "0x2" instead of "0x20000000" + // In addition, they do a linear scan instead of correctly decoding opcodes. + // This causes issues if "0x2" appears as an immediate in the conditional block... + + // We also support nesting of conditional blocks, and Gateway does not. + if (skip_opcode.begin_conditional_block) { + condition_depth++; + } else if (std::holds_alternative(skip_opcode.opcode)) { + condition_depth--; + } + } + } else { + // Skipping, but condition_depth = 0. + // This is an error condition. + // However, I don't actually believe it is possible for this to happen. + // I guess we'll throw a fatal error here, so as to encourage me to fix the VM + // in the event that someone triggers it? I don't know how you'd do that. + UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM"); + } +} + +u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + // Invalid bit width -> return 0. + return 0; + } +} + +u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata, + MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType::MainNso: + default: + return metadata.main_nso_extents.base + rel_address; + case MemoryAccessType::Heap: + return metadata.heap_extents.base + rel_address; + } +} + +void DmntCheatVm::ResetState() { + registers.fill(0); + saved_values.fill(0); + loop_tops.fill(0); + instruction_ptr = 0; + condition_depth = 0; + decode_success = true; +} + +bool DmntCheatVm::LoadProgram(const std::vector& entries) { + // Reset opcode count. + num_opcodes = 0; + + for (std::size_t i = 0; i < entries.size(); i++) { + if (entries[i].enabled) { + // Bounds check. + if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) { + num_opcodes = 0; + return false; + } + + for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) { + program[num_opcodes++] = entries[i].definition.opcodes[n]; + } + } + } + + return true; +} + +void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) { + CheatVmOpcode cur_opcode{}; + + // Get Keys down. + u64 kDown = callbacks->HidKeysDown(); + + callbacks->CommandLog("Started VM execution."); + callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base)); + callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast(kDown & 0x0FFFFFFF))); + + // Clear VM state. + ResetState(); + + // Loop until program finishes. + while (DecodeNextOpcode(cur_opcode)) { + callbacks->CommandLog( + fmt::format("Instruction Ptr: {:04X}", static_cast(instruction_ptr))); + + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i])); + } + + for (std::size_t i = 0; i < NumRegisters; i++) { + callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i])); + } + LogOpcode(cur_opcode); + + // Increment conditional depth, if relevant. + if (cur_opcode.begin_conditional_block) { + condition_depth++; + } + + if (auto store_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address, write value to memory. + u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type, + store_static->rel_address + + registers[store_static->offset_register]); + u64 dst_value = GetVmInt(store_static->value, store_static->bit_width); + switch (store_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width); + break; + } + } else if (auto begin_cond = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. + u64 src_address = + GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address); + u64 src_value = 0; + switch (store_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width); + break; + } + // Check against condition. + u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width); + bool cond_met = false; + switch (begin_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(); + } + } else if (auto end_cond = std::get_if(&cur_opcode.opcode)) { + // Decrement the condition depth. + // We will assume, graciously, that mismatched conditional block ends are a nop. + if (condition_depth > 0) { + condition_depth--; + } + } else if (auto ctrl_loop = std::get_if(&cur_opcode.opcode)) { + if (ctrl_loop->start_loop) { + // Start a loop. + registers[ctrl_loop->reg_index] = ctrl_loop->num_iters; + loop_tops[ctrl_loop->reg_index] = instruction_ptr; + } else { + // End a loop. + registers[ctrl_loop->reg_index]--; + if (registers[ctrl_loop->reg_index] != 0) { + instruction_ptr = loop_tops[ctrl_loop->reg_index]; + } + } + } else if (auto ldr_static = std::get_if(&cur_opcode.opcode)) { + // Set a register to a static value. + registers[ldr_static->reg_index] = ldr_static->value; + } else if (auto ldr_memory = std::get_if(&cur_opcode.opcode)) { + // Choose source address. + u64 src_address; + if (ldr_memory->load_from_reg) { + src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address; + } else { + src_address = + GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address); + } + // Read into register. Gateway only reads on valid bitwidth. + switch (ldr_memory->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(src_address, ®isters[ldr_memory->reg_index], + ldr_memory->bit_width); + break; + } + } else if (auto str_static = std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_address = registers[str_static->reg_index]; + u64 dst_value = str_static->value; + if (str_static->add_offset_reg) { + dst_address += registers[str_static->offset_reg_index]; + } + // Write value to memory. Gateway only writes on valid bitwidth. + switch (str_static->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width); + break; + } + // Increment register if relevant. + if (str_static->increment_reg) { + registers[str_static->reg_index] += str_static->bit_width; + } + } else if (auto perform_math_static = + std::get_if(&cur_opcode.opcode)) { + // Do requested math. + switch (perform_math_static->math_type) { + case RegisterArithmeticType::Addition: + registers[perform_math_static->reg_index] += + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::Subtraction: + registers[perform_math_static->reg_index] -= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::Multiplication: + registers[perform_math_static->reg_index] *= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::LeftShift: + registers[perform_math_static->reg_index] <<= + static_cast(perform_math_static->value); + break; + case RegisterArithmeticType::RightShift: + registers[perform_math_static->reg_index] >>= + static_cast(perform_math_static->value); + break; + default: + // Do not handle extensions here. + break; + } + // Apply bit width. + switch (perform_math_static->bit_width) { + case 1: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 2: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 4: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + case 8: + registers[perform_math_static->reg_index] = + static_cast(registers[perform_math_static->reg_index]); + break; + } + } else if (auto begin_keypress_cond = + std::get_if(&cur_opcode.opcode)) { + // Check for keypress. + if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) { + // Keys not pressed. Skip conditional block. + SkipConditionalBlock(); + } + } else if (auto perform_math_reg = + std::get_if(&cur_opcode.opcode)) { + const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index]; + const u64 operand_2_value = + perform_math_reg->has_immediate + ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width) + : registers[perform_math_reg->src_reg_2_index]; + + u64 res_val = 0; + // Do requested math. + switch (perform_math_reg->math_type) { + case RegisterArithmeticType::Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType::Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType::Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType::LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType::RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType::LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType::LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType::LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType::LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType::None: + res_val = operand_1_value; + break; + } + + // Apply bit width. + switch (perform_math_reg->bit_width) { + case 1: + res_val = static_cast(res_val); + break; + case 2: + res_val = static_cast(res_val); + break; + case 4: + res_val = static_cast(res_val); + break; + case 8: + res_val = static_cast(res_val); + break; + } + + // Save to register. + registers[perform_math_reg->dst_reg_index] = res_val; + } else if (auto str_register = + std::get_if(&cur_opcode.opcode)) { + // Calculate address. + u64 dst_value = registers[str_register->str_reg_index]; + u64 dst_address = registers[str_register->addr_reg_index]; + switch (str_register->ofs_type) { + case StoreRegisterOffsetType::None: + // Nothing more to do + break; + case StoreRegisterOffsetType::Reg: + dst_address += registers[str_register->ofs_reg_index]; + break; + case StoreRegisterOffsetType::Imm: + dst_address += str_register->rel_address; + break; + case StoreRegisterOffsetType::MemReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index]); + break; + case StoreRegisterOffsetType::MemImm: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + str_register->rel_address); + break; + case StoreRegisterOffsetType::MemImmReg: + dst_address = GetCheatProcessAddress(metadata, str_register->mem_type, + registers[str_register->addr_reg_index] + + str_register->rel_address); + break; + } + + // Write value to memory. Write only on valid bitwidth. + switch (str_register->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width); + break; + } + + // Increment register if relevant. + if (str_register->increment_reg) { + registers[str_register->addr_reg_index] += str_register->bit_width; + } + } else if (auto begin_reg_cond = + std::get_if(&cur_opcode.opcode)) { + // Get value from register. + u64 src_value = 0; + switch (begin_reg_cond->bit_width) { + case 1: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = + static_cast(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = static_cast(registers[begin_reg_cond->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + + // Read value from memory. + u64 cond_value = 0; + if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) { + cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width); + } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) { + switch (begin_reg_cond->bit_width) { + case 1: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFul); + break; + case 2: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = + static_cast(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast(registers[begin_reg_cond->other_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (begin_reg_cond->comp_type) { + case CompareRegisterValueType::MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + begin_reg_cond->rel_address); + break; + case CompareRegisterValueType::MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type, + registers[begin_reg_cond->ofs_reg_index]); + break; + case CompareRegisterValueType::RegisterRelAddr: + cond_address = + registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address; + break; + case CompareRegisterValueType::RegisterOfsReg: + cond_address = registers[begin_reg_cond->addr_reg_index] + + registers[begin_reg_cond->ofs_reg_index]; + break; + default: + break; + } + switch (begin_reg_cond->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width); + break; + } + } + + // Check against condition. + bool cond_met = false; + switch (begin_reg_cond->cond_type) { + case ConditionalComparisonType::GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType::GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType::LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType::LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType::EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType::NE: + cond_met = src_value != cond_value; + break; + } + + // Skip conditional block if condition not met. + if (!cond_met) { + SkipConditionalBlock(); + } + } else if (auto save_restore_reg = + std::get_if(&cur_opcode.opcode)) { + // Save or restore a register. + switch (save_restore_reg->op_type) { + case SaveRestoreRegisterOpType::ClearRegs: + registers[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::ClearSaved: + saved_values[save_restore_reg->dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index]; + break; + case SaveRestoreRegisterOpType::Restore: + default: + registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index]; + break; + } + } else if (auto save_restore_regmask = + std::get_if(&cur_opcode.opcode)) { + // Save or restore register mask. + u64* src; + u64* dst; + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::Save: + src = registers.data(); + dst = saved_values.data(); + break; + case SaveRestoreRegisterOpType::ClearRegs: + case SaveRestoreRegisterOpType::Restore: + default: + src = registers.data(); + dst = saved_values.data(); + break; + } + for (std::size_t i = 0; i < NumRegisters; i++) { + if (save_restore_regmask->should_operate[i]) { + switch (save_restore_regmask->op_type) { + case SaveRestoreRegisterOpType::ClearSaved: + case SaveRestoreRegisterOpType::ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType::Save: + case SaveRestoreRegisterOpType::Restore: + default: + dst[i] = src[i]; + break; + } + } + } + } else if (auto debug_log = std::get_if(&cur_opcode.opcode)) { + // Read value from memory. + u64 log_value = 0; + if (debug_log->val_type == DebugLogValueType::RegisterValue) { + switch (debug_log->bit_width) { + case 1: + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast(registers[debug_log->val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = + static_cast(registers[debug_log->val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast(registers[debug_log->val_reg_index] & + 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (debug_log->val_type) { + case DebugLogValueType::MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + debug_log->rel_address); + break; + case DebugLogValueType::MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, debug_log->mem_type, + registers[debug_log->ofs_reg_index]); + break; + case DebugLogValueType::RegisterRelAddr: + val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address; + break; + case DebugLogValueType::RegisterOfsReg: + val_address = + registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index]; + break; + default: + break; + } + switch (debug_log->bit_width) { + case 1: + case 2: + case 4: + case 8: + callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width); + break; + } + } + + // Log value. + DebugLog(debug_log->log_id, log_value); + } + } +} + +} // namespace Memory diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/memory/dmnt_cheat_vm.h new file mode 100644 index 000000000..c36212cf1 --- /dev/null +++ b/src/core/memory/dmnt_cheat_vm.h @@ -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 . + */ + +/* + * 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 +#include +#include +#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 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 + 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); + ~DmntCheatVm(); + + std::size_t GetProgramSize() const { + return this->num_opcodes; + } + + bool LoadProgram(const std::vector& cheats); + void Execute(const CheatProcessMetadata& metadata); + +private: + std::unique_ptr 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 program{}; + std::array registers{}; + std::array saved_values{}; + std::array 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