cheat_engine: Move to memory and strip VM
This is to go with the Atmosphere VM port, now it just contains the callbacks needed for the interface between DmntCheatVm and yuzu, along with the cheat parsers.
This commit is contained in:
parent
12aa127df3
commit
7d41c1f523
5 changed files with 325 additions and 728 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
234
src/core/memory/cheat_engine.cpp
Normal file
234
src/core/memory/cheat_engine.cpp
Normal file
|
@ -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
|
86
src/core/memory/cheat_engine.h
Normal file
86
src/core/memory/cheat_engine.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue