2022-04-23 04:59:50 -04:00
|
|
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2019-05-30 19:35:03 -04:00
|
|
|
|
|
|
|
#include <locale>
|
|
|
|
#include "common/hex_util.h"
|
|
|
|
#include "common/microprofile.h"
|
|
|
|
#include "common/swap.h"
|
2024-02-06 14:44:39 -05:00
|
|
|
#include "core/arm/debug.h"
|
2019-05-30 19:35:03 -04:00
|
|
|
#include "core/core.h"
|
|
|
|
#include "core/core_timing.h"
|
2021-02-12 20:58:31 -05:00
|
|
|
#include "core/hle/kernel/k_page_table.h"
|
2021-04-24 01:04:28 -04:00
|
|
|
#include "core/hle/kernel/k_process.h"
|
2024-02-04 16:19:54 -05:00
|
|
|
#include "core/hle/kernel/k_process_page_table.h"
|
2024-02-05 15:19:26 -05:00
|
|
|
#include "core/hle/kernel/svc_types.h"
|
2023-11-14 22:34:27 -05:00
|
|
|
#include "core/hle/service/hid/hid_server.h"
|
2019-05-30 19:35:03 -04:00
|
|
|
#include "core/hle/service/sm/sm.h"
|
2020-04-08 22:21:21 -04:00
|
|
|
#include "core/memory.h"
|
2019-05-30 19:35:03 -04:00
|
|
|
#include "core/memory/cheat_engine.h"
|
2024-01-04 21:37:43 -05:00
|
|
|
#include "hid_core/resource_manager.h"
|
|
|
|
#include "hid_core/resources/npad/npad.h"
|
2019-05-30 19:35:03 -04:00
|
|
|
|
2020-03-31 15:10:44 -04:00
|
|
|
namespace Core::Memory {
|
2020-09-15 03:24:42 -04:00
|
|
|
namespace {
|
2020-07-15 18:30:06 -04:00
|
|
|
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
|
2019-05-30 19:35:03 -04:00
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
|
|
|
|
std::size_t start_index, char match) {
|
2020-09-15 03:24:42 -04:00
|
|
|
auto end_index = start_index;
|
|
|
|
while (data[end_index] != match) {
|
|
|
|
++end_index;
|
2023-10-21 23:03:04 -04:00
|
|
|
if (end_index > data.size()) {
|
2020-09-15 03:24:42 -04:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
out_name_size = end_index - start_index;
|
|
|
|
|
|
|
|
// Clamp name if it's too big
|
|
|
|
if (out_name_size > sizeof(CheatDefinition::readable_name)) {
|
|
|
|
end_index = start_index + sizeof(CheatDefinition::readable_name);
|
|
|
|
}
|
|
|
|
|
2020-09-15 03:24:42 -04:00
|
|
|
return data.substr(start_index, end_index - start_index);
|
|
|
|
}
|
|
|
|
} // Anonymous namespace
|
|
|
|
|
2021-05-16 01:46:30 -04:00
|
|
|
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_)
|
|
|
|
: metadata{metadata_}, system{system_} {}
|
2019-05-30 19:35:03 -04:00
|
|
|
|
|
|
|
StandardVmCallbacks::~StandardVmCallbacks() = default;
|
|
|
|
|
2024-02-05 12:06:25 -05:00
|
|
|
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) {
|
|
|
|
// Return zero on invalid address
|
|
|
|
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
|
|
|
std::memset(data, 0, size);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
system.ApplicationMemory().ReadBlock(address, data, size);
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
2024-02-05 12:06:25 -05:00
|
|
|
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
|
|
|
|
// Skip invalid memory write address
|
|
|
|
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-02-06 14:44:39 -05:00
|
|
|
if (system.ApplicationMemory().WriteBlock(address, data, size)) {
|
|
|
|
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
|
|
|
|
}
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
u64 StandardVmCallbacks::HidKeysDown() {
|
2023-11-14 22:34:27 -05:00
|
|
|
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
|
2023-02-19 15:29:42 -05:00
|
|
|
if (hid == nullptr) {
|
|
|
|
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-11-14 22:34:27 -05:00
|
|
|
const auto applet_resource = hid->GetResourceManager();
|
2023-12-14 21:04:38 -05:00
|
|
|
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
|
2019-05-30 19:35:03 -04:00
|
|
|
LOG_WARNING(CheatEngine,
|
|
|
|
"Attempted to read input state, but applet resource is not initialized!");
|
2020-08-06 02:55:45 -04:00
|
|
|
return 0;
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
2023-11-17 12:46:26 -05:00
|
|
|
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
|
2021-11-29 18:23:52 -05:00
|
|
|
return static_cast<u64>(press_state & HID::NpadButton::All);
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
2024-02-05 15:19:26 -05:00
|
|
|
void StandardVmCallbacks::PauseProcess() {
|
|
|
|
if (system.ApplicationProcess()->IsSuspended()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused);
|
|
|
|
}
|
|
|
|
|
|
|
|
void StandardVmCallbacks::ResumeProcess() {
|
|
|
|
if (!system.ApplicationProcess()->IsSuspended()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
|
|
|
|
}
|
|
|
|
|
2019-05-30 19:35:03 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-02-05 12:06:25 -05:00
|
|
|
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const {
|
2019-05-30 19:35:03 -04:00
|
|
|
if ((in < metadata.main_nso_extents.base ||
|
|
|
|
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) &&
|
|
|
|
(in < metadata.heap_extents.base ||
|
2024-02-04 16:19:54 -05:00
|
|
|
in >= metadata.heap_extents.base + metadata.heap_extents.size) &&
|
|
|
|
(in < metadata.alias_extents.base ||
|
2024-02-17 20:10:17 -05:00
|
|
|
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
|
2024-02-04 16:19:54 -05:00
|
|
|
(in < metadata.aslr_extents.base ||
|
2024-02-17 20:10:17 -05:00
|
|
|
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
|
2024-02-04 16:19:54 -05:00
|
|
|
LOG_DEBUG(CheatEngine,
|
2019-05-30 19:35:03 -04:00
|
|
|
"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);
|
2024-02-05 12:06:25 -05:00
|
|
|
return false; ///< Invalid addresses will hard crash
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
2024-02-05 12:06:25 -05:00
|
|
|
return true;
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
CheatParser::~CheatParser() = default;
|
|
|
|
|
|
|
|
TextCheatParser::~TextCheatParser() = default;
|
|
|
|
|
2020-09-15 03:13:22 -04:00
|
|
|
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
|
2019-05-30 19:35:03 -04:00
|
|
|
std::vector<CheatEntry> out(1);
|
2020-09-15 03:13:22 -04:00
|
|
|
std::optional<u64> current_entry;
|
2019-05-30 19:35:03 -04:00
|
|
|
|
|
|
|
for (std::size_t i = 0; i < data.size(); ++i) {
|
2019-09-21 18:13:10 -04:00
|
|
|
if (::isspace(data[i])) {
|
2019-05-30 19:35:03 -04:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data[i] == '{') {
|
|
|
|
current_entry = 0;
|
|
|
|
|
|
|
|
if (out[*current_entry].definition.num_opcodes > 0) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
std::size_t name_size{};
|
|
|
|
const auto name = ExtractName(name_size, data, i + 1, '}');
|
2019-05-30 19:35:03 -04:00
|
|
|
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';
|
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
i += name_size + 1;
|
2019-05-30 19:35:03 -04:00
|
|
|
} else if (data[i] == '[') {
|
|
|
|
current_entry = out.size();
|
|
|
|
out.emplace_back();
|
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
std::size_t name_size{};
|
|
|
|
const auto name = ExtractName(name_size, data, i + 1, ']');
|
2019-05-30 19:35:03 -04:00
|
|
|
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';
|
|
|
|
|
2023-10-21 23:03:04 -04:00
|
|
|
i += name_size + 1;
|
2019-09-21 18:13:10 -04:00
|
|
|
} else if (::isxdigit(data[i])) {
|
2019-05-30 19:35:03 -04:00
|
|
|
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 {};
|
|
|
|
}
|
|
|
|
|
2023-09-13 13:51:58 -04:00
|
|
|
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
|
2019-05-30 19:35:03 -04:00
|
|
|
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
|
2020-10-13 08:10:50 -04:00
|
|
|
value;
|
2019-05-30 19:35:03 -04:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-05-16 01:46:30 -04:00
|
|
|
CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_,
|
|
|
|
const std::array<u8, 0x20>& build_id_)
|
2023-07-14 21:58:20 -04:00
|
|
|
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
|
|
|
|
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
|
2021-05-16 01:46:30 -04:00
|
|
|
metadata.main_nso_build_id = build_id_;
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
CheatEngine::~CheatEngine() {
|
2023-12-23 13:58:09 -05:00
|
|
|
core_timing.UnscheduleEvent(event);
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void CheatEngine::Initialize() {
|
2020-07-27 19:00:41 -04:00
|
|
|
event = Core::Timing::CreateEvent(
|
|
|
|
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
|
2023-12-23 13:58:09 -05:00
|
|
|
[this](s64 time,
|
2022-07-10 01:59:40 -04:00
|
|
|
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
2023-12-23 13:58:09 -05:00
|
|
|
FrameCallback(ns_late);
|
2022-07-10 01:59:40 -04:00
|
|
|
return std::nullopt;
|
2020-07-27 19:00:41 -04:00
|
|
|
});
|
2022-07-10 03:29:37 -04:00
|
|
|
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
|
2019-05-30 19:35:03 -04:00
|
|
|
|
2023-03-07 16:45:13 -05:00
|
|
|
metadata.process_id = system.ApplicationProcess()->GetProcessId();
|
2023-02-13 11:21:43 -05:00
|
|
|
metadata.title_id = system.GetApplicationProcessProgramID();
|
2019-05-30 19:35:03 -04:00
|
|
|
|
2023-07-14 21:43:15 -04:00
|
|
|
const auto& page_table = system.ApplicationProcess()->GetPageTable();
|
2020-08-06 02:48:11 -04:00
|
|
|
metadata.heap_extents = {
|
2023-03-17 21:26:04 -04:00
|
|
|
.base = GetInteger(page_table.GetHeapRegionStart()),
|
2020-08-06 02:48:11 -04:00
|
|
|
.size = page_table.GetHeapRegionSize(),
|
|
|
|
};
|
2024-02-04 16:19:54 -05:00
|
|
|
metadata.aslr_extents = {
|
2023-03-17 21:26:04 -04:00
|
|
|
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
|
2020-08-06 02:48:11 -04:00
|
|
|
.size = page_table.GetAliasCodeRegionSize(),
|
|
|
|
};
|
2024-02-04 16:19:54 -05:00
|
|
|
metadata.alias_extents = {
|
|
|
|
.base = GetInteger(page_table.GetAliasRegionStart()),
|
|
|
|
.size = page_table.GetAliasRegionSize(),
|
|
|
|
};
|
2019-05-30 19:35:03 -04:00
|
|
|
|
|
|
|
is_pending_reload.exchange(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
|
2020-08-06 02:48:11 -04:00
|
|
|
metadata.main_nso_extents = {
|
|
|
|
.base = main_region_begin,
|
|
|
|
.size = main_region_size,
|
|
|
|
};
|
2019-05-30 19:35:03 -04:00
|
|
|
}
|
|
|
|
|
2021-05-02 22:14:15 -04:00
|
|
|
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
|
|
|
|
cheats = std::move(reload_cheats);
|
2019-05-30 19:35:03 -04:00
|
|
|
is_pending_reload.exchange(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
|
|
|
|
|
2023-12-23 13:58:09 -05:00
|
|
|
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
|
2019-05-30 19:35:03 -04:00
|
|
|
if (is_pending_reload.exchange(false)) {
|
|
|
|
vm.LoadProgram(cheats);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vm.GetProgramSize() == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
MICROPROFILE_SCOPE(Cheat_Engine);
|
|
|
|
|
|
|
|
vm.Execute(metadata);
|
|
|
|
}
|
|
|
|
|
2020-03-31 15:10:44 -04:00
|
|
|
} // namespace Core::Memory
|