summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/core.cpp25
-rw-r--r--src/core/core.h10
-rw-r--r--src/core/file_sys/cheat_engine.cpp492
-rw-r--r--src/core/file_sys/cheat_engine.h234
-rw-r--r--src/core/file_sys/patch_manager.cpp32
-rw-r--r--src/core/file_sys/patch_manager.h6
-rw-r--r--src/core/loader/nso.cpp3
-rw-r--r--src/core/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
-rw-r--r--src/core/memory/dmnt_cheat_types.h58
-rw-r--r--src/core/memory/dmnt_cheat_vm.cpp1212
-rw-r--r--src/core/memory/dmnt_cheat_vm.h321
13 files changed, 1960 insertions, 760 deletions
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<FileSys::CheatEngine> cheat_engine;
+ std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Tools::Freezer> memory_freezer;
/// Frontend applets
@@ -544,13 +549,6 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
return impl->debug_context.get();
}
-void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
- const std::string& build_id, VAddr code_region_start,
- VAddr code_region_end) {
- impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
- code_region_start, code_region_end);
-}
-
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
impl->virtual_filesystem = std::move(vfs);
}
@@ -559,6 +557,13 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
return impl->virtual_filesystem;
}
+void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
+ const std::array<u8, 32>& build_id, VAddr main_region_begin,
+ u64 main_region_size) {
+ impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
+ impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
+}
+
void System::SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set) {
impl->applet_manager.SetAppletFrontendSet(std::move(set));
}
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<FileSys::VfsFilesystem> GetFilesystem() const;
- void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
- VAddr code_region_start, VAddr code_region_end);
+ void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
+ const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
+ u64 main_region_size);
void SetAppletFrontendSet(Service::AM::Applets::AppletFrontendSet&& set);
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 <locale>
-#include "common/hex_util.h"
-#include "common/microprofile.h"
-#include "common/swap.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/core_timing_util.h"
-#include "core/file_sys/cheat_engine.h"
-#include "core/hle/kernel/process.h"
-#include "core/hle/service/hid/controllers/npad.h"
-#include "core/hle/service/hid/hid.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace FileSys {
-
-constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
-constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
-
-u64 Cheat::Address() const {
- u64 out;
- std::memcpy(&out, raw.data(), sizeof(u64));
- return Common::swap64(out) & 0xFFFFFFFFFF;
-}
-
-u64 Cheat::ValueWidth(u64 offset) const {
- return Value(offset, width);
-}
-
-u64 Cheat::Value(u64 offset, u64 width) const {
- u64 out;
- std::memcpy(&out, raw.data() + offset, sizeof(u64));
- out = Common::swap64(out);
- if (width == 8)
- return out;
- return out & ((1ull << (width * CHAR_BIT)) - 1);
-}
-
-u32 Cheat::KeypadValue() const {
- u32 out;
- std::memcpy(&out, raw.data(), sizeof(u32));
- return Common::swap32(out) & 0x0FFFFFFF;
-}
-
-void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
- VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
- this->main_region_begin = main_begin;
- this->main_region_end = main_end;
- this->heap_region_begin = heap_begin;
- this->heap_region_end = heap_end;
- this->writer = writer;
- this->reader = reader;
-}
-
-MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
-
-void CheatList::Execute() {
- MICROPROFILE_SCOPE(Cheat_Engine);
-
- std::fill(scratch.begin(), scratch.end(), 0);
- in_standard = false;
- for (std::size_t i = 0; i < master_list.size(); ++i) {
- LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
- current_block = i;
- ExecuteBlock(master_list[i].second);
- }
-
- in_standard = true;
- for (std::size_t i = 0; i < standard_list.size(); ++i) {
- LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
- current_block = i;
- ExecuteBlock(standard_list[i].second);
- }
-}
-
-CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
- : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
-
-bool CheatList::EvaluateConditional(const Cheat& cheat) const {
- using ComparisonFunction = bool (*)(u64, u64);
- constexpr std::array<ComparisonFunction, 6> comparison_functions{
- [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
- [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
- [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
- };
-
- if (cheat.type == CodeType::ConditionalInput) {
- const auto applet_resource =
- system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
- if (applet_resource == nullptr) {
- LOG_WARNING(
- Common_Filesystem,
- "Attempted to evaluate input conditional, but applet resource is not initialized!");
- return false;
- }
-
- const auto press_state =
- applet_resource
- ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
- .GetAndResetPressState();
- return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
- }
-
- ASSERT(cheat.type == CodeType::Conditional);
-
- const auto offset =
- cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
- ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
- auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
- const auto addr = cheat.Address() + offset;
-
- return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
-}
-
-void CheatList::ProcessBlockPairs(const Block& block) {
- block_pairs.clear();
-
- u64 scope = 0;
- std::map<u64, u64> pairs;
-
- for (std::size_t i = 0; i < block.size(); ++i) {
- const auto& cheat = block[i];
-
- switch (cheat.type) {
- case CodeType::Conditional:
- case CodeType::ConditionalInput:
- pairs.insert_or_assign(scope, i);
- ++scope;
- break;
- case CodeType::EndConditional: {
- --scope;
- const auto idx = pairs.at(scope);
- block_pairs.insert_or_assign(idx, i);
- break;
- }
- case CodeType::Loop: {
- if (cheat.end_of_loop) {
- --scope;
- const auto idx = pairs.at(scope);
- block_pairs.insert_or_assign(idx, i);
- } else {
- pairs.insert_or_assign(scope, i);
- ++scope;
- }
- break;
- }
- }
- }
-}
-
-void CheatList::WriteImmediate(const Cheat& cheat) {
- const auto offset =
- cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
- const auto& register_3 = scratch.at(cheat.register_3);
-
- const auto addr = cheat.Address() + offset + register_3;
- LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
- cheat.Value(8, cheat.width));
- writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
-}
-
-void CheatList::BeginConditional(const Cheat& cheat) {
- if (EvaluateConditional(cheat)) {
- return;
- }
-
- const auto iter = block_pairs.find(current_index);
- ASSERT(iter != block_pairs.end());
- current_index = iter->second - 1;
-}
-
-void CheatList::EndConditional(const Cheat& cheat) {
- LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
-}
-
-void CheatList::Loop(const Cheat& cheat) {
- if (cheat.end_of_loop.Value())
- ASSERT(!cheat.end_of_loop.Value());
-
- auto& register_3 = scratch.at(cheat.register_3);
- const auto iter = block_pairs.find(current_index);
- ASSERT(iter != block_pairs.end());
- ASSERT(iter->first < iter->second);
-
- const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
- for (s32 i = initial_value; i >= 0; --i) {
- register_3 = static_cast<u64>(i);
- for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
- current_index = c;
- ExecuteSingleCheat(
- (in_standard ? standard_list : master_list)[current_block].second[c]);
- }
- }
-
- current_index = iter->second;
-}
-
-void CheatList::LoadImmediate(const Cheat& cheat) {
- auto& register_3 = scratch.at(cheat.register_3);
-
- LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
- cheat.Value(4, 8));
- register_3 = cheat.Value(4, 8);
-}
-
-void CheatList::LoadIndexed(const Cheat& cheat) {
- const auto offset =
- cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
- auto& register_3 = scratch.at(cheat.register_3);
-
- const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
- LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
- cheat.register_3, addr);
- register_3 = reader(cheat.width, SanitizeAddress(addr));
-}
-
-void CheatList::StoreIndexed(const Cheat& cheat) {
- const auto& register_3 = scratch.at(cheat.register_3);
-
- const auto addr =
- register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
- LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
- cheat.Value(4, cheat.width), addr);
- writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
-}
-
-void CheatList::RegisterArithmetic(const Cheat& cheat) {
- using ArithmeticFunction = u64 (*)(u64, u64);
- constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
- [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
- [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
- [](u64 a, u64 b) { return a >> b; },
- };
-
- using ArithmeticOverflowCheck = bool (*)(u64, u64);
- constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
- [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
- [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
- [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
- [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
- [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
- };
-
- static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
- "Missing or have extra arithmetic overflow checks compared to functions!");
-
- auto& register_3 = scratch.at(cheat.register_3);
-
- ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
- auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
- auto* overflow_function =
- arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
- LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
- cheat.register_3, cheat.ValueWidth(4));
-
- if (overflow_function(register_3, cheat.ValueWidth(4))) {
- LOG_WARNING(Common_Filesystem,
- "overflow will occur when performing arithmetic operation={:02X} with operands "
- "a={:016X}, b={:016X}!",
- static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
- }
-
- register_3 = function(register_3, cheat.ValueWidth(4));
-}
-
-void CheatList::BeginConditionalInput(const Cheat& cheat) {
- if (EvaluateConditional(cheat))
- return;
-
- const auto iter = block_pairs.find(current_index);
- ASSERT(iter != block_pairs.end());
- current_index = iter->second - 1;
-}
-
-VAddr CheatList::SanitizeAddress(VAddr in) const {
- if ((in < main_region_begin || in >= main_region_end) &&
- (in < heap_region_begin || in >= heap_region_end)) {
- LOG_ERROR(Common_Filesystem,
- "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
- "the cheat may be incorrect. However, this may be normal early in execution if "
- "the game has not properly set up yet.",
- in);
- return 0; ///< Invalid addresses will hard crash
- }
-
- return in;
-}
-
-void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
- using CheatOperationFunction = void (CheatList::*)(const Cheat&);
- constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
- &CheatList::WriteImmediate, &CheatList::BeginConditional,
- &CheatList::EndConditional, &CheatList::Loop,
- &CheatList::LoadImmediate, &CheatList::LoadIndexed,
- &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
- &CheatList::BeginConditionalInput,
- };
-
- const auto index = static_cast<u8>(cheat.type.Value());
- ASSERT(index < sizeof(cheat_operation_functions));
- const auto op = cheat_operation_functions[index];
- (this->*op)(cheat);
-}
-
-void CheatList::ExecuteBlock(const Block& block) {
- encountered_loops.clear();
-
- ProcessBlockPairs(block);
- for (std::size_t i = 0; i < block.size(); ++i) {
- current_index = i;
- ExecuteSingleCheat(block[i]);
- i = current_index;
- }
-}
-
-CheatParser::~CheatParser() = default;
-
-CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
- CheatList::ProgramSegment standard) const {
- return {system, std::move(master), std::move(standard)};
-}
-
-TextCheatParser::~TextCheatParser() = default;
-
-CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
- std::stringstream ss;
- ss.write(reinterpret_cast<const char*>(data.data()), data.size());
-
- std::vector<std::string> lines;
- std::string stream_line;
- while (std::getline(ss, stream_line)) {
- // Remove a trailing \r
- if (!stream_line.empty() && stream_line.back() == '\r')
- stream_line.pop_back();
- lines.push_back(std::move(stream_line));
- }
-
- CheatList::ProgramSegment master_list;
- CheatList::ProgramSegment standard_list;
-
- for (std::size_t i = 0; i < lines.size(); ++i) {
- auto line = lines[i];
-
- if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
- const auto master = line[0] == '{';
- const auto begin = master ? line.find('{') : line.find('[');
- const auto end = master ? line.rfind('}') : line.rfind(']');
-
- ASSERT(begin != std::string::npos && end != std::string::npos);
-
- const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
- CheatList::Block block{};
-
- while (i < lines.size() - 1) {
- line = lines[++i];
- if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
- --i;
- break;
- }
-
- if (line.size() < 8)
- continue;
-
- Cheat out{};
- out.raw = ParseSingleLineCheat(line);
- block.push_back(out);
- }
-
- (master ? master_list : standard_list).emplace_back(patch_name, block);
- }
- }
-
- return MakeCheatList(system, master_list, standard_list);
-}
-
-std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
- std::array<u8, 16> out{};
-
- if (line.size() < 8)
- return out;
-
- const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
- std::memcpy(out.data(), word1.data(), sizeof(u32));
-
- if (line.size() < 17 || line[8] != ' ')
- return out;
-
- const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
- std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
-
- if (line.size() < 26 || line[17] != ' ') {
- // Perform shifting in case value is truncated early.
- const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
- if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
- type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
- std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
- std::memset(out.data() + 4, 0, sizeof(u32));
- }
-
- return out;
- }
-
- const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
- std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
-
- if (line.size() < 35 || line[26] != ' ') {
- // Perform shifting in case value is truncated early.
- const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
- if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
- std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
- std::memset(out.data() + 8, 0, sizeof(u32));
- }
-
- return out;
- }
-
- const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
- std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
-
- return out;
-}
-
-namespace {
-u64 MemoryReadImpl(u32 width, VAddr addr) {
- switch (width) {
- case 1:
- return Memory::Read8(addr);
- case 2:
- return Memory::Read16(addr);
- case 4:
- return Memory::Read32(addr);
- case 8:
- return Memory::Read64(addr);
- default:
- UNREACHABLE();
- return 0;
- }
-}
-
-void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
- switch (width) {
- case 1:
- Memory::Write8(addr, static_cast<u8>(value));
- break;
- case 2:
- Memory::Write16(addr, static_cast<u16>(value));
- break;
- case 4:
- Memory::Write32(addr, static_cast<u32>(value));
- break;
- case 8:
- Memory::Write64(addr, value);
- break;
- default:
- UNREACHABLE();
- }
-}
-} // Anonymous namespace
-
-CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
- const std::string& build_id, VAddr code_region_start,
- VAddr code_region_end)
- : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
- event = core_timing.RegisterEvent(
- "CheatEngine::FrameCallback::" + build_id,
- [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
- core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
-
- const auto& vm_manager = system.CurrentProcess()->VMManager();
- for (auto& list : this->cheats) {
- list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
- code_region_end, vm_manager.GetHeapRegionEndAddress(),
- &MemoryWriteImpl, &MemoryReadImpl);
- }
-}
-
-CheatEngine::~CheatEngine() {
- core_timing.UnscheduleEvent(event, 0);
-}
-
-void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
- for (auto& list : cheats) {
- list.Execute();
- }
-
- core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
-}
-
-} // namespace FileSys
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 <map>
-#include <set>
-#include <vector>
-#include "common/bit_field.h"
-#include "common/common_types.h"
-
-namespace Core {
-class System;
-}
-
-namespace Core::Timing {
-class CoreTiming;
-struct EventType;
-} // namespace Core::Timing
-
-namespace FileSys {
-
-enum class CodeType : u32 {
- // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
- // Writes a T sized value Y to the address A added to the value of register R in memory domain M
- WriteImmediate = 0,
-
- // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
- // Compares the T sized value Y to the value at address A in memory domain M using the
- // conditional function C. If success, continues execution. If failure, jumps to the matching
- // EndConditional statement.
- Conditional = 1,
-
- // 20000000
- // Terminates a Conditional or ConditionalInput block.
- EndConditional = 2,
-
- // 300R0000 VVVVVVVV
- // Starts looping V times, storing the current count in register R.
- // Loop block is terminated with a matching 310R0000.
- Loop = 3,
-
- // 400R0000 VVVVVVVV VVVVVVVV
- // Sets the value of register R to the value V.
- LoadImmediate = 4,
-
- // 5TMRI0AA AAAAAAAA
- // Sets the value of register R to the value of width T at address A in memory domain M, with
- // the current value of R added to the address if I == 1.
- LoadIndexed = 5,
-
- // 6T0RIFG0 VVVVVVVV VVVVVVVV
- // Writes the value V of width T to the memory address stored in register R. Adds the value of
- // register G to the final calculation if F is nonzero. Increments the value of register R by T
- // after operation if I is nonzero.
- StoreIndexed = 6,
-
- // 7T0RA000 VVVVVVVV
- // Performs the arithmetic operation A on the value in register R and the value V of width T,
- // storing the result in register R.
- RegisterArithmetic = 7,
-
- // 8KKKKKKK
- // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
- // execution continues. If none are, execution skips to the next EndConditional command.
- ConditionalInput = 8,
-};
-
-enum class MemoryType : u32 {
- // Addressed relative to start of main NSO
- MainNSO = 0,
-
- // Addressed relative to start of heap
- Heap = 1,
-};
-
-enum class ArithmeticOp : u32 {
- Add = 0,
- Sub = 1,
- Mult = 2,
- LShift = 3,
- RShift = 4,
-};
-
-enum class ComparisonOp : u32 {
- GreaterThan = 1,
- GreaterThanEqual = 2,
- LessThan = 3,
- LessThanEqual = 4,
- Equal = 5,
- Inequal = 6,
-};
-
-union Cheat {
- std::array<u8, 16> raw;
-
- BitField<4, 4, CodeType> type;
- BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
- BitField<0, 4, u32> end_of_loop;
- BitField<12, 4, MemoryType> memory_type;
- BitField<8, 4, u32> register_3;
- BitField<8, 4, ComparisonOp> comparison_op;
- BitField<20, 4, u32> load_from_register;
- BitField<20, 4, u32> increment_register;
- BitField<20, 4, ArithmeticOp> arithmetic_op;
- BitField<16, 4, u32> add_additional_register;
- BitField<28, 4, u32> register_6;
-
- u64 Address() const;
- u64 ValueWidth(u64 offset) const;
- u64 Value(u64 offset, u64 width) const;
- u32 KeypadValue() const;
-};
-
-class CheatParser;
-
-// Represents a full collection of cheats for a game. The Execute function should be called every
-// interval that all cheats should be executed. Clients should not directly instantiate this class
-// (hence private constructor), they should instead receive an instance from CheatParser, which
-// guarantees the list is always in an acceptable state.
-class CheatList {
-public:
- friend class CheatParser;
-
- using Block = std::vector<Cheat>;
- using ProgramSegment = std::vector<std::pair<std::string, Block>>;
-
- // (width in bytes, address, value)
- using MemoryWriter = void (*)(u32, VAddr, u64);
- // (width in bytes, address) -> value
- using MemoryReader = u64 (*)(u32, VAddr);
-
- void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
- MemoryWriter writer, MemoryReader reader);
-
- void Execute();
-
-private:
- CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
-
- void ProcessBlockPairs(const Block& block);
- void ExecuteSingleCheat(const Cheat& cheat);
-
- void ExecuteBlock(const Block& block);
-
- bool EvaluateConditional(const Cheat& cheat) const;
-
- // Individual cheat operations
- void WriteImmediate(const Cheat& cheat);
- void BeginConditional(const Cheat& cheat);
- void EndConditional(const Cheat& cheat);
- void Loop(const Cheat& cheat);
- void LoadImmediate(const Cheat& cheat);
- void LoadIndexed(const Cheat& cheat);
- void StoreIndexed(const Cheat& cheat);
- void RegisterArithmetic(const Cheat& cheat);
- void BeginConditionalInput(const Cheat& cheat);
-
- VAddr SanitizeAddress(VAddr in) const;
-
- // Master Codes are defined as codes that cannot be disabled and are run prior to all
- // others.
- ProgramSegment master_list;
- // All other codes
- ProgramSegment standard_list;
-
- bool in_standard = false;
-
- // 16 (0x0-0xF) scratch registers that can be used by cheats
- std::array<u64, 16> scratch{};
-
- MemoryWriter writer = nullptr;
- MemoryReader reader = nullptr;
-
- u64 main_region_begin{};
- u64 heap_region_begin{};
- u64 main_region_end{};
- u64 heap_region_end{};
-
- u64 current_block{};
- // The current index of the cheat within the current Block
- u64 current_index{};
-
- // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
- // pushed onto this queue. When a end block is encountered, the condition is checked.
- std::map<u64, u64> block_pairs;
-
- std::set<u64> encountered_loops;
-
- const Core::System* system;
-};
-
-// Intermediary class that parses a text file or other disk format for storing cheats into a
-// CheatList object, that can be used for execution.
-class CheatParser {
-public:
- virtual ~CheatParser();
-
- virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
-
-protected:
- CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
- CheatList::ProgramSegment standard) const;
-};
-
-// CheatParser implementation that parses text files
-class TextCheatParser final : public CheatParser {
-public:
- ~TextCheatParser() override;
-
- CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
-
-private:
- std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
-};
-
-// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
-class CheatEngine final {
-public:
- CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
- VAddr code_region_start, VAddr code_region_end);
- ~CheatEngine();
-
-private:
- void FrameCallback(u64 userdata, s64 cycles_late);
-
- std::vector<CheatList> cheats;
-
- Core::Timing::EventType* event;
- Core::Timing::CoreTiming& core_timing;
-};
-
-} // namespace FileSys
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<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
-static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
- const std::array<u8, 0x20>& build_id_,
- const VirtualDir& base_path, bool upper) {
+namespace {
+std::optional<std::vector<Memory::CheatEntry>> ReadCheatFileFromFolder(
+ const Core::System& system, u64 title_id, const std::array<u8, 0x20>& build_id_,
+ const VirtualDir& base_path, bool upper) {
const auto build_id_raw = Common::HexToString(build_id_, upper);
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
@@ -267,14 +269,16 @@ static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& syst
return std::nullopt;
}
- TextCheatParser parser;
- return parser.Parse(system, data);
+ Memory::TextCheatParser parser;
+ return parser.Parse(
+ system, std::string_view(reinterpret_cast<const char* const>(data.data()), data.size()));
}
-std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
- const std::array<u8, 32>& build_id_) const {
- const auto load_dir =
- Core::System::GetInstance().GetFileSystemController().GetModificationLoadRoot(title_id);
+} // Anonymous namespace
+
+std::vector<Memory::CheatEntry> PatchManager::CreateCheatList(
+ const Core::System& system, const std::array<u8, 32>& build_id_) const {
+ const auto load_dir = system.GetFileSystemController().GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id);
return {};
@@ -284,20 +288,20 @@ std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- std::vector<CheatList> out;
- out.reserve(patch_dirs.size());
+ std::vector<Memory::CheatEntry> out;
for (const auto& subdir : patch_dirs) {
auto cheats_dir = subdir->GetSubdirectory("cheats");
if (cheats_dir != nullptr) {
auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
if (res.has_value()) {
- out.push_back(std::move(*res));
+ std::copy(res->begin(), res->end(), std::back_inserter(out));
continue;
}
res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
- if (res.has_value())
- out.push_back(std::move(*res));
+ if (res.has_value()) {
+ std::copy(res->begin(), res->end(), std::back_inserter(out));
+ }
}
}
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 <memory>
#include <string>
#include "common/common_types.h"
-#include "core/file_sys/cheat_engine.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs.h"
+#include "core/memory/dmnt_cheat_types.h"
namespace Core {
class System;
@@ -51,8 +51,8 @@ public:
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
// Creates a CheatList object with all
- std::vector<CheatList> CreateCheatList(const Core::System& system,
- const std::array<u8, 0x20>& build_id) const;
+ std::vector<Memory::CheatEntry> CreateCheatList(const Core::System& system,
+ const std::array<u8, 0x20>& build_id) const;
// Currently tracked RomFS patches:
// - Game Updates
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<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
auto& system = Core::System::GetInstance();
const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
if (!cheats.empty()) {
- system.RegisterCheatList(cheats, Common::HexToString(nso_header.build_id), load_base,
- load_base + program_image.size());
+ system.RegisterCheatList(cheats, nso_header.build_id, load_base, image_size);
}
}
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 <locale>
+#include "common/hex_util.h"
+#include "common/microprofile.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+#include "core/memory/cheat_engine.h"
+
+namespace Memory {
+
+constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 12);
+constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
+
+StandardVmCallbacks::StandardVmCallbacks(const Core::System& system,
+ const CheatProcessMetadata& metadata)
+ : system(system), metadata(metadata) {}
+
+StandardVmCallbacks::~StandardVmCallbacks() = default;
+
+void StandardVmCallbacks::MemoryRead(VAddr address, void* data, u64 size) {
+ ReadBlock(SanitizeAddress(address), data, size);
+}
+
+void StandardVmCallbacks::MemoryWrite(VAddr address, const void* data, u64 size) {
+ WriteBlock(SanitizeAddress(address), data, size);
+}
+
+u64 StandardVmCallbacks::HidKeysDown() {
+ const auto applet_resource =
+ system.ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
+ if (applet_resource == nullptr) {
+ LOG_WARNING(CheatEngine,
+ "Attempted to read input state, but applet resource is not initialized!");
+ return false;
+ }
+
+ const auto press_state =
+ applet_resource
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
+ .GetAndResetPressState();
+ return press_state & KEYPAD_BITMASK;
+}
+
+void StandardVmCallbacks::DebugLog(u8 id, u64 value) {
+ LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
+}
+
+void StandardVmCallbacks::CommandLog(std::string_view data) {
+ LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
+ data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
+}
+
+VAddr StandardVmCallbacks::SanitizeAddress(VAddr in) const {
+ if ((in < metadata.main_nso_extents.base ||
+ in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
+ (in < metadata.heap_extents.base ||
+ in >= metadata.heap_extents.base + metadata.heap_extents.size)) {
+ LOG_ERROR(CheatEngine,
+ "Cheat attempting to access memory at invalid address={:016X}, if this "
+ "persists, "
+ "the cheat may be incorrect. However, this may be normal early in execution if "
+ "the game has not properly set up yet.",
+ in);
+ return 0; ///< Invalid addresses will hard crash
+ }
+
+ return in;
+}
+
+CheatParser::~CheatParser() = default;
+
+TextCheatParser::~TextCheatParser() = default;
+
+namespace {
+template <char match>
+std::string_view ExtractName(std::string_view data, std::size_t start_index) {
+ auto end_index = start_index;
+ while (data[end_index] != match) {
+ ++end_index;
+ if (end_index > data.size() ||
+ (end_index - start_index - 1) > sizeof(CheatDefinition::readable_name)) {
+ return {};
+ }
+ }
+
+ return data.substr(start_index, end_index - start_index);
+}
+} // Anonymous namespace
+
+std::vector<CheatEntry> TextCheatParser::Parse(const Core::System& system,
+ std::string_view data) const {
+ std::vector<CheatEntry> out(1);
+ std::optional<u64> current_entry = std::nullopt;
+
+ for (std::size_t i = 0; i < data.size(); ++i) {
+ if (::isspace(data[i])) {
+ continue;
+ }
+
+ if (data[i] == '{') {
+ current_entry = 0;
+
+ if (out[*current_entry].definition.num_opcodes > 0) {
+ return {};
+ }
+
+ const auto name = ExtractName<'}'>(data, i + 1);
+ if (name.empty()) {
+ return {};
+ }
+
+ std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
+ std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
+ name.size()));
+ out[*current_entry]
+ .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
+ '\0';
+
+ i += name.length() + 1;
+ } else if (data[i] == '[') {
+ current_entry = out.size();
+ out.emplace_back();
+
+ const auto name = ExtractName<']'>(data, i + 1);
+ if (name.empty()) {
+ return {};
+ }
+
+ std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(),
+ std::min<std::size_t>(out[*current_entry].definition.readable_name.size(),
+ name.size()));
+ out[*current_entry]
+ .definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] =
+ '\0';
+
+ i += name.length() + 1;
+ } else if (::isxdigit(data[i])) {
+ if (!current_entry || out[*current_entry].definition.num_opcodes >=
+ out[*current_entry].definition.opcodes.size()) {
+ return {};
+ }
+
+ const auto hex = std::string(data.substr(i, 8));
+ if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) {
+ return {};
+ }
+
+ out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
+ std::stoul(hex, nullptr, 0x10);
+
+ i += 8;
+ } else {
+ return {};
+ }
+ }
+
+ out[0].enabled = out[0].definition.num_opcodes > 0;
+ out[0].cheat_id = 0;
+
+ for (u32 i = 1; i < out.size(); ++i) {
+ out[i].enabled = out[i].definition.num_opcodes > 0;
+ out[i].cheat_id = i;
+ }
+
+ return out;
+}
+
+CheatEngine::CheatEngine(Core::System& system, std::vector<CheatEntry> cheats,
+ const std::array<u8, 0x20>& build_id)
+ : system{system}, core_timing{system.CoreTiming()}, vm{std::make_unique<StandardVmCallbacks>(
+ system, metadata)},
+ cheats(std::move(cheats)) {
+ metadata.main_nso_build_id = build_id;
+}
+
+CheatEngine::~CheatEngine() {
+ core_timing.UnscheduleEvent(event, 0);
+}
+
+void CheatEngine::Initialize() {
+ event = core_timing.RegisterEvent(
+ "CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
+ [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
+
+ metadata.process_id = system.CurrentProcess()->GetProcessID();
+ metadata.title_id = system.CurrentProcess()->GetTitleID();
+
+ const auto& vm_manager = system.CurrentProcess()->VMManager();
+ metadata.heap_extents = {vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionSize()};
+ metadata.address_space_extents = {vm_manager.GetAddressSpaceBaseAddress(),
+ vm_manager.GetAddressSpaceSize()};
+ metadata.alias_extents = {vm_manager.GetMapRegionBaseAddress(), vm_manager.GetMapRegionSize()};
+
+ is_pending_reload.exchange(true);
+}
+
+void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
+ metadata.main_nso_extents = {main_region_begin, main_region_size};
+}
+
+void CheatEngine::Reload(std::vector<CheatEntry> cheats) {
+ this->cheats = std::move(cheats);
+ is_pending_reload.exchange(true);
+}
+
+MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
+
+void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
+ if (is_pending_reload.exchange(false)) {
+ vm.LoadProgram(cheats);
+ }
+
+ if (vm.GetProgramSize() == 0) {
+ return;
+ }
+
+ MICROPROFILE_SCOPE(Cheat_Engine);
+
+ vm.Execute(metadata);
+
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
+}
+
+} // namespace Memory
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 <atomic>
+#include <vector>
+#include "common/common_types.h"
+#include "core/memory/dmnt_cheat_types.h"
+#include "core/memory/dmnt_cheat_vm.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+class CoreTiming;
+struct EventType;
+} // namespace Core::Timing
+
+namespace Memory {
+
+class StandardVmCallbacks : public DmntCheatVm::Callbacks {
+public:
+ StandardVmCallbacks(const Core::System& system, const CheatProcessMetadata& metadata);
+ ~StandardVmCallbacks() override;
+
+ void MemoryRead(VAddr address, void* data, u64 size) override;
+ void MemoryWrite(VAddr address, const void* data, u64 size) override;
+ u64 HidKeysDown() override;
+ void DebugLog(u8 id, u64 value) override;
+ void CommandLog(std::string_view data) override;
+
+private:
+ VAddr SanitizeAddress(VAddr address) const;
+
+ const CheatProcessMetadata& metadata;
+ const Core::System& system;
+};
+
+// Intermediary class that parses a text file or other disk format for storing cheats into a
+// CheatList object, that can be used for execution.
+class CheatParser {
+public:
+ virtual ~CheatParser();
+
+ virtual std::vector<CheatEntry> Parse(const Core::System& system,
+ std::string_view data) const = 0;
+};
+
+// CheatParser implementation that parses text files
+class TextCheatParser final : public CheatParser {
+public:
+ ~TextCheatParser() override;
+
+ std::vector<CheatEntry> Parse(const Core::System& system, std::string_view data) const override;
+};
+
+// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
+class CheatEngine final {
+public:
+ CheatEngine(Core::System& system_, std::vector<CheatEntry> cheats_,
+ const std::array<u8, 0x20>& build_id);
+ ~CheatEngine();
+
+ void Initialize();
+ void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size);
+
+ void Reload(std::vector<CheatEntry> cheats);
+
+private:
+ void FrameCallback(u64 userdata, s64 cycles_late);
+
+ DmntCheatVm vm;
+ CheatProcessMetadata metadata;
+
+ std::vector<CheatEntry> cheats;
+ std::atomic_bool is_pending_reload{false};
+
+ Core::Timing::EventType* event{};
+ Core::Timing::CoreTiming& core_timing;
+ Core::System& system;
+};
+
+} // namespace Memory
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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Adapted by DarkLordZach for use/interaction with yuzu
+ *
+ * Modifications Copyright 2019 yuzu emulator team
+ * Licensed under GPLv2 or any later version
+ * Refer to the license.txt file included.
+ */
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace Memory {
+
+struct MemoryRegionExtents {
+ u64 base{};
+ u64 size{};
+};
+
+struct CheatProcessMetadata {
+ u64 process_id{};
+ u64 title_id{};
+ MemoryRegionExtents main_nso_extents{};
+ MemoryRegionExtents heap_extents{};
+ MemoryRegionExtents alias_extents{};
+ MemoryRegionExtents address_space_extents{};
+ std::array<u8, 0x20> main_nso_build_id{};
+};
+
+struct CheatDefinition {
+ std::array<char, 0x40> readable_name{};
+ u32 num_opcodes{};
+ std::array<u32, 0x100> opcodes{};
+};
+
+struct CheatEntry {
+ bool enabled{};
+ u32 cheat_id{};
+ CheatDefinition definition{};
+};
+
+} // namespace Memory
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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Adapted by DarkLordZach for use/interaction with yuzu
+ *
+ * Modifications Copyright 2019 yuzu emulator team
+ * Licensed under GPLv2 or any later version
+ * Refer to the license.txt file included.
+ */
+
+#include "common/assert.h"
+#include "common/scope_exit.h"
+#include "core/memory/dmnt_cheat_types.h"
+#include "core/memory/dmnt_cheat_vm.h"
+
+namespace Memory {
+
+DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks) : callbacks(std::move(callbacks)) {}
+
+DmntCheatVm::~DmntCheatVm() = default;
+
+void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
+ callbacks->DebugLog(static_cast<u8>(log_id), value);
+}
+
+void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
+ if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Store Static");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type)));
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
+ callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
+ } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Begin Conditional");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
+ callbacks->CommandLog(
+ fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
+ callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
+ } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: End Conditional");
+ } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
+ if (ctrl_loop->start_loop) {
+ callbacks->CommandLog("Opcode: Start Loop");
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
+ callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
+ } else {
+ callbacks->CommandLog("Opcode: End Loop");
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
+ }
+ } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Load Register Static");
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
+ callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value));
+ } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Load Register Memory");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
+ callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
+ } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Store Static to Address");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
+ if (str_static->add_offset_reg) {
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
+ }
+ callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
+ callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value));
+ } else if (auto perform_math_static =
+ std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Perform Static Arithmetic");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
+ callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
+ callbacks->CommandLog(
+ fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type)));
+ callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
+ } else if (auto begin_keypress_cond =
+ std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Begin Keypress Conditional");
+ callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
+ } else if (auto perform_math_reg =
+ std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Perform Register Arithmetic");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
+ callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
+ callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
+ if (perform_math_reg->has_immediate) {
+ callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
+ } else {
+ callbacks->CommandLog(
+ fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
+ }
+ } else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Store Register to Address");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
+ callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
+ callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
+ callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
+ switch (str_register->ofs_type) {
+ case StoreRegisterOffsetType::None:
+ break;
+ case StoreRegisterOffsetType::Reg:
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
+ break;
+ case StoreRegisterOffsetType::Imm:
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
+ break;
+ case StoreRegisterOffsetType::MemReg:
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
+ break;
+ case StoreRegisterOffsetType::MemImm:
+ case StoreRegisterOffsetType::MemImmReg:
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
+ break;
+ }
+ } else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Begin Register Conditional");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
+ callbacks->CommandLog(
+ fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type)));
+ callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
+ switch (begin_reg_cond->comp_type) {
+ case CompareRegisterValueType::StaticValue:
+ callbacks->CommandLog("Comp Type: Static Value");
+ callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
+ break;
+ case CompareRegisterValueType::OtherRegister:
+ callbacks->CommandLog("Comp Type: Other Register");
+ callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
+ break;
+ case CompareRegisterValueType::MemoryRelAddr:
+ callbacks->CommandLog("Comp Type: Memory Relative Address");
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
+ break;
+ case CompareRegisterValueType::MemoryOfsReg:
+ callbacks->CommandLog("Comp Type: Memory Offset Register");
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
+ break;
+ case CompareRegisterValueType::RegisterRelAddr:
+ callbacks->CommandLog("Comp Type: Register Relative Address");
+ callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
+ break;
+ case CompareRegisterValueType::RegisterOfsReg:
+ callbacks->CommandLog("Comp Type: Register Offset Register");
+ callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
+ break;
+ }
+ } else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Save or Restore Register");
+ callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
+ callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
+ callbacks->CommandLog(
+ fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type)));
+ } else if (auto save_restore_regmask =
+ std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Save or Restore Register Mask");
+ callbacks->CommandLog(
+ fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type)));
+ for (std::size_t i = 0; i < NumRegisters; i++) {
+ callbacks->CommandLog(
+ fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
+ }
+ } else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
+ callbacks->CommandLog("Opcode: Debug Log");
+ callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
+ callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
+ callbacks->CommandLog(
+ fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
+ switch (debug_log->val_type) {
+ case DebugLogValueType::RegisterValue:
+ callbacks->CommandLog("Val Type: Register Value");
+ callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
+ break;
+ case DebugLogValueType::MemoryRelAddr:
+ callbacks->CommandLog("Val Type: Memory Relative Address");
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
+ break;
+ case DebugLogValueType::MemoryOfsReg:
+ callbacks->CommandLog("Val Type: Memory Offset Register");
+ callbacks->CommandLog(
+ fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
+ break;
+ case DebugLogValueType::RegisterRelAddr:
+ callbacks->CommandLog("Val Type: Register Relative Address");
+ callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
+ callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
+ break;
+ case DebugLogValueType::RegisterOfsReg:
+ callbacks->CommandLog("Val Type: Register Offset Register");
+ callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
+ callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
+ break;
+ }
+ } else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
+ callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
+ }
+}
+
+DmntCheatVm::Callbacks::~Callbacks() = default;
+
+bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
+ // If we've ever seen a decode failure, return false.
+ bool valid = decode_success;
+ CheatVmOpcode opcode = {};
+ SCOPE_EXIT({
+ decode_success &= valid;
+ if (valid) {
+ out = opcode;
+ }
+ });
+
+ // Helper function for getting instruction dwords.
+ const auto GetNextDword = [&] {
+ if (instruction_ptr >= num_opcodes) {
+ valid = false;
+ return static_cast<u32>(0);
+ }
+ return program[instruction_ptr++];
+ };
+
+ // Helper function for parsing a VmInt.
+ const auto GetNextVmInt = [&](const u32 bit_width) {
+ VmInt val{};
+
+ const u32 first_dword = GetNextDword();
+ switch (bit_width) {
+ case 1:
+ val.bit8 = static_cast<u8>(first_dword);
+ break;
+ case 2:
+ val.bit16 = static_cast<u16>(first_dword);
+ break;
+ case 4:
+ val.bit32 = first_dword;
+ break;
+ case 8:
+ val.bit64 = (static_cast<u64>(first_dword) << 32ul) | static_cast<u64>(GetNextDword());
+ break;
+ }
+
+ return val;
+ };
+
+ // Read opcode.
+ const u32 first_dword = GetNextDword();
+ if (!valid) {
+ return valid;
+ }
+
+ auto opcode_type = static_cast<CheatVmOpcodeType>(((first_dword >> 28) & 0xF));
+ if (opcode_type >= CheatVmOpcodeType::ExtendedWidth) {
+ opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
+ ((first_dword >> 24) & 0xF));
+ }
+ if (opcode_type >= CheatVmOpcodeType::DoubleExtendedWidth) {
+ opcode_type = static_cast<CheatVmOpcodeType>((static_cast<u32>(opcode_type) << 4) |
+ ((first_dword >> 20) & 0xF));
+ }
+
+ // detect condition start.
+ switch (opcode_type) {
+ case CheatVmOpcodeType::BeginConditionalBlock:
+ case CheatVmOpcodeType::BeginKeypressConditionalBlock:
+ case CheatVmOpcodeType::BeginRegisterConditionalBlock:
+ opcode.begin_conditional_block = true;
+ break;
+ default:
+ opcode.begin_conditional_block = false;
+ break;
+ }
+
+ switch (opcode_type) {
+ case CheatVmOpcodeType::StoreStatic: {
+ StoreStaticOpcode store_static{};
+ // 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
+ // Read additional words.
+ const u32 second_dword = GetNextDword();
+ store_static.bit_width = (first_dword >> 24) & 0xF;
+ store_static.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
+ store_static.offset_register = ((first_dword >> 16) & 0xF);
+ store_static.rel_address =
+ (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
+ store_static.value = GetNextVmInt(store_static.bit_width);
+ opcode.opcode = store_static;
+ } break;
+ case CheatVmOpcodeType::BeginConditionalBlock: {
+ BeginConditionalOpcode begin_cond{};
+ // 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY)
+ // Read additional words.
+ const u32 second_dword = GetNextDword();
+ begin_cond.bit_width = (first_dword >> 24) & 0xF;
+ begin_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
+ begin_cond.cond_type = static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
+ begin_cond.rel_address =
+ (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
+ begin_cond.value = GetNextVmInt(begin_cond.bit_width);
+ opcode.opcode = begin_cond;
+ } break;
+ case CheatVmOpcodeType::EndConditionalBlock: {
+ // 20000000
+ // There's actually nothing left to process here!
+ opcode.opcode = EndConditionalOpcode{};
+ } break;
+ case CheatVmOpcodeType::ControlLoop: {
+ ControlLoopOpcode ctrl_loop{};
+ // 300R0000 VVVVVVVV
+ // 310R0000
+ // Parse register, whether loop start or loop end.
+ ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0;
+ ctrl_loop.reg_index = ((first_dword >> 20) & 0xF);
+
+ // Read number of iters if loop start.
+ if (ctrl_loop.start_loop) {
+ ctrl_loop.num_iters = GetNextDword();
+ }
+ opcode.opcode = ctrl_loop;
+ } break;
+ case CheatVmOpcodeType::LoadRegisterStatic: {
+ LoadRegisterStaticOpcode ldr_static{};
+ // 400R0000 VVVVVVVV VVVVVVVV
+ // Read additional words.
+ ldr_static.reg_index = ((first_dword >> 16) & 0xF);
+ ldr_static.value =
+ (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
+ opcode.opcode = ldr_static;
+ } break;
+ case CheatVmOpcodeType::LoadRegisterMemory: {
+ LoadRegisterMemoryOpcode ldr_memory{};
+ // 5TMRI0AA AAAAAAAA
+ // Read additional words.
+ const u32 second_dword = GetNextDword();
+ ldr_memory.bit_width = (first_dword >> 24) & 0xF;
+ ldr_memory.mem_type = static_cast<MemoryAccessType>((first_dword >> 20) & 0xF);
+ ldr_memory.reg_index = ((first_dword >> 16) & 0xF);
+ ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0;
+ ldr_memory.rel_address =
+ (static_cast<u64>(first_dword & 0xFF) << 32ul) | static_cast<u64>(second_dword);
+ opcode.opcode = ldr_memory;
+ } break;
+ case CheatVmOpcodeType::StoreStaticToAddress: {
+ StoreStaticToAddressOpcode str_static{};
+ // 6T0RIor0 VVVVVVVV VVVVVVVV
+ // Read additional words.
+ str_static.bit_width = (first_dword >> 24) & 0xF;
+ str_static.reg_index = ((first_dword >> 16) & 0xF);
+ str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0;
+ str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0;
+ str_static.offset_reg_index = ((first_dword >> 4) & 0xF);
+ str_static.value =
+ (static_cast<u64>(GetNextDword()) << 32ul) | static_cast<u64>(GetNextDword());
+ opcode.opcode = str_static;
+ } break;
+ case CheatVmOpcodeType::PerformArithmeticStatic: {
+ PerformArithmeticStaticOpcode perform_math_static{};
+ // 7T0RC000 VVVVVVVV
+ // Read additional words.
+ perform_math_static.bit_width = (first_dword >> 24) & 0xF;
+ perform_math_static.reg_index = ((first_dword >> 16) & 0xF);
+ perform_math_static.math_type =
+ static_cast<RegisterArithmeticType>((first_dword >> 12) & 0xF);
+ perform_math_static.value = GetNextDword();
+ opcode.opcode = perform_math_static;
+ } break;
+ case CheatVmOpcodeType::BeginKeypressConditionalBlock: {
+ BeginKeypressConditionalOpcode begin_keypress_cond{};
+ // 8kkkkkkk
+ // Just parse the mask.
+ begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF;
+ } break;
+ case CheatVmOpcodeType::PerformArithmeticRegister: {
+ PerformArithmeticRegisterOpcode perform_math_reg{};
+ // 9TCRSIs0 (VVVVVVVV (VVVVVVVV))
+ perform_math_reg.bit_width = (first_dword >> 24) & 0xF;
+ perform_math_reg.math_type = static_cast<RegisterArithmeticType>((first_dword >> 20) & 0xF);
+ perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF);
+ perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF);
+ perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0;
+ if (perform_math_reg.has_immediate) {
+ perform_math_reg.src_reg_2_index = 0;
+ perform_math_reg.value = GetNextVmInt(perform_math_reg.bit_width);
+ } else {
+ perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF);
+ }
+ opcode.opcode = perform_math_reg;
+ } break;
+ case CheatVmOpcodeType::StoreRegisterToAddress: {
+ StoreRegisterToAddressOpcode str_register{};
+ // ATSRIOxa (aaaaaaaa)
+ // A = opcode 10
+ // T = bit width
+ // S = src register index
+ // R = address register index
+ // I = 1 if increment address register, 0 if not increment address register
+ // O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region,
+ // 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region +
+ // Relative Address
+ // x = offset register (for offset type 1), memory type (for offset type 3)
+ // a = relative address (for offset type 2+3)
+ str_register.bit_width = (first_dword >> 24) & 0xF;
+ str_register.str_reg_index = ((first_dword >> 20) & 0xF);
+ str_register.addr_reg_index = ((first_dword >> 16) & 0xF);
+ str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0;
+ str_register.ofs_type = static_cast<StoreRegisterOffsetType>(((first_dword >> 8) & 0xF));
+ str_register.ofs_reg_index = ((first_dword >> 4) & 0xF);
+ switch (str_register.ofs_type) {
+ case StoreRegisterOffsetType::None:
+ case StoreRegisterOffsetType::Reg:
+ // Nothing more to do
+ break;
+ case StoreRegisterOffsetType::Imm:
+ str_register.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ case StoreRegisterOffsetType::MemReg:
+ str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ break;
+ case StoreRegisterOffsetType::MemImm:
+ case StoreRegisterOffsetType::MemImmReg:
+ str_register.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ str_register.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ default:
+ str_register.ofs_type = StoreRegisterOffsetType::None;
+ break;
+ }
+ opcode.opcode = str_register;
+ } break;
+ case CheatVmOpcodeType::BeginRegisterConditionalBlock: {
+ BeginRegisterConditionalOpcode begin_reg_cond{};
+ // C0TcSX##
+ // C0TcS0Ma aaaaaaaa
+ // C0TcS1Mr
+ // C0TcS2Ra aaaaaaaa
+ // C0TcS3Rr
+ // C0TcS400 VVVVVVVV (VVVVVVVV)
+ // C0TcS5X0
+ // C0 = opcode 0xC0
+ // T = bit width
+ // c = condition type.
+ // S = source register.
+ // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
+ // register,
+ // 2 = register with relative offset, 3 = register with offset register, 4 = static
+ // value, 5 = other register.
+ // M = memory type.
+ // R = address register.
+ // a = relative address.
+ // r = offset register.
+ // X = other register.
+ // V = value.
+ begin_reg_cond.bit_width = (first_dword >> 20) & 0xF;
+ begin_reg_cond.cond_type =
+ static_cast<ConditionalComparisonType>((first_dword >> 16) & 0xF);
+ begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF);
+ begin_reg_cond.comp_type = static_cast<CompareRegisterValueType>((first_dword >> 8) & 0xF);
+
+ switch (begin_reg_cond.comp_type) {
+ case CompareRegisterValueType::StaticValue:
+ begin_reg_cond.value = GetNextVmInt(begin_reg_cond.bit_width);
+ break;
+ case CompareRegisterValueType::OtherRegister:
+ begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF);
+ break;
+ case CompareRegisterValueType::MemoryRelAddr:
+ begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ begin_reg_cond.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ case CompareRegisterValueType::MemoryOfsReg:
+ begin_reg_cond.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
+ break;
+ case CompareRegisterValueType::RegisterRelAddr:
+ begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
+ begin_reg_cond.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ case CompareRegisterValueType::RegisterOfsReg:
+ begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF);
+ begin_reg_cond.ofs_reg_index = (first_dword & 0xF);
+ break;
+ }
+ opcode.opcode = begin_reg_cond;
+ } break;
+ case CheatVmOpcodeType::SaveRestoreRegister: {
+ SaveRestoreRegisterOpcode save_restore_reg{};
+ // C10D0Sx0
+ // C1 = opcode 0xC1
+ // D = destination index.
+ // S = source index.
+ // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring
+ // a register.
+ // NOTE: If we add more save slots later, current encoding is backwards compatible.
+ save_restore_reg.dst_index = (first_dword >> 16) & 0xF;
+ save_restore_reg.src_index = (first_dword >> 8) & 0xF;
+ save_restore_reg.op_type = static_cast<SaveRestoreRegisterOpType>((first_dword >> 4) & 0xF);
+ opcode.opcode = save_restore_reg;
+ } break;
+ case CheatVmOpcodeType::SaveRestoreRegisterMask: {
+ SaveRestoreRegisterMaskOpcode save_restore_regmask{};
+ // C2x0XXXX
+ // C2 = opcode 0xC2
+ // x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring.
+ // X = 16-bit bitmask, bit i --> save or restore register i.
+ save_restore_regmask.op_type =
+ static_cast<SaveRestoreRegisterOpType>((first_dword >> 20) & 0xF);
+ for (std::size_t i = 0; i < NumRegisters; i++) {
+ save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0;
+ }
+ opcode.opcode = save_restore_regmask;
+ } break;
+ case CheatVmOpcodeType::DebugLog: {
+ DebugLogOpcode debug_log{};
+ // FFFTIX##
+ // FFFTI0Ma aaaaaaaa
+ // FFFTI1Mr
+ // FFFTI2Ra aaaaaaaa
+ // FFFTI3Rr
+ // FFFTI4X0
+ // FFF = opcode 0xFFF
+ // T = bit width.
+ // I = log id.
+ // X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset
+ // register,
+ // 2 = register with relative offset, 3 = register with offset register, 4 = register
+ // value.
+ // M = memory type.
+ // R = address register.
+ // a = relative address.
+ // r = offset register.
+ // X = value register.
+ debug_log.bit_width = (first_dword >> 16) & 0xF;
+ debug_log.log_id = ((first_dword >> 12) & 0xF);
+ debug_log.val_type = static_cast<DebugLogValueType>((first_dword >> 8) & 0xF);
+
+ switch (debug_log.val_type) {
+ case DebugLogValueType::RegisterValue:
+ debug_log.val_reg_index = ((first_dword >> 4) & 0xF);
+ break;
+ case DebugLogValueType::MemoryRelAddr:
+ debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ debug_log.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ case DebugLogValueType::MemoryOfsReg:
+ debug_log.mem_type = static_cast<MemoryAccessType>((first_dword >> 4) & 0xF);
+ debug_log.ofs_reg_index = (first_dword & 0xF);
+ break;
+ case DebugLogValueType::RegisterRelAddr:
+ debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
+ debug_log.rel_address =
+ ((static_cast<u64>(first_dword & 0xF) << 32ul) | static_cast<u64>(GetNextDword()));
+ break;
+ case DebugLogValueType::RegisterOfsReg:
+ debug_log.addr_reg_index = ((first_dword >> 4) & 0xF);
+ debug_log.ofs_reg_index = (first_dword & 0xF);
+ break;
+ }
+ opcode.opcode = debug_log;
+ } break;
+ case CheatVmOpcodeType::ExtendedWidth:
+ case CheatVmOpcodeType::DoubleExtendedWidth:
+ default:
+ // Unrecognized instruction cannot be decoded.
+ valid = false;
+ opcode.opcode = UnrecognizedInstruction{opcode_type};
+ break;
+ }
+
+ // End decoding.
+ return valid;
+}
+
+void DmntCheatVm::SkipConditionalBlock() {
+ if (condition_depth > 0) {
+ // We want to continue until we're out of the current block.
+ const std::size_t desired_depth = condition_depth - 1;
+
+ CheatVmOpcode skip_opcode{};
+ while (condition_depth > desired_depth && DecodeNextOpcode(skip_opcode)) {
+ // Decode instructions until we see end of the current conditional block.
+ // NOTE: This is broken in gateway's implementation.
+ // Gateway currently checks for "0x2" instead of "0x20000000"
+ // In addition, they do a linear scan instead of correctly decoding opcodes.
+ // This causes issues if "0x2" appears as an immediate in the conditional block...
+
+ // We also support nesting of conditional blocks, and Gateway does not.
+ if (skip_opcode.begin_conditional_block) {
+ condition_depth++;
+ } else if (std::holds_alternative<EndConditionalOpcode>(skip_opcode.opcode)) {
+ condition_depth--;
+ }
+ }
+ } else {
+ // Skipping, but condition_depth = 0.
+ // This is an error condition.
+ // However, I don't actually believe it is possible for this to happen.
+ // I guess we'll throw a fatal error here, so as to encourage me to fix the VM
+ // in the event that someone triggers it? I don't know how you'd do that.
+ UNREACHABLE_MSG("Invalid condition depth in DMNT Cheat VM");
+ }
+}
+
+u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
+ switch (bit_width) {
+ case 1:
+ return value.bit8;
+ case 2:
+ return value.bit16;
+ case 4:
+ return value.bit32;
+ case 8:
+ return value.bit64;
+ default:
+ // Invalid bit width -> return 0.
+ return 0;
+ }
+}
+
+u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
+ MemoryAccessType mem_type, u64 rel_address) {
+ switch (mem_type) {
+ case MemoryAccessType::MainNso:
+ default:
+ return metadata.main_nso_extents.base + rel_address;
+ case MemoryAccessType::Heap:
+ return metadata.heap_extents.base + rel_address;
+ }
+}
+
+void DmntCheatVm::ResetState() {
+ registers.fill(0);
+ saved_values.fill(0);
+ loop_tops.fill(0);
+ instruction_ptr = 0;
+ condition_depth = 0;
+ decode_success = true;
+}
+
+bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
+ // Reset opcode count.
+ num_opcodes = 0;
+
+ for (std::size_t i = 0; i < entries.size(); i++) {
+ if (entries[i].enabled) {
+ // Bounds check.
+ if (entries[i].definition.num_opcodes + num_opcodes > MaximumProgramOpcodeCount) {
+ num_opcodes = 0;
+ return false;
+ }
+
+ for (std::size_t n = 0; n < entries[i].definition.num_opcodes; n++) {
+ program[num_opcodes++] = entries[i].definition.opcodes[n];
+ }
+ }
+ }
+
+ return true;
+}
+
+void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
+ CheatVmOpcode cur_opcode{};
+
+ // Get Keys down.
+ u64 kDown = callbacks->HidKeysDown();
+
+ callbacks->CommandLog("Started VM execution.");
+ callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
+ callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
+ callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
+
+ // Clear VM state.
+ ResetState();
+
+ // Loop until program finishes.
+ while (DecodeNextOpcode(cur_opcode)) {
+ callbacks->CommandLog(
+ fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
+
+ for (std::size_t i = 0; i < NumRegisters; i++) {
+ callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
+ }
+
+ for (std::size_t i = 0; i < NumRegisters; i++) {
+ callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
+ }
+ LogOpcode(cur_opcode);
+
+ // Increment conditional depth, if relevant.
+ if (cur_opcode.begin_conditional_block) {
+ condition_depth++;
+ }
+
+ if (auto store_static = std::get_if<StoreStaticOpcode>(&cur_opcode.opcode)) {
+ // Calculate address, write value to memory.
+ u64 dst_address = GetCheatProcessAddress(metadata, store_static->mem_type,
+ store_static->rel_address +
+ registers[store_static->offset_register]);
+ u64 dst_value = GetVmInt(store_static->value, store_static->bit_width);
+ switch (store_static->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryWrite(dst_address, &dst_value, store_static->bit_width);
+ break;
+ }
+ } else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
+ // Read value from memory.
+ u64 src_address =
+ GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address);
+ u64 src_value = 0;
+ switch (store_static->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryRead(src_address, &src_value, begin_cond->bit_width);
+ break;
+ }
+ // Check against condition.
+ u64 cond_value = GetVmInt(begin_cond->value, begin_cond->bit_width);
+ bool cond_met = false;
+ switch (begin_cond->cond_type) {
+ case ConditionalComparisonType::GT:
+ cond_met = src_value > cond_value;
+ break;
+ case ConditionalComparisonType::GE:
+ cond_met = src_value >= cond_value;
+ break;
+ case ConditionalComparisonType::LT:
+ cond_met = src_value < cond_value;
+ break;
+ case ConditionalComparisonType::LE:
+ cond_met = src_value <= cond_value;
+ break;
+ case ConditionalComparisonType::EQ:
+ cond_met = src_value == cond_value;
+ break;
+ case ConditionalComparisonType::NE:
+ cond_met = src_value != cond_value;
+ break;
+ }
+ // Skip conditional block if condition not met.
+ if (!cond_met) {
+ SkipConditionalBlock();
+ }
+ } else if (auto end_cond = std::get_if<EndConditionalOpcode>(&cur_opcode.opcode)) {
+ // Decrement the condition depth.
+ // We will assume, graciously, that mismatched conditional block ends are a nop.
+ if (condition_depth > 0) {
+ condition_depth--;
+ }
+ } else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&cur_opcode.opcode)) {
+ if (ctrl_loop->start_loop) {
+ // Start a loop.
+ registers[ctrl_loop->reg_index] = ctrl_loop->num_iters;
+ loop_tops[ctrl_loop->reg_index] = instruction_ptr;
+ } else {
+ // End a loop.
+ registers[ctrl_loop->reg_index]--;
+ if (registers[ctrl_loop->reg_index] != 0) {
+ instruction_ptr = loop_tops[ctrl_loop->reg_index];
+ }
+ }
+ } else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&cur_opcode.opcode)) {
+ // Set a register to a static value.
+ registers[ldr_static->reg_index] = ldr_static->value;
+ } else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&cur_opcode.opcode)) {
+ // Choose source address.
+ u64 src_address;
+ if (ldr_memory->load_from_reg) {
+ src_address = registers[ldr_memory->reg_index] + ldr_memory->rel_address;
+ } else {
+ src_address =
+ GetCheatProcessAddress(metadata, ldr_memory->mem_type, ldr_memory->rel_address);
+ }
+ // Read into register. Gateway only reads on valid bitwidth.
+ switch (ldr_memory->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryRead(src_address, &registers[ldr_memory->reg_index],
+ ldr_memory->bit_width);
+ break;
+ }
+ } else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
+ // Calculate address.
+ u64 dst_address = registers[str_static->reg_index];
+ u64 dst_value = str_static->value;
+ if (str_static->add_offset_reg) {
+ dst_address += registers[str_static->offset_reg_index];
+ }
+ // Write value to memory. Gateway only writes on valid bitwidth.
+ switch (str_static->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryWrite(dst_address, &dst_value, str_static->bit_width);
+ break;
+ }
+ // Increment register if relevant.
+ if (str_static->increment_reg) {
+ registers[str_static->reg_index] += str_static->bit_width;
+ }
+ } else if (auto perform_math_static =
+ std::get_if<PerformArithmeticStaticOpcode>(&cur_opcode.opcode)) {
+ // Do requested math.
+ switch (perform_math_static->math_type) {
+ case RegisterArithmeticType::Addition:
+ registers[perform_math_static->reg_index] +=
+ static_cast<u64>(perform_math_static->value);
+ break;
+ case RegisterArithmeticType::Subtraction:
+ registers[perform_math_static->reg_index] -=
+ static_cast<u64>(perform_math_static->value);
+ break;
+ case RegisterArithmeticType::Multiplication:
+ registers[perform_math_static->reg_index] *=
+ static_cast<u64>(perform_math_static->value);
+ break;
+ case RegisterArithmeticType::LeftShift:
+ registers[perform_math_static->reg_index] <<=
+ static_cast<u64>(perform_math_static->value);
+ break;
+ case RegisterArithmeticType::RightShift:
+ registers[perform_math_static->reg_index] >>=
+ static_cast<u64>(perform_math_static->value);
+ break;
+ default:
+ // Do not handle extensions here.
+ break;
+ }
+ // Apply bit width.
+ switch (perform_math_static->bit_width) {
+ case 1:
+ registers[perform_math_static->reg_index] =
+ static_cast<u8>(registers[perform_math_static->reg_index]);
+ break;
+ case 2:
+ registers[perform_math_static->reg_index] =
+ static_cast<u16>(registers[perform_math_static->reg_index]);
+ break;
+ case 4:
+ registers[perform_math_static->reg_index] =
+ static_cast<u32>(registers[perform_math_static->reg_index]);
+ break;
+ case 8:
+ registers[perform_math_static->reg_index] =
+ static_cast<u64>(registers[perform_math_static->reg_index]);
+ break;
+ }
+ } else if (auto begin_keypress_cond =
+ std::get_if<BeginKeypressConditionalOpcode>(&cur_opcode.opcode)) {
+ // Check for keypress.
+ if ((begin_keypress_cond->key_mask & kDown) != begin_keypress_cond->key_mask) {
+ // Keys not pressed. Skip conditional block.
+ SkipConditionalBlock();
+ }
+ } else if (auto perform_math_reg =
+ std::get_if<PerformArithmeticRegisterOpcode>(&cur_opcode.opcode)) {
+ const u64 operand_1_value = registers[perform_math_reg->src_reg_1_index];
+ const u64 operand_2_value =
+ perform_math_reg->has_immediate
+ ? GetVmInt(perform_math_reg->value, perform_math_reg->bit_width)
+ : registers[perform_math_reg->src_reg_2_index];
+
+ u64 res_val = 0;
+ // Do requested math.
+ switch (perform_math_reg->math_type) {
+ case RegisterArithmeticType::Addition:
+ res_val = operand_1_value + operand_2_value;
+ break;
+ case RegisterArithmeticType::Subtraction:
+ res_val = operand_1_value - operand_2_value;
+ break;
+ case RegisterArithmeticType::Multiplication:
+ res_val = operand_1_value * operand_2_value;
+ break;
+ case RegisterArithmeticType::LeftShift:
+ res_val = operand_1_value << operand_2_value;
+ break;
+ case RegisterArithmeticType::RightShift:
+ res_val = operand_1_value >> operand_2_value;
+ break;
+ case RegisterArithmeticType::LogicalAnd:
+ res_val = operand_1_value & operand_2_value;
+ break;
+ case RegisterArithmeticType::LogicalOr:
+ res_val = operand_1_value | operand_2_value;
+ break;
+ case RegisterArithmeticType::LogicalNot:
+ res_val = ~operand_1_value;
+ break;
+ case RegisterArithmeticType::LogicalXor:
+ res_val = operand_1_value ^ operand_2_value;
+ break;
+ case RegisterArithmeticType::None:
+ res_val = operand_1_value;
+ break;
+ }
+
+ // Apply bit width.
+ switch (perform_math_reg->bit_width) {
+ case 1:
+ res_val = static_cast<u8>(res_val);
+ break;
+ case 2:
+ res_val = static_cast<u16>(res_val);
+ break;
+ case 4:
+ res_val = static_cast<u32>(res_val);
+ break;
+ case 8:
+ res_val = static_cast<u64>(res_val);
+ break;
+ }
+
+ // Save to register.
+ registers[perform_math_reg->dst_reg_index] = res_val;
+ } else if (auto str_register =
+ std::get_if<StoreRegisterToAddressOpcode>(&cur_opcode.opcode)) {
+ // Calculate address.
+ u64 dst_value = registers[str_register->str_reg_index];
+ u64 dst_address = registers[str_register->addr_reg_index];
+ switch (str_register->ofs_type) {
+ case StoreRegisterOffsetType::None:
+ // Nothing more to do
+ break;
+ case StoreRegisterOffsetType::Reg:
+ dst_address += registers[str_register->ofs_reg_index];
+ break;
+ case StoreRegisterOffsetType::Imm:
+ dst_address += str_register->rel_address;
+ break;
+ case StoreRegisterOffsetType::MemReg:
+ dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
+ registers[str_register->addr_reg_index]);
+ break;
+ case StoreRegisterOffsetType::MemImm:
+ dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
+ str_register->rel_address);
+ break;
+ case StoreRegisterOffsetType::MemImmReg:
+ dst_address = GetCheatProcessAddress(metadata, str_register->mem_type,
+ registers[str_register->addr_reg_index] +
+ str_register->rel_address);
+ break;
+ }
+
+ // Write value to memory. Write only on valid bitwidth.
+ switch (str_register->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryWrite(dst_address, &dst_value, str_register->bit_width);
+ break;
+ }
+
+ // Increment register if relevant.
+ if (str_register->increment_reg) {
+ registers[str_register->addr_reg_index] += str_register->bit_width;
+ }
+ } else if (auto begin_reg_cond =
+ std::get_if<BeginRegisterConditionalOpcode>(&cur_opcode.opcode)) {
+ // Get value from register.
+ u64 src_value = 0;
+ switch (begin_reg_cond->bit_width) {
+ case 1:
+ src_value = static_cast<u8>(registers[begin_reg_cond->val_reg_index] & 0xFFul);
+ break;
+ case 2:
+ src_value = static_cast<u16>(registers[begin_reg_cond->val_reg_index] & 0xFFFFul);
+ break;
+ case 4:
+ src_value =
+ static_cast<u32>(registers[begin_reg_cond->val_reg_index] & 0xFFFFFFFFul);
+ break;
+ case 8:
+ src_value = static_cast<u64>(registers[begin_reg_cond->val_reg_index] &
+ 0xFFFFFFFFFFFFFFFFul);
+ break;
+ }
+
+ // Read value from memory.
+ u64 cond_value = 0;
+ if (begin_reg_cond->comp_type == CompareRegisterValueType::StaticValue) {
+ cond_value = GetVmInt(begin_reg_cond->value, begin_reg_cond->bit_width);
+ } else if (begin_reg_cond->comp_type == CompareRegisterValueType::OtherRegister) {
+ switch (begin_reg_cond->bit_width) {
+ case 1:
+ cond_value =
+ static_cast<u8>(registers[begin_reg_cond->other_reg_index] & 0xFFul);
+ break;
+ case 2:
+ cond_value =
+ static_cast<u16>(registers[begin_reg_cond->other_reg_index] & 0xFFFFul);
+ break;
+ case 4:
+ cond_value =
+ static_cast<u32>(registers[begin_reg_cond->other_reg_index] & 0xFFFFFFFFul);
+ break;
+ case 8:
+ cond_value = static_cast<u64>(registers[begin_reg_cond->other_reg_index] &
+ 0xFFFFFFFFFFFFFFFFul);
+ break;
+ }
+ } else {
+ u64 cond_address = 0;
+ switch (begin_reg_cond->comp_type) {
+ case CompareRegisterValueType::MemoryRelAddr:
+ cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
+ begin_reg_cond->rel_address);
+ break;
+ case CompareRegisterValueType::MemoryOfsReg:
+ cond_address = GetCheatProcessAddress(metadata, begin_reg_cond->mem_type,
+ registers[begin_reg_cond->ofs_reg_index]);
+ break;
+ case CompareRegisterValueType::RegisterRelAddr:
+ cond_address =
+ registers[begin_reg_cond->addr_reg_index] + begin_reg_cond->rel_address;
+ break;
+ case CompareRegisterValueType::RegisterOfsReg:
+ cond_address = registers[begin_reg_cond->addr_reg_index] +
+ registers[begin_reg_cond->ofs_reg_index];
+ break;
+ default:
+ break;
+ }
+ switch (begin_reg_cond->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryRead(cond_address, &cond_value, begin_reg_cond->bit_width);
+ break;
+ }
+ }
+
+ // Check against condition.
+ bool cond_met = false;
+ switch (begin_reg_cond->cond_type) {
+ case ConditionalComparisonType::GT:
+ cond_met = src_value > cond_value;
+ break;
+ case ConditionalComparisonType::GE:
+ cond_met = src_value >= cond_value;
+ break;
+ case ConditionalComparisonType::LT:
+ cond_met = src_value < cond_value;
+ break;
+ case ConditionalComparisonType::LE:
+ cond_met = src_value <= cond_value;
+ break;
+ case ConditionalComparisonType::EQ:
+ cond_met = src_value == cond_value;
+ break;
+ case ConditionalComparisonType::NE:
+ cond_met = src_value != cond_value;
+ break;
+ }
+
+ // Skip conditional block if condition not met.
+ if (!cond_met) {
+ SkipConditionalBlock();
+ }
+ } else if (auto save_restore_reg =
+ std::get_if<SaveRestoreRegisterOpcode>(&cur_opcode.opcode)) {
+ // Save or restore a register.
+ switch (save_restore_reg->op_type) {
+ case SaveRestoreRegisterOpType::ClearRegs:
+ registers[save_restore_reg->dst_index] = 0ul;
+ break;
+ case SaveRestoreRegisterOpType::ClearSaved:
+ saved_values[save_restore_reg->dst_index] = 0ul;
+ break;
+ case SaveRestoreRegisterOpType::Save:
+ saved_values[save_restore_reg->dst_index] = registers[save_restore_reg->src_index];
+ break;
+ case SaveRestoreRegisterOpType::Restore:
+ default:
+ registers[save_restore_reg->dst_index] = saved_values[save_restore_reg->src_index];
+ break;
+ }
+ } else if (auto save_restore_regmask =
+ std::get_if<SaveRestoreRegisterMaskOpcode>(&cur_opcode.opcode)) {
+ // Save or restore register mask.
+ u64* src;
+ u64* dst;
+ switch (save_restore_regmask->op_type) {
+ case SaveRestoreRegisterOpType::ClearSaved:
+ case SaveRestoreRegisterOpType::Save:
+ src = registers.data();
+ dst = saved_values.data();
+ break;
+ case SaveRestoreRegisterOpType::ClearRegs:
+ case SaveRestoreRegisterOpType::Restore:
+ default:
+ src = registers.data();
+ dst = saved_values.data();
+ break;
+ }
+ for (std::size_t i = 0; i < NumRegisters; i++) {
+ if (save_restore_regmask->should_operate[i]) {
+ switch (save_restore_regmask->op_type) {
+ case SaveRestoreRegisterOpType::ClearSaved:
+ case SaveRestoreRegisterOpType::ClearRegs:
+ dst[i] = 0ul;
+ break;
+ case SaveRestoreRegisterOpType::Save:
+ case SaveRestoreRegisterOpType::Restore:
+ default:
+ dst[i] = src[i];
+ break;
+ }
+ }
+ }
+ } else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
+ // Read value from memory.
+ u64 log_value = 0;
+ if (debug_log->val_type == DebugLogValueType::RegisterValue) {
+ switch (debug_log->bit_width) {
+ case 1:
+ log_value = static_cast<u8>(registers[debug_log->val_reg_index] & 0xFFul);
+ break;
+ case 2:
+ log_value = static_cast<u16>(registers[debug_log->val_reg_index] & 0xFFFFul);
+ break;
+ case 4:
+ log_value =
+ static_cast<u32>(registers[debug_log->val_reg_index] & 0xFFFFFFFFul);
+ break;
+ case 8:
+ log_value = static_cast<u64>(registers[debug_log->val_reg_index] &
+ 0xFFFFFFFFFFFFFFFFul);
+ break;
+ }
+ } else {
+ u64 val_address = 0;
+ switch (debug_log->val_type) {
+ case DebugLogValueType::MemoryRelAddr:
+ val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
+ debug_log->rel_address);
+ break;
+ case DebugLogValueType::MemoryOfsReg:
+ val_address = GetCheatProcessAddress(metadata, debug_log->mem_type,
+ registers[debug_log->ofs_reg_index]);
+ break;
+ case DebugLogValueType::RegisterRelAddr:
+ val_address = registers[debug_log->addr_reg_index] + debug_log->rel_address;
+ break;
+ case DebugLogValueType::RegisterOfsReg:
+ val_address =
+ registers[debug_log->addr_reg_index] + registers[debug_log->ofs_reg_index];
+ break;
+ default:
+ break;
+ }
+ switch (debug_log->bit_width) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ callbacks->MemoryRead(val_address, &log_value, debug_log->bit_width);
+ break;
+ }
+ }
+
+ // Log value.
+ DebugLog(debug_log->log_id, log_value);
+ }
+ }
+}
+
+} // namespace Memory
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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Adapted by DarkLordZach for use/interaction with yuzu
+ *
+ * Modifications Copyright 2019 yuzu emulator team
+ * Licensed under GPLv2 or any later version
+ * Refer to the license.txt file included.
+ */
+
+#pragma once
+
+#include <variant>
+#include <vector>
+#include <fmt/printf.h>
+#include "common/common_types.h"
+#include "core/memory/dmnt_cheat_types.h"
+
+namespace Memory {
+
+enum class CheatVmOpcodeType : u32 {
+ StoreStatic = 0,
+ BeginConditionalBlock = 1,
+ EndConditionalBlock = 2,
+ ControlLoop = 3,
+ LoadRegisterStatic = 4,
+ LoadRegisterMemory = 5,
+ StoreStaticToAddress = 6,
+ PerformArithmeticStatic = 7,
+ BeginKeypressConditionalBlock = 8,
+
+ // These are not implemented by Gateway's VM.
+ PerformArithmeticRegister = 9,
+ StoreRegisterToAddress = 10,
+ Reserved11 = 11,
+
+ // This is a meta entry, and not a real opcode.
+ // This is to facilitate multi-nybble instruction decoding.
+ ExtendedWidth = 12,
+
+ // Extended width opcodes.
+ BeginRegisterConditionalBlock = 0xC0,
+ SaveRestoreRegister = 0xC1,
+ SaveRestoreRegisterMask = 0xC2,
+
+ // This is a meta entry, and not a real opcode.
+ // This is to facilitate multi-nybble instruction decoding.
+ DoubleExtendedWidth = 0xF0,
+
+ // Double-extended width opcodes.
+ DebugLog = 0xFFF,
+};
+
+enum class MemoryAccessType : u32 {
+ MainNso = 0,
+ Heap = 1,
+};
+
+enum class ConditionalComparisonType : u32 {
+ GT = 1,
+ GE = 2,
+ LT = 3,
+ LE = 4,
+ EQ = 5,
+ NE = 6,
+};
+
+enum class RegisterArithmeticType : u32 {
+ Addition = 0,
+ Subtraction = 1,
+ Multiplication = 2,
+ LeftShift = 3,
+ RightShift = 4,
+
+ // These are not supported by Gateway's VM.
+ LogicalAnd = 5,
+ LogicalOr = 6,
+ LogicalNot = 7,
+ LogicalXor = 8,
+
+ None = 9,
+};
+
+enum class StoreRegisterOffsetType : u32 {
+ None = 0,
+ Reg = 1,
+ Imm = 2,
+ MemReg = 3,
+ MemImm = 4,
+ MemImmReg = 5,
+};
+
+enum class CompareRegisterValueType : u32 {
+ MemoryRelAddr = 0,
+ MemoryOfsReg = 1,
+ RegisterRelAddr = 2,
+ RegisterOfsReg = 3,
+ StaticValue = 4,
+ OtherRegister = 5,
+};
+
+enum class SaveRestoreRegisterOpType : u32 {
+ Restore = 0,
+ Save = 1,
+ ClearSaved = 2,
+ ClearRegs = 3,
+};
+
+enum class DebugLogValueType : u32 {
+ MemoryRelAddr = 0,
+ MemoryOfsReg = 1,
+ RegisterRelAddr = 2,
+ RegisterOfsReg = 3,
+ RegisterValue = 4,
+};
+
+union VmInt {
+ u8 bit8;
+ u16 bit16;
+ u32 bit32;
+ u64 bit64;
+};
+
+struct StoreStaticOpcode {
+ u32 bit_width{};
+ MemoryAccessType mem_type{};
+ u32 offset_register{};
+ u64 rel_address{};
+ VmInt value{};
+};
+
+struct BeginConditionalOpcode {
+ u32 bit_width{};
+ MemoryAccessType mem_type{};
+ ConditionalComparisonType cond_type{};
+ u64 rel_address{};
+ VmInt value{};
+};
+
+struct EndConditionalOpcode {};
+
+struct ControlLoopOpcode {
+ bool start_loop{};
+ u32 reg_index{};
+ u32 num_iters{};
+};
+
+struct LoadRegisterStaticOpcode {
+ u32 reg_index{};
+ u64 value{};
+};
+
+struct LoadRegisterMemoryOpcode {
+ u32 bit_width{};
+ MemoryAccessType mem_type{};
+ u32 reg_index{};
+ bool load_from_reg{};
+ u64 rel_address{};
+};
+
+struct StoreStaticToAddressOpcode {
+ u32 bit_width{};
+ u32 reg_index{};
+ bool increment_reg{};
+ bool add_offset_reg{};
+ u32 offset_reg_index{};
+ u64 value{};
+};
+
+struct PerformArithmeticStaticOpcode {
+ u32 bit_width{};
+ u32 reg_index{};
+ RegisterArithmeticType math_type{};
+ u32 value{};
+};
+
+struct BeginKeypressConditionalOpcode {
+ u32 key_mask{};
+};
+
+struct PerformArithmeticRegisterOpcode {
+ u32 bit_width{};
+ RegisterArithmeticType math_type{};
+ u32 dst_reg_index{};
+ u32 src_reg_1_index{};
+ u32 src_reg_2_index{};
+ bool has_immediate{};
+ VmInt value{};
+};
+
+struct StoreRegisterToAddressOpcode {
+ u32 bit_width{};
+ u32 str_reg_index{};
+ u32 addr_reg_index{};
+ bool increment_reg{};
+ StoreRegisterOffsetType ofs_type{};
+ MemoryAccessType mem_type{};
+ u32 ofs_reg_index{};
+ u64 rel_address{};
+};
+
+struct BeginRegisterConditionalOpcode {
+ u32 bit_width{};
+ ConditionalComparisonType cond_type{};
+ u32 val_reg_index{};
+ CompareRegisterValueType comp_type{};
+ MemoryAccessType mem_type{};
+ u32 addr_reg_index{};
+ u32 other_reg_index{};
+ u32 ofs_reg_index{};
+ u64 rel_address{};
+ VmInt value{};
+};
+
+struct SaveRestoreRegisterOpcode {
+ u32 dst_index{};
+ u32 src_index{};
+ SaveRestoreRegisterOpType op_type{};
+};
+
+struct SaveRestoreRegisterMaskOpcode {
+ SaveRestoreRegisterOpType op_type{};
+ std::array<bool, 0x10> should_operate{};
+};
+
+struct DebugLogOpcode {
+ u32 bit_width{};
+ u32 log_id{};
+ DebugLogValueType val_type{};
+ MemoryAccessType mem_type{};
+ u32 addr_reg_index{};
+ u32 val_reg_index{};
+ u32 ofs_reg_index{};
+ u64 rel_address{};
+};
+
+struct UnrecognizedInstruction {
+ CheatVmOpcodeType opcode{};
+};
+
+struct CheatVmOpcode {
+ bool begin_conditional_block{};
+ std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode,
+ LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode,
+ PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode,
+ PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode,
+ BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode,
+ SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction>
+ opcode{};
+};
+
+class DmntCheatVm {
+public:
+ /// Helper Type for DmntCheatVm <=> yuzu Interface
+ class Callbacks {
+ public:
+ virtual ~Callbacks();
+
+ virtual void MemoryRead(VAddr address, void* data, u64 size) = 0;
+ virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0;
+
+ virtual u64 HidKeysDown() = 0;
+
+ virtual void DebugLog(u8 id, u64 value) = 0;
+ virtual void CommandLog(std::string_view data) = 0;
+ };
+
+ static constexpr std::size_t MaximumProgramOpcodeCount = 0x400;
+ static constexpr std::size_t NumRegisters = 0x10;
+
+ explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks);
+ ~DmntCheatVm();
+
+ std::size_t GetProgramSize() const {
+ return this->num_opcodes;
+ }
+
+ bool LoadProgram(const std::vector<CheatEntry>& cheats);
+ void Execute(const CheatProcessMetadata& metadata);
+
+private:
+ std::unique_ptr<Callbacks> callbacks;
+
+ std::size_t num_opcodes = 0;
+ std::size_t instruction_ptr = 0;
+ std::size_t condition_depth = 0;
+ bool decode_success = false;
+ std::array<u32, MaximumProgramOpcodeCount> program{};
+ std::array<u64, NumRegisters> registers{};
+ std::array<u64, NumRegisters> saved_values{};
+ std::array<std::size_t, NumRegisters> loop_tops{};
+
+ bool DecodeNextOpcode(CheatVmOpcode& out);
+ void SkipConditionalBlock();
+ void ResetState();
+
+ // For implementing the DebugLog opcode.
+ void DebugLog(u32 log_id, u64 value);
+
+ void LogOpcode(const CheatVmOpcode& opcode);
+
+ static u64 GetVmInt(VmInt value, u32 bit_width);
+ static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
+ MemoryAccessType mem_type, u64 rel_address);
+};
+
+}; // namespace Memory