summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorZach Hilman <zachhilman@gmail.com>2019-05-31 01:35:03 +0200
committerZach Hilman <zachhilman@gmail.com>2019-09-22 03:44:22 +0200
commit7d41c1f52390abb47e67d3fc43310e9d87fbd862 (patch)
tree79a4891115f3efd039b98cc47d63380d811021b1 /src
parentmemory: Port Atmosphere's DmntCheatVm (diff)
downloadyuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.gz
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.bz2
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.lz
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.xz
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.tar.zst
yuzu-7d41c1f52390abb47e67d3fc43310e9d87fbd862.zip
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt7
-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/memory/cheat_engine.cpp234
-rw-r--r--src/core/memory/cheat_engine.h86
5 files changed, 325 insertions, 728 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 877a9e353..a6b56c9c6 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
@@ -477,6 +475,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/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/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp
new file mode 100644
index 000000000..ea5c76fc0
--- /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 (std::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 (std::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::HexArrayToString(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