Browse Source
Merge pull request #2535 from DarkLordZach/cheat-v2
Merge pull request #2535 from DarkLordZach/cheat-v2
cheat_engine: Use Atmosphere's Cheat VM and fix cheat crashnce_cpp
committed by
GitHub
15 changed files with 1962 additions and 760 deletions
-
1src/common/logging/backend.cpp
-
1src/common/logging/log.h
-
7src/core/CMakeLists.txt
-
25src/core/core.cpp
-
10src/core/core.h
-
492src/core/file_sys/cheat_engine.cpp
-
234src/core/file_sys/cheat_engine.h
-
32src/core/file_sys/patch_manager.cpp
-
6src/core/file_sys/patch_manager.h
-
3src/core/loader/nso.cpp
-
234src/core/memory/cheat_engine.cpp
-
86src/core/memory/cheat_engine.h
-
58src/core/memory/dmnt_cheat_types.h
-
1212src/core/memory/dmnt_cheat_vm.cpp
-
321src/core/memory/dmnt_cheat_vm.h
@ -1,492 +0,0 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <locale>
|
|||
#include "common/hex_util.h"
|
|||
#include "common/microprofile.h"
|
|||
#include "common/swap.h"
|
|||
#include "core/core.h"
|
|||
#include "core/core_timing.h"
|
|||
#include "core/core_timing_util.h"
|
|||
#include "core/file_sys/cheat_engine.h"
|
|||
#include "core/hle/kernel/process.h"
|
|||
#include "core/hle/service/hid/controllers/npad.h"
|
|||
#include "core/hle/service/hid/hid.h"
|
|||
#include "core/hle/service/sm/sm.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60); |
|||
constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF; |
|||
|
|||
u64 Cheat::Address() const { |
|||
u64 out; |
|||
std::memcpy(&out, raw.data(), sizeof(u64)); |
|||
return Common::swap64(out) & 0xFFFFFFFFFF; |
|||
} |
|||
|
|||
u64 Cheat::ValueWidth(u64 offset) const { |
|||
return Value(offset, width); |
|||
} |
|||
|
|||
u64 Cheat::Value(u64 offset, u64 width) const { |
|||
u64 out; |
|||
std::memcpy(&out, raw.data() + offset, sizeof(u64)); |
|||
out = Common::swap64(out); |
|||
if (width == 8) |
|||
return out; |
|||
return out & ((1ull << (width * CHAR_BIT)) - 1); |
|||
} |
|||
|
|||
u32 Cheat::KeypadValue() const { |
|||
u32 out; |
|||
std::memcpy(&out, raw.data(), sizeof(u32)); |
|||
return Common::swap32(out) & 0x0FFFFFFF; |
|||
} |
|||
|
|||
void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, |
|||
VAddr heap_end, MemoryWriter writer, MemoryReader reader) { |
|||
this->main_region_begin = main_begin; |
|||
this->main_region_end = main_end; |
|||
this->heap_region_begin = heap_begin; |
|||
this->heap_region_end = heap_end; |
|||
this->writer = writer; |
|||
this->reader = reader; |
|||
} |
|||
|
|||
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70)); |
|||
|
|||
void CheatList::Execute() { |
|||
MICROPROFILE_SCOPE(Cheat_Engine); |
|||
|
|||
std::fill(scratch.begin(), scratch.end(), 0); |
|||
in_standard = false; |
|||
for (std::size_t i = 0; i < master_list.size(); ++i) { |
|||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first); |
|||
current_block = i; |
|||
ExecuteBlock(master_list[i].second); |
|||
} |
|||
|
|||
in_standard = true; |
|||
for (std::size_t i = 0; i < standard_list.size(); ++i) { |
|||
LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first); |
|||
current_block = i; |
|||
ExecuteBlock(standard_list[i].second); |
|||
} |
|||
} |
|||
|
|||
CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard) |
|||
: master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {} |
|||
|
|||
bool CheatList::EvaluateConditional(const Cheat& cheat) const { |
|||
using ComparisonFunction = bool (*)(u64, u64); |
|||
constexpr std::array<ComparisonFunction, 6> comparison_functions{ |
|||
[](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; }, |
|||
[](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; }, |
|||
[](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; }, |
|||
}; |
|||
|
|||
if (cheat.type == CodeType::ConditionalInput) { |
|||
const auto applet_resource = |
|||
system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource(); |
|||
if (applet_resource == nullptr) { |
|||
LOG_WARNING( |
|||
Common_Filesystem, |
|||
"Attempted to evaluate input conditional, but applet resource is not initialized!"); |
|||
return false; |
|||
} |
|||
|
|||
const auto press_state = |
|||
applet_resource |
|||
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad) |
|||
.GetAndResetPressState(); |
|||
return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0; |
|||
} |
|||
|
|||
ASSERT(cheat.type == CodeType::Conditional); |
|||
|
|||
const auto offset = |
|||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; |
|||
ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6); |
|||
auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())]; |
|||
const auto addr = cheat.Address() + offset; |
|||
|
|||
return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8)); |
|||
} |
|||
|
|||
void CheatList::ProcessBlockPairs(const Block& block) { |
|||
block_pairs.clear(); |
|||
|
|||
u64 scope = 0; |
|||
std::map<u64, u64> pairs; |
|||
|
|||
for (std::size_t i = 0; i < block.size(); ++i) { |
|||
const auto& cheat = block[i]; |
|||
|
|||
switch (cheat.type) { |
|||
case CodeType::Conditional: |
|||
case CodeType::ConditionalInput: |
|||
pairs.insert_or_assign(scope, i); |
|||
++scope; |
|||
break; |
|||
case CodeType::EndConditional: { |
|||
--scope; |
|||
const auto idx = pairs.at(scope); |
|||
block_pairs.insert_or_assign(idx, i); |
|||
break; |
|||
} |
|||
case CodeType::Loop: { |
|||
if (cheat.end_of_loop) { |
|||
--scope; |
|||
const auto idx = pairs.at(scope); |
|||
block_pairs.insert_or_assign(idx, i); |
|||
} else { |
|||
pairs.insert_or_assign(scope, i); |
|||
++scope; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CheatList::WriteImmediate(const Cheat& cheat) { |
|||
const auto offset = |
|||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; |
|||
const auto& register_3 = scratch.at(cheat.register_3); |
|||
|
|||
const auto addr = cheat.Address() + offset + register_3; |
|||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr, |
|||
cheat.Value(8, cheat.width)); |
|||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8)); |
|||
} |
|||
|
|||
void CheatList::BeginConditional(const Cheat& cheat) { |
|||
if (EvaluateConditional(cheat)) { |
|||
return; |
|||
} |
|||
|
|||
const auto iter = block_pairs.find(current_index); |
|||
ASSERT(iter != block_pairs.end()); |
|||
current_index = iter->second - 1; |
|||
} |
|||
|
|||
void CheatList::EndConditional(const Cheat& cheat) { |
|||
LOG_DEBUG(Common_Filesystem, "Ending conditional block."); |
|||
} |
|||
|
|||
void CheatList::Loop(const Cheat& cheat) { |
|||
if (cheat.end_of_loop.Value()) |
|||
ASSERT(!cheat.end_of_loop.Value()); |
|||
|
|||
auto& register_3 = scratch.at(cheat.register_3); |
|||
const auto iter = block_pairs.find(current_index); |
|||
ASSERT(iter != block_pairs.end()); |
|||
ASSERT(iter->first < iter->second); |
|||
|
|||
const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32))); |
|||
for (s32 i = initial_value; i >= 0; --i) { |
|||
register_3 = static_cast<u64>(i); |
|||
for (std::size_t c = iter->first + 1; c < iter->second; ++c) { |
|||
current_index = c; |
|||
ExecuteSingleCheat( |
|||
(in_standard ? standard_list : master_list)[current_block].second[c]); |
|||
} |
|||
} |
|||
|
|||
current_index = iter->second; |
|||
} |
|||
|
|||
void CheatList::LoadImmediate(const Cheat& cheat) { |
|||
auto& register_3 = scratch.at(cheat.register_3); |
|||
|
|||
LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3, |
|||
cheat.Value(4, 8)); |
|||
register_3 = cheat.Value(4, 8); |
|||
} |
|||
|
|||
void CheatList::LoadIndexed(const Cheat& cheat) { |
|||
const auto offset = |
|||
cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin; |
|||
auto& register_3 = scratch.at(cheat.register_3); |
|||
|
|||
const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address(); |
|||
LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}", |
|||
cheat.register_3, addr); |
|||
register_3 = reader(cheat.width, SanitizeAddress(addr)); |
|||
} |
|||
|
|||
void CheatList::StoreIndexed(const Cheat& cheat) { |
|||
const auto& register_3 = scratch.at(cheat.register_3); |
|||
|
|||
const auto addr = |
|||
register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0); |
|||
LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", |
|||
cheat.Value(4, cheat.width), addr); |
|||
writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4)); |
|||
} |
|||
|
|||
void CheatList::RegisterArithmetic(const Cheat& cheat) { |
|||
using ArithmeticFunction = u64 (*)(u64, u64); |
|||
constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{ |
|||
[](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; }, |
|||
[](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; }, |
|||
[](u64 a, u64 b) { return a >> b; }, |
|||
}; |
|||
|
|||
using ArithmeticOverflowCheck = bool (*)(u64, u64); |
|||
constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{ |
|||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
|
|||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
|
|||
[](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
|
|||
[](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
|
|||
[](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
|
|||
}; |
|||
|
|||
static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks), |
|||
"Missing or have extra arithmetic overflow checks compared to functions!"); |
|||
|
|||
auto& register_3 = scratch.at(cheat.register_3); |
|||
|
|||
ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5); |
|||
auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())]; |
|||
auto* overflow_function = |
|||
arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())]; |
|||
LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}", |
|||
cheat.register_3, cheat.ValueWidth(4)); |
|||
|
|||
if (overflow_function(register_3, cheat.ValueWidth(4))) { |
|||
LOG_WARNING(Common_Filesystem, |
|||
"overflow will occur when performing arithmetic operation={:02X} with operands " |
|||
"a={:016X}, b={:016X}!", |
|||
static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4)); |
|||
} |
|||
|
|||
register_3 = function(register_3, cheat.ValueWidth(4)); |
|||
} |
|||
|
|||
void CheatList::BeginConditionalInput(const Cheat& cheat) { |
|||
if (EvaluateConditional(cheat)) |
|||
return; |
|||
|
|||
const auto iter = block_pairs.find(current_index); |
|||
ASSERT(iter != block_pairs.end()); |
|||
current_index = iter->second - 1; |
|||
} |
|||
|
|||
VAddr CheatList::SanitizeAddress(VAddr in) const { |
|||
if ((in < main_region_begin || in >= main_region_end) && |
|||
(in < heap_region_begin || in >= heap_region_end)) { |
|||
LOG_ERROR(Common_Filesystem, |
|||
"Cheat attempting to access memory at invalid address={:016X}, if this persists, " |
|||
"the cheat may be incorrect. However, this may be normal early in execution if " |
|||
"the game has not properly set up yet.", |
|||
in); |
|||
return 0; ///< Invalid addresses will hard crash
|
|||
} |
|||
|
|||
return in; |
|||
} |
|||
|
|||
void CheatList::ExecuteSingleCheat(const Cheat& cheat) { |
|||
using CheatOperationFunction = void (CheatList::*)(const Cheat&); |
|||
constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{ |
|||
&CheatList::WriteImmediate, &CheatList::BeginConditional, |
|||
&CheatList::EndConditional, &CheatList::Loop, |
|||
&CheatList::LoadImmediate, &CheatList::LoadIndexed, |
|||
&CheatList::StoreIndexed, &CheatList::RegisterArithmetic, |
|||
&CheatList::BeginConditionalInput, |
|||
}; |
|||
|
|||
const auto index = static_cast<u8>(cheat.type.Value()); |
|||
ASSERT(index < sizeof(cheat_operation_functions)); |
|||
const auto op = cheat_operation_functions[index]; |
|||
(this->*op)(cheat); |
|||
} |
|||
|
|||
void CheatList::ExecuteBlock(const Block& block) { |
|||
encountered_loops.clear(); |
|||
|
|||
ProcessBlockPairs(block); |
|||
for (std::size_t i = 0; i < block.size(); ++i) { |
|||
current_index = i; |
|||
ExecuteSingleCheat(block[i]); |
|||
i = current_index; |
|||
} |
|||
} |
|||
|
|||
CheatParser::~CheatParser() = default; |
|||
|
|||
CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master, |
|||
CheatList::ProgramSegment standard) const { |
|||
return {system, std::move(master), std::move(standard)}; |
|||
} |
|||
|
|||
TextCheatParser::~TextCheatParser() = default; |
|||
|
|||
CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const { |
|||
std::stringstream ss; |
|||
ss.write(reinterpret_cast<const char*>(data.data()), data.size()); |
|||
|
|||
std::vector<std::string> lines; |
|||
std::string stream_line; |
|||
while (std::getline(ss, stream_line)) { |
|||
// Remove a trailing \r
|
|||
if (!stream_line.empty() && stream_line.back() == '\r') |
|||
stream_line.pop_back(); |
|||
lines.push_back(std::move(stream_line)); |
|||
} |
|||
|
|||
CheatList::ProgramSegment master_list; |
|||
CheatList::ProgramSegment standard_list; |
|||
|
|||
for (std::size_t i = 0; i < lines.size(); ++i) { |
|||
auto line = lines[i]; |
|||
|
|||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) { |
|||
const auto master = line[0] == '{'; |
|||
const auto begin = master ? line.find('{') : line.find('['); |
|||
const auto end = master ? line.rfind('}') : line.rfind(']'); |
|||
|
|||
ASSERT(begin != std::string::npos && end != std::string::npos); |
|||
|
|||
const std::string patch_name{line.begin() + begin + 1, line.begin() + end}; |
|||
CheatList::Block block{}; |
|||
|
|||
while (i < lines.size() - 1) { |
|||
line = lines[++i]; |
|||
if (!line.empty() && (line[0] == '[' || line[0] == '{')) { |
|||
--i; |
|||
break; |
|||
} |
|||
|
|||
if (line.size() < 8) |
|||
continue; |
|||
|
|||
Cheat out{}; |
|||
out.raw = ParseSingleLineCheat(line); |
|||
block.push_back(out); |
|||
} |
|||
|
|||
(master ? master_list : standard_list).emplace_back(patch_name, block); |
|||
} |
|||
} |
|||
|
|||
return MakeCheatList(system, master_list, standard_list); |
|||
} |
|||
|
|||
std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const { |
|||
std::array<u8, 16> out{}; |
|||
|
|||
if (line.size() < 8) |
|||
return out; |
|||
|
|||
const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8}); |
|||
std::memcpy(out.data(), word1.data(), sizeof(u32)); |
|||
|
|||
if (line.size() < 17 || line[8] != ' ') |
|||
return out; |
|||
|
|||
const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8}); |
|||
std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32)); |
|||
|
|||
if (line.size() < 26 || line[17] != ' ') { |
|||
// Perform shifting in case value is truncated early.
|
|||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); |
|||
if (type == CodeType::Loop || type == CodeType::LoadImmediate || |
|||
type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) { |
|||
std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32)); |
|||
std::memset(out.data() + 4, 0, sizeof(u32)); |
|||
} |
|||
|
|||
return out; |
|||
} |
|||
|
|||
const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8}); |
|||
std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32)); |
|||
|
|||
if (line.size() < 35 || line[26] != ' ') { |
|||
// Perform shifting in case value is truncated early.
|
|||
const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4); |
|||
if (type == CodeType::WriteImmediate || type == CodeType::Conditional) { |
|||
std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32)); |
|||
std::memset(out.data() + 8, 0, sizeof(u32)); |
|||
} |
|||
|
|||
return out; |
|||
} |
|||
|
|||
const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8}); |
|||
std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32)); |
|||
|
|||
return out; |
|||
} |
|||
|
|||
namespace { |
|||
u64 MemoryReadImpl(u32 width, VAddr addr) { |
|||
switch (width) { |
|||
case 1: |
|||
return Memory::Read8(addr); |
|||
case 2: |
|||
return Memory::Read16(addr); |
|||
case 4: |
|||
return Memory::Read32(addr); |
|||
case 8: |
|||
return Memory::Read64(addr); |
|||
default: |
|||
UNREACHABLE(); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
void MemoryWriteImpl(u32 width, VAddr addr, u64 value) { |
|||
switch (width) { |
|||
case 1: |
|||
Memory::Write8(addr, static_cast<u8>(value)); |
|||
break; |
|||
case 2: |
|||
Memory::Write16(addr, static_cast<u16>(value)); |
|||
break; |
|||
case 4: |
|||
Memory::Write32(addr, static_cast<u32>(value)); |
|||
break; |
|||
case 8: |
|||
Memory::Write64(addr, value); |
|||
break; |
|||
default: |
|||
UNREACHABLE(); |
|||
} |
|||
} |
|||
} // Anonymous namespace
|
|||
|
|||
CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_, |
|||
const std::string& build_id, VAddr code_region_start, |
|||
VAddr code_region_end) |
|||
: cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} { |
|||
event = core_timing.RegisterEvent( |
|||
"CheatEngine::FrameCallback::" + build_id, |
|||
[this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); }); |
|||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event); |
|||
|
|||
const auto& vm_manager = system.CurrentProcess()->VMManager(); |
|||
for (auto& list : this->cheats) { |
|||
list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(), |
|||
code_region_end, vm_manager.GetHeapRegionEndAddress(), |
|||
&MemoryWriteImpl, &MemoryReadImpl); |
|||
} |
|||
} |
|||
|
|||
CheatEngine::~CheatEngine() { |
|||
core_timing.UnscheduleEvent(event, 0); |
|||
} |
|||
|
|||
void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) { |
|||
for (auto& list : cheats) { |
|||
list.Execute(); |
|||
} |
|||
|
|||
core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event); |
|||
} |
|||
|
|||
} // namespace FileSys
|
|||
@ -1,234 +0,0 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <set> |
|||
#include <vector> |
|||
#include "common/bit_field.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} |
|||
|
|||
namespace Core::Timing { |
|||
class CoreTiming; |
|||
struct EventType; |
|||
} // namespace Core::Timing |
|||
|
|||
namespace FileSys { |
|||
|
|||
enum class CodeType : u32 { |
|||
// 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY |
|||
// Writes a T sized value Y to the address A added to the value of register R in memory domain M |
|||
WriteImmediate = 0, |
|||
|
|||
// 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY |
|||
// Compares the T sized value Y to the value at address A in memory domain M using the |
|||
// conditional function C. If success, continues execution. If failure, jumps to the matching |
|||
// EndConditional statement. |
|||
Conditional = 1, |
|||
|
|||
// 20000000 |
|||
// Terminates a Conditional or ConditionalInput block. |
|||
EndConditional = 2, |
|||
|
|||
// 300R0000 VVVVVVVV |
|||
// Starts looping V times, storing the current count in register R. |
|||
// Loop block is terminated with a matching 310R0000. |
|||
Loop = 3, |
|||
|
|||
// 400R0000 VVVVVVVV VVVVVVVV |
|||
// Sets the value of register R to the value V. |
|||
LoadImmediate = 4, |
|||
|
|||
// 5TMRI0AA AAAAAAAA |
|||
// Sets the value of register R to the value of width T at address A in memory domain M, with |
|||
// the current value of R added to the address if I == 1. |
|||
LoadIndexed = 5, |
|||
|
|||
// 6T0RIFG0 VVVVVVVV VVVVVVVV |
|||
// Writes the value V of width T to the memory address stored in register R. Adds the value of |
|||
// register G to the final calculation if F is nonzero. Increments the value of register R by T |
|||
// after operation if I is nonzero. |
|||
StoreIndexed = 6, |
|||
|
|||
// 7T0RA000 VVVVVVVV |
|||
// Performs the arithmetic operation A on the value in register R and the value V of width T, |
|||
// storing the result in register R. |
|||
RegisterArithmetic = 7, |
|||
|
|||
// 8KKKKKKK |
|||
// Checks to see if any of the buttons defined by the bitmask K are pressed. If any are, |
|||
// execution continues. If none are, execution skips to the next EndConditional command. |
|||
ConditionalInput = 8, |
|||
}; |
|||
|
|||
enum class MemoryType : u32 { |
|||
// Addressed relative to start of main NSO |
|||
MainNSO = 0, |
|||
|
|||
// Addressed relative to start of heap |
|||
Heap = 1, |
|||
}; |
|||
|
|||
enum class ArithmeticOp : u32 { |
|||
Add = 0, |
|||
Sub = 1, |
|||
Mult = 2, |
|||
LShift = 3, |
|||
RShift = 4, |
|||
}; |
|||
|
|||
enum class ComparisonOp : u32 { |
|||
GreaterThan = 1, |
|||
GreaterThanEqual = 2, |
|||
LessThan = 3, |
|||
LessThanEqual = 4, |
|||
Equal = 5, |
|||
Inequal = 6, |
|||
}; |
|||
|
|||
union Cheat { |
|||
std::array<u8, 16> raw; |
|||
|
|||
BitField<4, 4, CodeType> type; |
|||
BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes. |
|||
BitField<0, 4, u32> end_of_loop; |
|||
BitField<12, 4, MemoryType> memory_type; |
|||
BitField<8, 4, u32> register_3; |
|||
BitField<8, 4, ComparisonOp> comparison_op; |
|||
BitField<20, 4, u32> load_from_register; |
|||
BitField<20, 4, u32> increment_register; |
|||
BitField<20, 4, ArithmeticOp> arithmetic_op; |
|||
BitField<16, 4, u32> add_additional_register; |
|||
BitField<28, 4, u32> register_6; |
|||
|
|||
u64 Address() const; |
|||
u64 ValueWidth(u64 offset) const; |
|||
u64 Value(u64 offset, u64 width) const; |
|||
u32 KeypadValue() const; |
|||
}; |
|||
|
|||
class CheatParser; |
|||
|
|||
// Represents a full collection of cheats for a game. The Execute function should be called every |
|||
// interval that all cheats should be executed. Clients should not directly instantiate this class |
|||
// (hence private constructor), they should instead receive an instance from CheatParser, which |
|||
// guarantees the list is always in an acceptable state. |
|||
class CheatList { |
|||
public: |
|||
friend class CheatParser; |
|||
|
|||
using Block = std::vector<Cheat>; |
|||
using ProgramSegment = std::vector<std::pair<std::string, Block>>; |
|||
|
|||
// (width in bytes, address, value) |
|||
using MemoryWriter = void (*)(u32, VAddr, u64); |
|||
// (width in bytes, address) -> value |
|||
using MemoryReader = u64 (*)(u32, VAddr); |
|||
|
|||
void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end, |
|||
MemoryWriter writer, MemoryReader reader); |
|||
|
|||
void Execute(); |
|||
|
|||
private: |
|||
CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard); |
|||
|
|||
void ProcessBlockPairs(const Block& block); |
|||
void ExecuteSingleCheat(const Cheat& cheat); |
|||
|
|||
void ExecuteBlock(const Block& block); |
|||
|
|||
bool EvaluateConditional(const Cheat& cheat) const; |
|||
|
|||
// Individual cheat operations |
|||
void WriteImmediate(const Cheat& cheat); |
|||
void BeginConditional(const Cheat& cheat); |
|||
void EndConditional(const Cheat& cheat); |
|||
void Loop(const Cheat& cheat); |
|||
void LoadImmediate(const Cheat& cheat); |
|||
void LoadIndexed(const Cheat& cheat); |
|||
void StoreIndexed(const Cheat& cheat); |
|||
void RegisterArithmetic(const Cheat& cheat); |
|||
void BeginConditionalInput(const Cheat& cheat); |
|||
|
|||
VAddr SanitizeAddress(VAddr in) const; |
|||
|
|||
// Master Codes are defined as codes that cannot be disabled and are run prior to all |
|||
// others. |
|||
ProgramSegment master_list; |
|||
// All other codes |
|||
ProgramSegment standard_list; |
|||
|
|||
bool in_standard = false; |
|||
|
|||
// 16 (0x0-0xF) scratch registers that can be used by cheats |
|||
std::array<u64, 16> scratch{}; |
|||
|
|||
MemoryWriter writer = nullptr; |
|||
MemoryReader reader = nullptr; |
|||
|
|||
u64 main_region_begin{}; |
|||
u64 heap_region_begin{}; |
|||
u64 main_region_end{}; |
|||
u64 heap_region_end{}; |
|||
|
|||
u64 current_block{}; |
|||
// The current index of the cheat within the current Block |
|||
u64 current_index{}; |
|||
|
|||
// The 'stack' of the program. When a conditional or loop statement is encountered, its index is |
|||
// pushed onto this queue. When a end block is encountered, the condition is checked. |
|||
std::map<u64, u64> block_pairs; |
|||
|
|||
std::set<u64> encountered_loops; |
|||
|
|||
const Core::System* system; |
|||
}; |
|||
|
|||
// Intermediary class that parses a text file or other disk format for storing cheats into a |
|||
// CheatList object, that can be used for execution. |
|||
class CheatParser { |
|||
public: |
|||
virtual ~CheatParser(); |
|||
|
|||
virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0; |
|||
|
|||
protected: |
|||
CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master, |
|||
CheatList::ProgramSegment standard) const; |
|||
}; |
|||
|
|||
// CheatParser implementation that parses text files |
|||
class TextCheatParser final : public CheatParser { |
|||
public: |
|||
~TextCheatParser() override; |
|||
|
|||
CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override; |
|||
|
|||
private: |
|||
std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const; |
|||
}; |
|||
|
|||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming |
|||
class CheatEngine final { |
|||
public: |
|||
CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id, |
|||
VAddr code_region_start, VAddr code_region_end); |
|||
~CheatEngine(); |
|||
|
|||
private: |
|||
void FrameCallback(u64 userdata, s64 cycles_late); |
|||
|
|||
std::vector<CheatList> cheats; |
|||
|
|||
Core::Timing::EventType* event; |
|||
Core::Timing::CoreTiming& core_timing; |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
@ -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
|
|||
@ -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 |
|||
@ -0,0 +1,58 @@ |
|||
/* |
|||
* Copyright (c) 2018-2019 Atmosphère-NX |
|||
* |
|||
* This program is free software; you can redistribute it and/or modify it |
|||
* under the terms and conditions of the GNU General Public License, |
|||
* version 2, as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope it will be useful, but WITHOUT |
|||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|||
* more details. |
|||
* |
|||
* You should have received a copy of the GNU General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
*/ |
|||
|
|||
/* |
|||
* Adapted by DarkLordZach for use/interaction with yuzu |
|||
* |
|||
* Modifications Copyright 2019 yuzu emulator team |
|||
* Licensed under GPLv2 or any later version |
|||
* Refer to the license.txt file included. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
namespace Memory { |
|||
|
|||
struct MemoryRegionExtents { |
|||
u64 base{}; |
|||
u64 size{}; |
|||
}; |
|||
|
|||
struct CheatProcessMetadata { |
|||
u64 process_id{}; |
|||
u64 title_id{}; |
|||
MemoryRegionExtents main_nso_extents{}; |
|||
MemoryRegionExtents heap_extents{}; |
|||
MemoryRegionExtents alias_extents{}; |
|||
MemoryRegionExtents address_space_extents{}; |
|||
std::array<u8, 0x20> main_nso_build_id{}; |
|||
}; |
|||
|
|||
struct CheatDefinition { |
|||
std::array<char, 0x40> readable_name{}; |
|||
u32 num_opcodes{}; |
|||
std::array<u32, 0x100> opcodes{}; |
|||
}; |
|||
|
|||
struct CheatEntry { |
|||
bool enabled{}; |
|||
u32 cheat_id{}; |
|||
CheatDefinition definition{}; |
|||
}; |
|||
|
|||
} // namespace Memory |
|||
1212
src/core/memory/dmnt_cheat_vm.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,321 @@ |
|||
/* |
|||
* Copyright (c) 2018-2019 Atmosphère-NX |
|||
* |
|||
* This program is free software; you can redistribute it and/or modify it |
|||
* under the terms and conditions of the GNU General Public License, |
|||
* version 2, as published by the Free Software Foundation. |
|||
* |
|||
* This program is distributed in the hope it will be useful, but WITHOUT |
|||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
|||
* more details. |
|||
* |
|||
* You should have received a copy of the GNU General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
*/ |
|||
|
|||
/* |
|||
* Adapted by DarkLordZach for use/interaction with yuzu |
|||
* |
|||
* Modifications Copyright 2019 yuzu emulator team |
|||
* Licensed under GPLv2 or any later version |
|||
* Refer to the license.txt file included. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <variant> |
|||
#include <vector> |
|||
#include <fmt/printf.h> |
|||
#include "common/common_types.h" |
|||
#include "core/memory/dmnt_cheat_types.h" |
|||
|
|||
namespace Memory { |
|||
|
|||
enum class CheatVmOpcodeType : u32 { |
|||
StoreStatic = 0, |
|||
BeginConditionalBlock = 1, |
|||
EndConditionalBlock = 2, |
|||
ControlLoop = 3, |
|||
LoadRegisterStatic = 4, |
|||
LoadRegisterMemory = 5, |
|||
StoreStaticToAddress = 6, |
|||
PerformArithmeticStatic = 7, |
|||
BeginKeypressConditionalBlock = 8, |
|||
|
|||
// These are not implemented by Gateway's VM. |
|||
PerformArithmeticRegister = 9, |
|||
StoreRegisterToAddress = 10, |
|||
Reserved11 = 11, |
|||
|
|||
// This is a meta entry, and not a real opcode. |
|||
// This is to facilitate multi-nybble instruction decoding. |
|||
ExtendedWidth = 12, |
|||
|
|||
// Extended width opcodes. |
|||
BeginRegisterConditionalBlock = 0xC0, |
|||
SaveRestoreRegister = 0xC1, |
|||
SaveRestoreRegisterMask = 0xC2, |
|||
|
|||
// This is a meta entry, and not a real opcode. |
|||
// This is to facilitate multi-nybble instruction decoding. |
|||
DoubleExtendedWidth = 0xF0, |
|||
|
|||
// Double-extended width opcodes. |
|||
DebugLog = 0xFFF, |
|||
}; |
|||
|
|||
enum class MemoryAccessType : u32 { |
|||
MainNso = 0, |
|||
Heap = 1, |
|||
}; |
|||
|
|||
enum class ConditionalComparisonType : u32 { |
|||
GT = 1, |
|||
GE = 2, |
|||
LT = 3, |
|||
LE = 4, |
|||
EQ = 5, |
|||
NE = 6, |
|||
}; |
|||
|
|||
enum class RegisterArithmeticType : u32 { |
|||
Addition = 0, |
|||
Subtraction = 1, |
|||
Multiplication = 2, |
|||
LeftShift = 3, |
|||
RightShift = 4, |
|||
|
|||
// These are not supported by Gateway's VM. |
|||
LogicalAnd = 5, |
|||
LogicalOr = 6, |
|||
LogicalNot = 7, |
|||
LogicalXor = 8, |
|||
|
|||
None = 9, |
|||
}; |
|||
|
|||
enum class StoreRegisterOffsetType : u32 { |
|||
None = 0, |
|||
Reg = 1, |
|||
Imm = 2, |
|||
MemReg = 3, |
|||
MemImm = 4, |
|||
MemImmReg = 5, |
|||
}; |
|||
|
|||
enum class CompareRegisterValueType : u32 { |
|||
MemoryRelAddr = 0, |
|||
MemoryOfsReg = 1, |
|||
RegisterRelAddr = 2, |
|||
RegisterOfsReg = 3, |
|||
StaticValue = 4, |
|||
OtherRegister = 5, |
|||
}; |
|||
|
|||
enum class SaveRestoreRegisterOpType : u32 { |
|||
Restore = 0, |
|||
Save = 1, |
|||
ClearSaved = 2, |
|||
ClearRegs = 3, |
|||
}; |
|||
|
|||
enum class DebugLogValueType : u32 { |
|||
MemoryRelAddr = 0, |
|||
MemoryOfsReg = 1, |
|||
RegisterRelAddr = 2, |
|||
RegisterOfsReg = 3, |
|||
RegisterValue = 4, |
|||
}; |
|||
|
|||
union VmInt { |
|||
u8 bit8; |
|||
u16 bit16; |
|||
u32 bit32; |
|||
u64 bit64; |
|||
}; |
|||
|
|||
struct StoreStaticOpcode { |
|||
u32 bit_width{}; |
|||
MemoryAccessType mem_type{}; |
|||
u32 offset_register{}; |
|||
u64 rel_address{}; |
|||
VmInt value{}; |
|||
}; |
|||
|
|||
struct BeginConditionalOpcode { |
|||
u32 bit_width{}; |
|||
MemoryAccessType mem_type{}; |
|||
ConditionalComparisonType cond_type{}; |
|||
u64 rel_address{}; |
|||
VmInt value{}; |
|||
}; |
|||
|
|||
struct EndConditionalOpcode {}; |
|||
|
|||
struct ControlLoopOpcode { |
|||
bool start_loop{}; |
|||
u32 reg_index{}; |
|||
u32 num_iters{}; |
|||
}; |
|||
|
|||
struct LoadRegisterStaticOpcode { |
|||
u32 reg_index{}; |
|||
u64 value{}; |
|||
}; |
|||
|
|||
struct LoadRegisterMemoryOpcode { |
|||
u32 bit_width{}; |
|||
MemoryAccessType mem_type{}; |
|||
u32 reg_index{}; |
|||
bool load_from_reg{}; |
|||
u64 rel_address{}; |
|||
}; |
|||
|
|||
struct StoreStaticToAddressOpcode { |
|||
u32 bit_width{}; |
|||
u32 reg_index{}; |
|||
bool increment_reg{}; |
|||
bool add_offset_reg{}; |
|||
u32 offset_reg_index{}; |
|||
u64 value{}; |
|||
}; |
|||
|
|||
struct PerformArithmeticStaticOpcode { |
|||
u32 bit_width{}; |
|||
u32 reg_index{}; |
|||
RegisterArithmeticType math_type{}; |
|||
u32 value{}; |
|||
}; |
|||
|
|||
struct BeginKeypressConditionalOpcode { |
|||
u32 key_mask{}; |
|||
}; |
|||
|
|||
struct PerformArithmeticRegisterOpcode { |
|||
u32 bit_width{}; |
|||
RegisterArithmeticType math_type{}; |
|||
u32 dst_reg_index{}; |
|||
u32 src_reg_1_index{}; |
|||
u32 src_reg_2_index{}; |
|||
bool has_immediate{}; |
|||
VmInt value{}; |
|||
}; |
|||
|
|||
struct StoreRegisterToAddressOpcode { |
|||
u32 bit_width{}; |
|||
u32 str_reg_index{}; |
|||
u32 addr_reg_index{}; |
|||
bool increment_reg{}; |
|||
StoreRegisterOffsetType ofs_type{}; |
|||
MemoryAccessType mem_type{}; |
|||
u32 ofs_reg_index{}; |
|||
u64 rel_address{}; |
|||
}; |
|||
|
|||
struct BeginRegisterConditionalOpcode { |
|||
u32 bit_width{}; |
|||
ConditionalComparisonType cond_type{}; |
|||
u32 val_reg_index{}; |
|||
CompareRegisterValueType comp_type{}; |
|||
MemoryAccessType mem_type{}; |
|||
u32 addr_reg_index{}; |
|||
u32 other_reg_index{}; |
|||
u32 ofs_reg_index{}; |
|||
u64 rel_address{}; |
|||
VmInt value{}; |
|||
}; |
|||
|
|||
struct SaveRestoreRegisterOpcode { |
|||
u32 dst_index{}; |
|||
u32 src_index{}; |
|||
SaveRestoreRegisterOpType op_type{}; |
|||
}; |
|||
|
|||
struct SaveRestoreRegisterMaskOpcode { |
|||
SaveRestoreRegisterOpType op_type{}; |
|||
std::array<bool, 0x10> should_operate{}; |
|||
}; |
|||
|
|||
struct DebugLogOpcode { |
|||
u32 bit_width{}; |
|||
u32 log_id{}; |
|||
DebugLogValueType val_type{}; |
|||
MemoryAccessType mem_type{}; |
|||
u32 addr_reg_index{}; |
|||
u32 val_reg_index{}; |
|||
u32 ofs_reg_index{}; |
|||
u64 rel_address{}; |
|||
}; |
|||
|
|||
struct UnrecognizedInstruction { |
|||
CheatVmOpcodeType opcode{}; |
|||
}; |
|||
|
|||
struct CheatVmOpcode { |
|||
bool begin_conditional_block{}; |
|||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, |
|||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, |
|||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, |
|||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, |
|||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, |
|||
SaveRestoreRegisterMaskOpcode, DebugLogOpcode, UnrecognizedInstruction> |
|||
opcode{}; |
|||
}; |
|||
|
|||
class DmntCheatVm { |
|||
public: |
|||
/// Helper Type for DmntCheatVm <=> yuzu Interface |
|||
class Callbacks { |
|||
public: |
|||
virtual ~Callbacks(); |
|||
|
|||
virtual void MemoryRead(VAddr address, void* data, u64 size) = 0; |
|||
virtual void MemoryWrite(VAddr address, const void* data, u64 size) = 0; |
|||
|
|||
virtual u64 HidKeysDown() = 0; |
|||
|
|||
virtual void DebugLog(u8 id, u64 value) = 0; |
|||
virtual void CommandLog(std::string_view data) = 0; |
|||
}; |
|||
|
|||
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; |
|||
static constexpr std::size_t NumRegisters = 0x10; |
|||
|
|||
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks); |
|||
~DmntCheatVm(); |
|||
|
|||
std::size_t GetProgramSize() const { |
|||
return this->num_opcodes; |
|||
} |
|||
|
|||
bool LoadProgram(const std::vector<CheatEntry>& cheats); |
|||
void Execute(const CheatProcessMetadata& metadata); |
|||
|
|||
private: |
|||
std::unique_ptr<Callbacks> callbacks; |
|||
|
|||
std::size_t num_opcodes = 0; |
|||
std::size_t instruction_ptr = 0; |
|||
std::size_t condition_depth = 0; |
|||
bool decode_success = false; |
|||
std::array<u32, MaximumProgramOpcodeCount> program{}; |
|||
std::array<u64, NumRegisters> registers{}; |
|||
std::array<u64, NumRegisters> saved_values{}; |
|||
std::array<std::size_t, NumRegisters> loop_tops{}; |
|||
|
|||
bool DecodeNextOpcode(CheatVmOpcode& out); |
|||
void SkipConditionalBlock(); |
|||
void ResetState(); |
|||
|
|||
// For implementing the DebugLog opcode. |
|||
void DebugLog(u32 log_id, u64 value); |
|||
|
|||
void LogOpcode(const CheatVmOpcode& opcode); |
|||
|
|||
static u64 GetVmInt(VmInt value, u32 bit_width); |
|||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, |
|||
MemoryAccessType mem_type, u64 rel_address); |
|||
}; |
|||
|
|||
}; // namespace Memory |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue