mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #2535 from DarkLordZach/cheat-v2
cheat_engine: Use Atmosphere's Cheat VM and fix cheat crashmerge-requests/60/head
commit
9187350b32
@ -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
|
File diff suppressed because it is too large
Load Diff
@ -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
|
Loading…
Reference in New Issue