service: dmnt: Implement cheat manager and interface

This commit is contained in:
german77 2024-02-18 10:48:12 -06:00
parent a93d249ac1
commit 012c0b0157
21 changed files with 1554 additions and 630 deletions

View file

@ -590,6 +590,18 @@ add_library(core STATIC
hle/service/caps/caps_u.h
hle/service/cmif_serialization.h
hle/service/cmif_types.h
hle/service/dmnt/cheat_interface.cpp
hle/service/dmnt/cheat_interface.h
hle/service/dmnt/cheat_parser.cpp
hle/service/dmnt/cheat_parser.h
hle/service/dmnt/cheat_process_manager.cpp
hle/service/dmnt/cheat_process_manager.h
hle/service/dmnt/cheat_virtual_machine.cpp
hle/service/dmnt/cheat_virtual_machine.h
hle/service/dmnt/dmnt.cpp
hle/service/dmnt/dmnt.h
hle/service/dmnt/dmnt_results.h
hle/service/dmnt/dmnt_types.h
hle/service/erpt/erpt.cpp
hle/service/erpt/erpt.h
hle/service/es/es.cpp
@ -1109,11 +1121,6 @@ add_library(core STATIC
loader/xci.h
memory.cpp
memory.h
memory/cheat_engine.cpp
memory/cheat_engine.h
memory/dmnt_cheat_types.h
memory/dmnt_cheat_vm.cpp
memory/dmnt_cheat_vm.h
perf_stats.cpp
perf_stats.h
precompiled_headers.h

View file

@ -39,6 +39,7 @@
#include "core/hle/service/am/applet_manager.h"
#include "core/hle/service/am/frontend/applets.h"
#include "core/hle/service/apm/apm_controller.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/glue/glue_manager.h"
#include "core/hle/service/glue/time/static.h"
@ -53,7 +54,6 @@
#include "core/internal_network/network.h"
#include "core/loader/loader.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
#include "core/perf_stats.h"
#include "core/reporter.h"
#include "core/telemetry_session.h"
@ -361,6 +361,11 @@ struct System::Impl {
Kernel::KProcess::Register(system.Kernel(), main_process);
kernel.AppendNewProcess(main_process);
kernel.MakeApplicationProcess(main_process);
if (!cheat_manager) {
cheat_manager = std::make_unique<Service::DMNT::CheatProcessManager>(system);
}
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
LOG_CRITICAL(Core, "Failed to load ROM (Error {})!", load_result);
@ -382,11 +387,6 @@ struct System::Impl {
AddGlueRegistrationForProcess(*app_loader, *main_process);
telemetry_session->AddInitialInfo(*app_loader, fs_controller, *content_provider);
// Initialize cheat engine
if (cheat_engine) {
cheat_engine->Initialize();
}
// Register with applet manager.
applet_manager.CreateAndInsertByFrontendAppletParameters(main_process->GetProcessId(),
params);
@ -470,7 +470,7 @@ struct System::Impl {
services.reset();
service_manager.reset();
fs_controller.Reset();
cheat_engine.reset();
cheat_manager.reset();
telemetry_session.reset();
core_timing.ClearPendingEvents();
app_loader.reset();
@ -573,7 +573,7 @@ struct System::Impl {
bool nvdec_active{};
Reporter reporter;
std::unique_ptr<Memory::CheatEngine> cheat_engine;
std::unique_ptr<Service::DMNT::CheatProcessManager> cheat_manager;
std::unique_ptr<Tools::Freezer> memory_freezer;
std::array<u8, 0x20> build_id{};
@ -879,11 +879,20 @@ FileSys::VirtualFilesystem System::GetFilesystem() const {
return impl->virtual_filesystem;
}
void System::RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
void System::RegisterCheatList(std::span<const Service::DMNT::CheatEntry> list,
const std::array<u8, 32>& build_id, u64 main_region_begin,
u64 main_region_size) {
impl->cheat_engine = std::make_unique<Memory::CheatEngine>(*this, list, build_id);
impl->cheat_engine->SetMainMemoryParameters(main_region_begin, main_region_size);
impl->cheat_manager->AttachToApplicationProcess(build_id, main_region_begin, main_region_size);
// Register cheat list
for (const auto& cheat : list) {
if (cheat.cheat_id == 0) {
impl->cheat_manager->SetMasterCheat(cheat.definition);
continue;
}
u32 cheat_id{};
impl->cheat_manager->AddCheat(cheat_id, cheat.enabled, cheat.definition);
}
}
void System::SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet&& set) {
@ -1033,6 +1042,14 @@ const Core::Debugger& System::GetDebugger() const {
return *impl->debugger;
}
Service::DMNT::CheatProcessManager& System::GetCheatManager() {
return *impl->cheat_manager;
}
const Service::DMNT::CheatProcessManager& System::GetCheatManager() const {
return *impl->cheat_manager;
}
Network::RoomNetwork& System::GetRoomNetwork() {
return impl->room_network;
}

View file

@ -40,7 +40,6 @@ enum class ResultStatus : u16;
} // namespace Loader
namespace Core::Memory {
struct CheatEntry;
class Memory;
} // namespace Core::Memory
@ -64,6 +63,11 @@ namespace APM {
class Controller;
}
namespace DMNT {
struct CheatEntry;
class CheatProcessManager;
} // namespace DMNT
namespace FileSystem {
class FileSystemController;
} // namespace FileSystem
@ -345,7 +349,7 @@ public:
[[nodiscard]] FileSys::VirtualFilesystem GetFilesystem() const;
void RegisterCheatList(const std::vector<Memory::CheatEntry>& list,
void RegisterCheatList(std::span<const Service::DMNT::CheatEntry> list,
const std::array<u8, 0x20>& build_id, u64 main_region_begin,
u64 main_region_size);
@ -387,6 +391,9 @@ public:
[[nodiscard]] Core::Debugger& GetDebugger();
[[nodiscard]] const Core::Debugger& GetDebugger() const;
[[nodiscard]] Service::DMNT::CheatProcessManager& GetCheatManager();
[[nodiscard]] const Service::DMNT::CheatProcessManager& GetCheatManager() const;
/// Gets a mutable reference to the Room Network.
[[nodiscard]] Network::RoomNetwork& GetRoomNetwork();

View file

@ -24,12 +24,13 @@
#include "core/file_sys/vfs/vfs_cached.h"
#include "core/file_sys/vfs/vfs_layered.h"
#include "core/file_sys/vfs/vfs_vector.h"
#include "core/hle/service/dmnt/cheat_parser.h"
#include "core/hle/service/dmnt/dmnt_types.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/ns/language.h"
#include "core/hle/service/set/settings_server.h"
#include "core/loader/loader.h"
#include "core/loader/nso.h"
#include "core/memory/cheat_engine.h"
namespace FileSys {
namespace {
@ -79,7 +80,7 @@ VirtualDir FindSubdirectoryCaseless(const VirtualDir dir, std::string_view name)
#endif
}
std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
std::optional<std::vector<Service::DMNT::CheatEntry>> ReadCheatFileFromFolder(
u64 title_id, const PatchManager::BuildID& build_id_, const VirtualDir& base_path, bool upper) {
const auto build_id_raw = Common::HexToString(build_id_, upper);
const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
@ -98,7 +99,7 @@ std::optional<std::vector<Core::Memory::CheatEntry>> ReadCheatFileFromFolder(
return std::nullopt;
}
const Core::Memory::TextCheatParser parser;
const Service::DMNT::CheatParser parser;
return parser.Parse(std::string_view(reinterpret_cast<const char*>(data.data()), data.size()));
}
@ -313,7 +314,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name)
return !CollectPatches(patch_dirs, build_id).empty();
}
std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
std::vector<Service::DMNT::CheatEntry> PatchManager::CreateCheatList(
const BuildID& build_id_) const {
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
if (load_dir == nullptr) {
@ -326,7 +327,7 @@ std::vector<Core::Memory::CheatEntry> PatchManager::CreateCheatList(
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
std::vector<Core::Memory::CheatEntry> out;
std::vector<Service::DMNT::CheatEntry> out;
for (const auto& subdir : patch_dirs) {
if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend()) {
continue;

View file

@ -10,7 +10,7 @@
#include "common/common_types.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs/vfs_types.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/hle/service/dmnt/dmnt_types.h"
namespace Core {
class System;
@ -20,6 +20,10 @@ namespace Service::FileSystem {
class FileSystemController;
}
namespace Service::DMNT {
struct CheatEntry;
}
namespace FileSys {
class ContentProvider;
@ -65,7 +69,7 @@ public:
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
// Creates a CheatList object with all
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
[[nodiscard]] std::vector<Service::DMNT::CheatEntry> CreateCheatList(
const BuildID& build_id) const;
// Currently tracked RomFS patches:

View file

@ -0,0 +1,237 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/dmnt/cheat_interface.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/dmnt/dmnt_results.h"
#include "core/hle/service/dmnt/dmnt_types.h"
namespace Service::DMNT {
ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager)
: ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} {
// clang-format off
static const FunctionInfo functions[] = {
{65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"},
{65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"},
{65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"},
{65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"},
{65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"},
{65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"},
{65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"},
{65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"},
{65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"},
{65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"},
{65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"},
{65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"},
{65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"},
{65201, C<&ICheatInterface::GetCheats>, "GetCheats"},
{65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"},
{65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"},
{65204, C<&ICheatInterface::AddCheat>, "AddCheat"},
{65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"},
{65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"},
{65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"},
{65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"},
{65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"},
{65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"},
{65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"},
{65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"},
{65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"},
{65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"},
};
// clang-format on
RegisterHandlers(functions);
}
ICheatInterface::~ICheatInterface() = default;
Result ICheatInterface::HasCheatProcess(Out<bool> out_has_cheat) {
LOG_INFO(CheatEngine, "called");
*out_has_cheat = cheat_process_manager.HasCheatProcess();
R_SUCCEED();
}
Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
LOG_INFO(CheatEngine, "called");
*out_event = &cheat_process_manager.GetCheatProcessEvent();
R_SUCCEED();
}
Result ICheatInterface::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata));
}
Result ICheatInterface::ForceOpenCheatProcess() {
LOG_INFO(CheatEngine, "called");
R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached);
R_SUCCEED();
}
Result ICheatInterface::PauseCheatProcess() {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.PauseCheatProcess());
}
Result ICheatInterface::ResumeCheatProcess() {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.ResumeCheatProcess());
}
Result ICheatInterface::ForceCloseCheatProcess() {
LOG_WARNING(CheatEngine, "(STUBBED) called");
R_RETURN(cheat_process_manager.ForceCloseCheatProcess());
}
Result ICheatInterface::GetCheatProcessMappingCount(Out<u64> out_count) {
LOG_WARNING(CheatEngine, "(STUBBED) called");
R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count));
}
Result ICheatInterface::GetCheatProcessMappings(
Out<u64> out_count, u64 offset,
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings) {
LOG_INFO(CheatEngine, "called, offset={}", offset);
R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer);
R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings));
}
Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size,
OutBuffer<BufferAttr_HipcMapAlias> out_buffer) {
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer);
R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer));
}
Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size,
InBuffer<BufferAttr_HipcMapAlias> buffer) {
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size);
R_UNLESS(!buffer.empty(), ResultCheatNullBuffer);
R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer));
}
Result ICheatInterface::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping,
u64 address) {
LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address);
R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address));
}
Result ICheatInterface::GetCheatCount(Out<u64> out_count) {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.GetCheatCount(*out_count));
}
Result ICheatInterface::GetCheats(Out<u64> out_count, u64 offset,
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats) {
LOG_INFO(CheatEngine, "called, offset={}", offset);
R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer);
R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats));
}
Result ICheatInterface::GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat,
u32 cheat_id) {
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id));
}
Result ICheatInterface::ToggleCheat(u32 cheat_id) {
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
R_RETURN(cheat_process_manager.ToggleCheat(cheat_id));
}
Result ICheatInterface::AddCheat(
Out<u32> out_cheat_id, bool is_enabled,
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled);
R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition));
}
Result ICheatInterface::RemoveCheat(u32 cheat_id) {
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id);
R_RETURN(cheat_process_manager.RemoveCheat(cheat_id));
}
Result ICheatInterface::ReadStaticRegister(Out<u64> out_value, u8 register_index) {
LOG_DEBUG(CheatEngine, "called, register_index={}", register_index);
R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index));
}
Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) {
LOG_DEBUG(CheatEngine, "called, register_index={, value={}", register_index, value);
R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value));
}
Result ICheatInterface::ResetStaticRegisters() {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.ResetStaticRegisters());
}
Result ICheatInterface::SetMasterCheat(
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) {
LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(),
cheat_definition->num_opcodes);
R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition));
}
Result ICheatInterface::GetFrozenAddressCount(Out<u64> out_count) {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count));
}
Result ICheatInterface::GetFrozenAddresses(
Out<u64> out_count, u64 offset,
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address) {
LOG_INFO(CheatEngine, "called, offset={}", offset);
R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer);
R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address));
}
Result ICheatInterface::GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry,
u64 address) {
LOG_INFO(CheatEngine, "called, address={}", address);
R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address));
}
Result ICheatInterface::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) {
LOG_INFO(CheatEngine, "called, address={}, width={}", address, width);
R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth);
R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth);
R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth);
R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width));
}
Result ICheatInterface::DisableFrozenAddress(u64 address) {
LOG_INFO(CheatEngine, "called, address={}", address);
R_RETURN(cheat_process_manager.DisableFrozenAddress(address));
}
void ICheatInterface::InitializeCheatManager() {
LOG_INFO(CheatEngine, "called");
}
Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data,
size_t size) {
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size));
}
Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data,
size_t size) {
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size);
R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size));
}
Result ICheatInterface::PauseCheatProcessUnsafe() {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe());
}
Result ICheatInterface::ResumeCheatProcessUnsafe() {
LOG_INFO(CheatEngine, "called");
R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe());
}
} // namespace Service::DMNT

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Kernel::Svc {
struct MemoryInfo;
}
namespace Service::DMNT {
struct CheatDefinition;
struct CheatEntry;
struct CheatProcessMetadata;
struct FrozenAddressEntry;
class CheatProcessManager;
class ICheatInterface final : public ServiceFramework<ICheatInterface> {
public:
explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager);
~ICheatInterface() override;
private:
Result HasCheatProcess(Out<bool> out_has_cheat);
Result GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata);
Result ForceOpenCheatProcess();
Result PauseCheatProcess();
Result ResumeCheatProcess();
Result ForceCloseCheatProcess();
Result GetCheatProcessMappingCount(Out<u64> out_count);
Result GetCheatProcessMappings(
Out<u64> out_count, u64 offset,
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings);
Result ReadCheatProcessMemory(u64 address, u64 size,
OutBuffer<BufferAttr_HipcMapAlias> out_buffer);
Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer<BufferAttr_HipcMapAlias> buffer);
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, u64 address);
Result GetCheatCount(Out<u64> out_count);
Result GetCheats(Out<u64> out_count, u64 offset,
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats);
Result GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, u32 cheat_id);
Result ToggleCheat(u32 cheat_id);
Result AddCheat(Out<u32> out_cheat_id, bool enabled,
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
Result RemoveCheat(u32 cheat_id);
Result ReadStaticRegister(Out<u64> out_value, u8 register_index);
Result WriteStaticRegister(u8 register_index, u64 value);
Result ResetStaticRegisters();
Result SetMasterCheat(InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition);
Result GetFrozenAddressCount(Out<u64> out_count);
Result GetFrozenAddresses(
Out<u64> out_count, u64 offset,
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address);
Result GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, u64 address);
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width);
Result DisableFrozenAddress(u64 address);
private:
void InitializeCheatManager();
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, size_t size);
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, size_t size);
Result PauseCheatProcessUnsafe();
Result ResumeCheatProcessUnsafe();
CheatProcessManager& cheat_process_manager;
};
} // namespace Service::DMNT

View file

@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <cctype>
#include <optional>
#include "core/hle/service/dmnt/cheat_parser.h"
#include "core/hle/service/dmnt/dmnt_types.h"
namespace Service::DMNT {
CheatParser::CheatParser() {}
CheatParser::~CheatParser() = default;
std::vector<CheatEntry> CheatParser::Parse(std::string_view data) const {
std::vector<CheatEntry> out(1);
std::optional<u64> current_entry;
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 {};
}
std::size_t name_size{};
const auto name = ExtractName(name_size, 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_size + 1;
} else if (data[i] == '[') {
current_entry = out.size();
out.emplace_back();
std::size_t name_size{};
const auto name = ExtractName(name_size, 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_size + 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 {};
}
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
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;
}
std::string_view CheatParser::ExtractName(std::size_t& out_name_size, std::string_view data,
std::size_t start_index, char match) const {
auto end_index = start_index;
while (data[end_index] != match) {
++end_index;
if (end_index > data.size()) {
return {};
}
}
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);
}
return data.substr(start_index, end_index - start_index);
}
} // namespace Service::DMNT

View file

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string_view>
#include <vector>
namespace Service::DMNT {
struct CheatEntry;
class CheatParser final {
public:
CheatParser();
~CheatParser();
std::vector<CheatEntry> Parse(std::string_view data) const;
private:
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
std::size_t start_index, char match) const;
};
} // namespace Service::DMNT

View file

@ -0,0 +1,597 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/arm/debug.h"
#include "core/core_timing.h"
#include "core/hle/service/cmif_serialization.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
#include "core/hle/service/dmnt/dmnt_results.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/sm/sm.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
namespace Service::DMNT {
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
CheatProcessManager::CheatProcessManager(Core::System& system_)
: system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} {
update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback",
[this](s64 time, std::chrono::nanoseconds ns_late)
-> std::optional<std::chrono::nanoseconds> {
FrameCallback(ns_late);
return std::nullopt;
});
for (size_t i = 0; i < MaxCheatCount; i++) {
ResetCheatEntry(i);
}
cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent");
}
CheatProcessManager::~CheatProcessManager() {
service_context.CloseEvent(cheat_process_event);
service_context.CloseEvent(unsafe_break_event);
core_timing.UnscheduleEvent(update_event);
}
void CheatProcessManager::SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm) {
cheat_vm = std::move(vm);
SetNeedsReloadVm(true);
}
bool CheatProcessManager::HasActiveCheatProcess() {
// Note: This function *MUST* be called only with the cheat lock held.
bool has_cheat_process =
cheat_process_debug_handle != InvalidHandle &&
system.ApplicationProcess()->GetProcessId() == cheat_process_metadata.process_id;
if (!has_cheat_process) {
CloseActiveCheatProcess();
}
return has_cheat_process;
}
void CheatProcessManager::CloseActiveCheatProcess() {
if (cheat_process_debug_handle != InvalidHandle) {
// We don't need to do any unsafe breaking.
broken_unsafe = false;
unsafe_break_event->Signal();
core_timing.UnscheduleEvent(update_event);
// Close resources.
cheat_process_debug_handle = InvalidHandle;
// Save cheat toggles.
if (always_save_cheat_toggles || should_save_cheat_toggles) {
// TODO: save cheat toggles
should_save_cheat_toggles = false;
}
// Clear metadata.
cheat_process_metadata = {};
// Clear cheat list.
ResetAllCheatEntries();
// Clear frozen addresses.
{
auto it = frozen_addresses_map.begin();
while (it != frozen_addresses_map.end()) {
it = frozen_addresses_map.erase(it);
}
}
// Signal to our fans.
cheat_process_event->Signal();
}
}
Result CheatProcessManager::EnsureCheatProcess() {
R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached);
R_SUCCEED();
}
void CheatProcessManager::SetNeedsReloadVm(bool reload) {
needs_reload_vm = reload;
}
void CheatProcessManager::ResetCheatEntry(size_t i) {
if (i < MaxCheatCount) {
cheat_entries[i] = {};
cheat_entries[i].cheat_id = static_cast<u32>(i);
SetNeedsReloadVm(true);
}
}
void CheatProcessManager::ResetAllCheatEntries() {
for (size_t i = 0; i < MaxCheatCount; i++) {
ResetCheatEntry(i);
}
cheat_vm->ResetStaticRegisters();
}
CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) {
if (i < MaxCheatCount) {
return cheat_entries.data() + i;
}
return nullptr;
}
CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) {
// Check all non-master cheats for match.
for (size_t i = 1; i < MaxCheatCount; i++) {
if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name,
sizeof(cheat_entries[i].definition.readable_name)) == 0) {
return cheat_entries.data() + i;
}
}
return nullptr;
}
CheatEntry* CheatProcessManager::GetFreeCheatEntry() {
// Check all non-master cheats for availability.
for (size_t i = 1; i < MaxCheatCount; i++) {
if (cheat_entries[i].definition.num_opcodes == 0) {
return cheat_entries.data() + i;
}
}
return nullptr;
}
bool CheatProcessManager::HasCheatProcess() {
std::scoped_lock lk(cheat_lock);
return HasActiveCheatProcess();
}
Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const {
return cheat_process_event->GetReadableEvent();
}
Result CheatProcessManager::AttachToApplicationProcess(const std::array<u8, 0x20>& build_id,
VAddr main_region_begin,
u64 main_region_size) {
std::scoped_lock lk(cheat_lock);
// Close the active process, if needed.
{
if (this->HasActiveCheatProcess()) {
// When forcing attach, we're done.
this->CloseActiveCheatProcess();
}
}
// Get the application process's ID.
cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId();
// Get process handle, use it to learn memory extents.
{
const auto& page_table = system.ApplicationProcess()->GetPageTable();
cheat_process_metadata.program_id = system.GetApplicationProcessProgramID();
cheat_process_metadata.heap_extents = {
.base = GetInteger(page_table.GetHeapRegionStart()),
.size = page_table.GetHeapRegionSize(),
};
cheat_process_metadata.aslr_extents = {
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
.size = page_table.GetAliasCodeRegionSize(),
};
cheat_process_metadata.alias_extents = {
.base = GetInteger(page_table.GetAliasRegionStart()),
.size = page_table.GetAliasRegionSize(),
};
}
// Get module information from loader.
{
cheat_process_metadata.main_nso_extents = {
.base = main_region_begin,
.size = main_region_size,
};
cheat_process_metadata.main_nso_build_id = build_id;
}
// Set our debug handle.
cheat_process_debug_handle = cheat_process_metadata.process_id;
// Reset broken state.
broken_unsafe = false;
unsafe_break_event->Signal();
// start the process.
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event);
// Signal to our fans.
cheat_process_event->Signal();
R_SUCCEED();
}
Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
out_metadata = cheat_process_metadata;
R_SUCCEED();
}
Result CheatProcessManager::ForceOpenCheatProcess() {
// R_RETURN(AttachToApplicationProcess(false));
R_SUCCEED();
}
Result CheatProcessManager::PauseCheatProcess() {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_RETURN(PauseCheatProcessUnsafe());
}
Result CheatProcessManager::PauseCheatProcessUnsafe() {
broken_unsafe = true;
unsafe_break_event->Clear();
if (system.ApplicationProcess()->IsSuspended()) {
R_SUCCEED();
}
R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused));
}
Result CheatProcessManager::ResumeCheatProcess() {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_RETURN(ResumeCheatProcessUnsafe());
}
Result CheatProcessManager::ResumeCheatProcessUnsafe() {
broken_unsafe = true;
unsafe_break_event->Clear();
if (!system.ApplicationProcess()->IsSuspended()) {
R_SUCCEED();
}
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable);
R_SUCCEED();
}
Result CheatProcessManager::ForceCloseCheatProcess() {
CloseActiveCheatProcess();
R_SUCCEED();
}
Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) {
std::scoped_lock lk(cheat_lock);
R_TRY(this->EnsureCheatProcess());
// TODO: Call svc::QueryDebugProcessMemory
out_count = 0;
R_SUCCEED();
}
Result CheatProcessManager::GetCheatProcessMappings(
u64& out_count, u64 offset, std::span<Kernel::Svc::MemoryInfo> out_mappings) {
std::scoped_lock lk(cheat_lock);
R_TRY(this->EnsureCheatProcess());
// TODO: Call svc::QueryDebugProcessMemory
out_count = 0;
R_SUCCEED();
}
Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size,
std::span<u8> out_data) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size));
}
Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data,
size_t size) {
// Return zero on invalid address
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
std::memset(out_data, 0, size);
R_SUCCEED();
}
system.ApplicationMemory().ReadBlock(process_address, out_data, size);
R_SUCCEED();
}
Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size,
std::span<const u8> data) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size));
}
Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data,
size_t size) {
// Skip invalid memory write address
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) {
R_SUCCEED();
}
if (system.ApplicationMemory().WriteBlock(process_address, data, size)) {
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size);
}
R_SUCCEED();
}
Result CheatProcessManager::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping,
u64 address) {
std::scoped_lock lk(cheat_lock);
R_TRY(this->EnsureCheatProcess());
// TODO: Call svc::QueryDebugProcessMemory
R_SUCCEED();
}
Result CheatProcessManager::GetCheatCount(u64& out_count) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(),
[](const auto& entry) { return entry.definition.num_opcodes != 0; });
R_SUCCEED();
}
Result CheatProcessManager::GetCheats(u64& out_count, u64 offset,
std::span<CheatEntry> out_cheats) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
size_t count = 0, total_count = 0;
for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) {
if (cheat_entries[i].definition.num_opcodes) {
total_count++;
if (total_count > offset) {
out_cheats[count++] = cheat_entries[i];
}
}
}
out_count = count;
R_SUCCEED();
}
Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
const CheatEntry* entry = GetCheatEntryById(cheat_id);
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
*out_cheat = *entry;
R_SUCCEED();
}
Result CheatProcessManager::ToggleCheat(u32 cheat_id) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
CheatEntry* entry = GetCheatEntryById(cheat_id);
R_UNLESS(entry != nullptr, ResultCheatUnknownId);
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId);
R_UNLESS(cheat_id != 0, ResultCheatCannotDisable);
entry->enabled = !entry->enabled;
// Trigger a VM reload.
SetNeedsReloadVm(true);
R_SUCCEED();
}
Result CheatProcessManager::AddCheat(u32& out_cheat_id, bool enabled,
const CheatDefinition& cheat_definition) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
CheatEntry* new_entry = GetFreeCheatEntry();
R_UNLESS(new_entry != nullptr, ResultCheatOutOfResource);
new_entry->enabled = enabled;
new_entry->definition = cheat_definition;
// Trigger a VM reload.
SetNeedsReloadVm(true);
// Set output id.
out_cheat_id = new_entry->cheat_id;
R_SUCCEED();
}
Result CheatProcessManager::RemoveCheat(u32 cheat_id) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_UNLESS(cheat_id < MaxCheatCount, ResultCheatUnknownId);
ResetCheatEntry(cheat_id);
SetNeedsReloadVm(true);
R_SUCCEED();
}
Result CheatProcessManager::ReadStaticRegister(u64& out_value, u64 register_index) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
out_value = cheat_vm->GetStaticRegister(register_index);
R_SUCCEED();
}
Result CheatProcessManager::WriteStaticRegister(u64 register_index, u64 value) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_UNLESS(register_index < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid);
cheat_vm->SetStaticRegister(register_index, value);
R_SUCCEED();
}
Result CheatProcessManager::ResetStaticRegisters() {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
cheat_vm->ResetStaticRegisters();
R_SUCCEED();
}
Result CheatProcessManager::SetMasterCheat(const CheatDefinition& cheat_definition) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
R_UNLESS(cheat_definition.num_opcodes != 0, ResultCheatInvalid);
R_UNLESS(cheat_definition.num_opcodes <= cheat_definition.opcodes.size(), ResultCheatInvalid);
cheat_entries[0] = {
.enabled = true,
.definition = cheat_definition,
};
// Trigger a VM reload.
SetNeedsReloadVm(true);
R_SUCCEED();
}
Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end());
R_SUCCEED();
}
Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset,
std::span<FrozenAddressEntry> out_frozen_address) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
u64 total_count = 0, written_count = 0;
for (const auto& [address, value] : frozen_addresses_map) {
if (written_count >= out_frozen_address.size()) {
break;
}
if (offset <= total_count) {
out_frozen_address[written_count].address = address;
out_frozen_address[written_count].value = value;
written_count++;
}
total_count++;
}
out_count = written_count;
R_SUCCEED();
}
Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry,
u64 address) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
const auto it = frozen_addresses_map.find(address);
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
out_frozen_address_entry = {
.address = it->first,
.value = it->second,
};
R_SUCCEED();
}
Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
const auto it = frozen_addresses_map.find(address);
R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists);
FrozenAddressValue value{};
value.width = static_cast<u8>(width);
R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width));
frozen_addresses_map.insert({address, value});
out_value = value.value;
R_SUCCEED();
}
Result CheatProcessManager::DisableFrozenAddress(u64 address) {
std::scoped_lock lk(cheat_lock);
R_TRY(EnsureCheatProcess());
const auto it = frozen_addresses_map.find(address);
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound);
frozen_addresses_map.erase(it);
R_SUCCEED();
}
u64 CheatProcessManager::HidKeysDown() const {
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
if (hid == nullptr) {
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
return 0;
}
const auto applet_resource = hid->GetResourceManager();
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
LOG_WARNING(CheatEngine,
"Attempted to read input state, but applet resource is not initialized!");
return 0;
}
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
return static_cast<u64>(press_state & Core::HID::NpadButton::All);
}
void CheatProcessManager::DebugLog(u8 id, u64 value) const {
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value);
}
void CheatProcessManager::CommandLog(std::string_view data) const {
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}",
data.back() == '\n' ? data.substr(0, data.size() - 1) : data);
}
void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) {
std::scoped_lock lk(cheat_lock);
if (cheat_vm == nullptr) {
return;
}
if (needs_reload_vm) {
cheat_vm->LoadProgram(cheat_entries);
needs_reload_vm = false;
}
if (cheat_vm->GetProgramSize() == 0) {
return;
}
cheat_vm->Execute(cheat_process_metadata);
}
} // namespace Service::DMNT

View file

@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <map>
#include <span>
#include "core/hle/service/cmif_types.h"
#include "core/hle/service/dmnt/dmnt_types.h"
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/service.h"
#include "common/intrusive_red_black_tree.h"
namespace Core {
class System;
}
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Kernel::Svc {
struct MemoryInfo;
}
namespace Service::DMNT {
class CheatVirtualMachine;
class CheatProcessManager final {
public:
static constexpr size_t MaxCheatCount = 0x80;
static constexpr size_t MaxFrozenAddressCount = 0x80;
CheatProcessManager(Core::System& system_);
~CheatProcessManager();
void SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm);
bool HasCheatProcess();
Kernel::KReadableEvent& GetCheatProcessEvent() const;
Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata);
Result AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, VAddr main_region_begin,
u64 main_region_size);
Result ForceOpenCheatProcess();
Result PauseCheatProcess();
Result PauseCheatProcessUnsafe();
Result ResumeCheatProcess();
Result ResumeCheatProcessUnsafe();
Result ForceCloseCheatProcess();
Result GetCheatProcessMappingCount(u64& out_count);
Result GetCheatProcessMappings(u64& out_count, u64 offset,
std::span<Kernel::Svc::MemoryInfo> out_mappings);
Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span<u8> out_data);
Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size);
Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span<const u8> data);
Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size);
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, u64 address);
Result GetCheatCount(u64& out_count);
Result GetCheats(u64& out_count, u64 offset, std::span<CheatEntry> out_cheats);
Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id);
Result ToggleCheat(u32 cheat_id);
Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition);
Result RemoveCheat(u32 cheat_id);
Result ReadStaticRegister(u64& out_value, u64 register_index);
Result WriteStaticRegister(u64 register_index, u64 value);
Result ResetStaticRegisters();
Result SetMasterCheat(const CheatDefinition& cheat_definition);
Result GetFrozenAddressCount(u64& out_count);
Result GetFrozenAddresses(u64& out_count, u64 offset,
std::span<FrozenAddressEntry> out_frozen_address);
Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address);
Result EnableFrozenAddress(u64& out_value, u64 address, u64 width);
Result DisableFrozenAddress(u64 address);
u64 HidKeysDown() const;
void DebugLog(u8 id, u64 value) const;
void CommandLog(std::string_view data) const;
private:
bool HasActiveCheatProcess();
void CloseActiveCheatProcess();
Result EnsureCheatProcess();
void SetNeedsReloadVm(bool reload);
void ResetCheatEntry(size_t i);
void ResetAllCheatEntries();
CheatEntry* GetCheatEntryById(size_t i);
CheatEntry* GetCheatEntryByReadableName(const char* readable_name);
CheatEntry* GetFreeCheatEntry();
void FrameCallback(std::chrono::nanoseconds ns_late);
static constexpr u64 InvalidHandle = 0;
mutable std::mutex cheat_lock;
Kernel::KEvent* unsafe_break_event;
Kernel::KEvent* cheat_process_event;
u64 cheat_process_debug_handle = InvalidHandle;
CheatProcessMetadata cheat_process_metadata = {};
bool broken_unsafe = false;
bool needs_reload_vm = false;
std::unique_ptr<CheatVirtualMachine> cheat_vm;
bool enable_cheats_by_default = true;
bool always_save_cheat_toggles = false;
bool should_save_cheat_toggles = false;
std::array<CheatEntry, MaxCheatCount> cheat_entries = {};
// TODO: Replace with IntrusiveRedBlackTree
std::map<u64, FrozenAddressValue> frozen_addresses_map = {};
Core::System& system;
KernelHelpers::ServiceContext service_context;
std::shared_ptr<Core::Timing::EventType> update_event;
Core::Timing::CoreTiming& core_timing;
};
} // namespace Service::DMNT

View file

@ -1,226 +1,221 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fmt/printf.h>
#include "common/assert.h"
#include "common/scope_exit.h"
#include "core/memory/dmnt_cheat_types.h"
#include "core/memory/dmnt_cheat_vm.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
namespace Core::Memory {
namespace Service::DMNT {
DmntCheatVm::DmntCheatVm(std::unique_ptr<Callbacks> callbacks_)
: callbacks(std::move(callbacks_)) {}
CheatVirtualMachine::CheatVirtualMachine(CheatProcessManager& cheat_manager)
: manager(cheat_manager) {}
DmntCheatVm::~DmntCheatVm() = default;
CheatVirtualMachine::~CheatVirtualMachine() = default;
void DmntCheatVm::DebugLog(u32 log_id, u64 value) {
callbacks->DebugLog(static_cast<u8>(log_id), value);
void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) const {
manager.DebugLog(static_cast<u8>(log_id), value);
}
void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) {
void CheatVirtualMachine::LogOpcode(const CheatVmOpcode& opcode) const {
if (auto store_static = std::get_if<StoreStaticOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Store Static");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
callbacks->CommandLog(
manager.CommandLog("Opcode: Store Static");
manager.CommandLog(fmt::format("Bit Width: {:X}", store_static->bit_width));
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(store_static->mem_type)));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
callbacks->CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
manager.CommandLog(fmt::format("Reg Idx: {:X}", store_static->offset_register));
manager.CommandLog(fmt::format("Rel Addr: {:X}", store_static->rel_address));
manager.CommandLog(fmt::format("Value: {:X}", store_static->value.bit64));
} else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Begin Conditional");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
callbacks->CommandLog(
fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
callbacks->CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
manager.CommandLog("Opcode: Begin Conditional");
manager.CommandLog(fmt::format("Bit Width: {:X}", begin_cond->bit_width));
manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast<u32>(begin_cond->mem_type)));
manager.CommandLog(fmt::format("Cond Type: {:X}", static_cast<u32>(begin_cond->cond_type)));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_cond->rel_address));
manager.CommandLog(fmt::format("Value: {:X}", begin_cond->value.bit64));
} else if (std::holds_alternative<EndConditionalOpcode>(opcode.opcode)) {
callbacks->CommandLog("Opcode: End Conditional");
manager.CommandLog("Opcode: End Conditional");
} else if (auto ctrl_loop = std::get_if<ControlLoopOpcode>(&opcode.opcode)) {
if (ctrl_loop->start_loop) {
callbacks->CommandLog("Opcode: Start Loop");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
callbacks->CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
manager.CommandLog("Opcode: Start Loop");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
manager.CommandLog(fmt::format("Num Iters: {:X}", ctrl_loop->num_iters));
} else {
callbacks->CommandLog("Opcode: End Loop");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
manager.CommandLog("Opcode: End Loop");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ctrl_loop->reg_index));
}
} else if (auto ldr_static = std::get_if<LoadRegisterStaticOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Load Register Static");
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
callbacks->CommandLog(fmt::format("Value: {:X}", ldr_static->value));
manager.CommandLog("Opcode: Load Register Static");
manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_static->reg_index));
manager.CommandLog(fmt::format("Value: {:X}", ldr_static->value));
} else if (auto ldr_memory = std::get_if<LoadRegisterMemoryOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Load Register Memory");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
callbacks->CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
callbacks->CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
manager.CommandLog("Opcode: Load Register Memory");
manager.CommandLog(fmt::format("Bit Width: {:X}", ldr_memory->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", ldr_memory->reg_index));
manager.CommandLog(fmt::format("Mem Type: {:X}", static_cast<u32>(ldr_memory->mem_type)));
manager.CommandLog(fmt::format("From Reg: {:d}", ldr_memory->load_from_reg));
manager.CommandLog(fmt::format("Rel Addr: {:X}", ldr_memory->rel_address));
} else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Store Static to Address");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
manager.CommandLog("Opcode: Store Static to Address");
manager.CommandLog(fmt::format("Bit Width: {:X}", str_static->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", str_static->reg_index));
if (str_static->add_offset_reg) {
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_static->offset_reg_index));
}
callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
callbacks->CommandLog(fmt::format("Value: {:X}", str_static->value));
manager.CommandLog(fmt::format("Incr Reg: {:d}", str_static->increment_reg));
manager.CommandLog(fmt::format("Value: {:X}", str_static->value));
} else if (auto perform_math_static =
std::get_if<PerformArithmeticStaticOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Perform Static Arithmetic");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
callbacks->CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
callbacks->CommandLog(
manager.CommandLog("Opcode: Perform Static Arithmetic");
manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_static->bit_width));
manager.CommandLog(fmt::format("Reg Idx: {:X}", perform_math_static->reg_index));
manager.CommandLog(
fmt::format("Math Type: {:X}", static_cast<u32>(perform_math_static->math_type)));
callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
manager.CommandLog(fmt::format("Value: {:X}", perform_math_static->value));
} else if (auto begin_keypress_cond =
std::get_if<BeginKeypressConditionalOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Begin Keypress Conditional");
callbacks->CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
manager.CommandLog("Opcode: Begin Keypress Conditional");
manager.CommandLog(fmt::format("Key Mask: {:X}", begin_keypress_cond->key_mask));
} else if (auto perform_math_reg =
std::get_if<PerformArithmeticRegisterOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Perform Register Arithmetic");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
callbacks->CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
callbacks->CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
manager.CommandLog("Opcode: Perform Register Arithmetic");
manager.CommandLog(fmt::format("Bit Width: {:X}", perform_math_reg->bit_width));
manager.CommandLog(fmt::format("Dst Idx: {:X}", perform_math_reg->dst_reg_index));
manager.CommandLog(fmt::format("Src1 Idx: {:X}", perform_math_reg->src_reg_1_index));
if (perform_math_reg->has_immediate) {
callbacks->CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
manager.CommandLog(fmt::format("Value: {:X}", perform_math_reg->value.bit64));
} else {
callbacks->CommandLog(
fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
manager.CommandLog(fmt::format("Src2 Idx: {:X}", perform_math_reg->src_reg_2_index));
}
} else if (auto str_register = std::get_if<StoreRegisterToAddressOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Store Register to Address");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
callbacks->CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
callbacks->CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
manager.CommandLog("Opcode: Store Register to Address");
manager.CommandLog(fmt::format("Bit Width: {:X}", str_register->bit_width));
manager.CommandLog(fmt::format("S Reg Idx: {:X}", str_register->str_reg_index));
manager.CommandLog(fmt::format("A Reg Idx: {:X}", str_register->addr_reg_index));
manager.CommandLog(fmt::format("Incr Reg: {:d}", str_register->increment_reg));
switch (str_register->ofs_type) {
case StoreRegisterOffsetType::None:
break;
case StoreRegisterOffsetType::Reg:
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", str_register->ofs_reg_index));
break;
case StoreRegisterOffsetType::Imm:
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
break;
case StoreRegisterOffsetType::MemReg:
callbacks->CommandLog(
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
break;
case StoreRegisterOffsetType::MemImm:
case StoreRegisterOffsetType::MemImmReg:
callbacks->CommandLog(
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(str_register->mem_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
manager.CommandLog(fmt::format("Rel Addr: {:X}", str_register->rel_address));
break;
}
} else if (auto begin_reg_cond = std::get_if<BeginRegisterConditionalOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Begin Register Conditional");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
callbacks->CommandLog(
manager.CommandLog("Opcode: Begin Register Conditional");
manager.CommandLog(fmt::format("Bit Width: {:X}", begin_reg_cond->bit_width));
manager.CommandLog(
fmt::format("Cond Type: {:X}", static_cast<u32>(begin_reg_cond->cond_type)));
callbacks->CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
manager.CommandLog(fmt::format("V Reg Idx: {:X}", begin_reg_cond->val_reg_index));
switch (begin_reg_cond->comp_type) {
case CompareRegisterValueType::StaticValue:
callbacks->CommandLog("Comp Type: Static Value");
callbacks->CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
manager.CommandLog("Comp Type: Static Value");
manager.CommandLog(fmt::format("Value: {:X}", begin_reg_cond->value.bit64));
break;
case CompareRegisterValueType::OtherRegister:
callbacks->CommandLog("Comp Type: Other Register");
callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
manager.CommandLog("Comp Type: Other Register");
manager.CommandLog(fmt::format("X Reg Idx: {:X}", begin_reg_cond->other_reg_index));
break;
case CompareRegisterValueType::MemoryRelAddr:
callbacks->CommandLog("Comp Type: Memory Relative Address");
callbacks->CommandLog(
manager.CommandLog("Comp Type: Memory Relative Address");
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
break;
case CompareRegisterValueType::MemoryOfsReg:
callbacks->CommandLog("Comp Type: Memory Offset Register");
callbacks->CommandLog(
manager.CommandLog("Comp Type: Memory Offset Register");
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(begin_reg_cond->mem_type)));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
break;
case CompareRegisterValueType::RegisterRelAddr:
callbacks->CommandLog("Comp Type: Register Relative Address");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
manager.CommandLog("Comp Type: Register Relative Address");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
manager.CommandLog(fmt::format("Rel Addr: {:X}", begin_reg_cond->rel_address));
break;
case CompareRegisterValueType::RegisterOfsReg:
callbacks->CommandLog("Comp Type: Register Offset Register");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
manager.CommandLog("Comp Type: Register Offset Register");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", begin_reg_cond->addr_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", begin_reg_cond->ofs_reg_index));
break;
}
} else if (auto save_restore_reg = std::get_if<SaveRestoreRegisterOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Save or Restore Register");
callbacks->CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
callbacks->CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
callbacks->CommandLog(
manager.CommandLog("Opcode: Save or Restore Register");
manager.CommandLog(fmt::format("Dst Idx: {:X}", save_restore_reg->dst_index));
manager.CommandLog(fmt::format("Src Idx: {:X}", save_restore_reg->src_index));
manager.CommandLog(
fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_reg->op_type)));
} else if (auto save_restore_regmask =
std::get_if<SaveRestoreRegisterMaskOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Save or Restore Register Mask");
callbacks->CommandLog(
manager.CommandLog("Opcode: Save or Restore Register Mask");
manager.CommandLog(
fmt::format("Op Type: {:d}", static_cast<u32>(save_restore_regmask->op_type)));
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(
manager.CommandLog(
fmt::format("Act[{:02X}]: {:d}", i, save_restore_regmask->should_operate[i]));
}
} else if (auto rw_static_reg = std::get_if<ReadWriteStaticRegisterOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Read/Write Static Register");
manager.CommandLog("Opcode: Read/Write Static Register");
if (rw_static_reg->static_idx < NumReadableStaticRegisters) {
callbacks->CommandLog("Op Type: ReadStaticRegister");
manager.CommandLog("Op Type: ReadStaticRegister");
} else {
callbacks->CommandLog("Op Type: WriteStaticRegister");
manager.CommandLog("Op Type: WriteStaticRegister");
}
callbacks->CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx));
callbacks->CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx));
manager.CommandLog(fmt::format("Reg Idx {:X}", rw_static_reg->idx));
manager.CommandLog(fmt::format("Stc Idx {:X}", rw_static_reg->static_idx));
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&opcode.opcode)) {
callbacks->CommandLog("Opcode: Debug Log");
callbacks->CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
callbacks->CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
callbacks->CommandLog(
fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
manager.CommandLog("Opcode: Debug Log");
manager.CommandLog(fmt::format("Bit Width: {:X}", debug_log->bit_width));
manager.CommandLog(fmt::format("Log ID: {:X}", debug_log->log_id));
manager.CommandLog(fmt::format("Val Type: {:X}", static_cast<u32>(debug_log->val_type)));
switch (debug_log->val_type) {
case DebugLogValueType::RegisterValue:
callbacks->CommandLog("Val Type: Register Value");
callbacks->CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
manager.CommandLog("Val Type: Register Value");
manager.CommandLog(fmt::format("X Reg Idx: {:X}", debug_log->val_reg_index));
break;
case DebugLogValueType::MemoryRelAddr:
callbacks->CommandLog("Val Type: Memory Relative Address");
callbacks->CommandLog(
manager.CommandLog("Val Type: Memory Relative Address");
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
break;
case DebugLogValueType::MemoryOfsReg:
callbacks->CommandLog("Val Type: Memory Offset Register");
callbacks->CommandLog(
manager.CommandLog("Val Type: Memory Offset Register");
manager.CommandLog(
fmt::format("Mem Type: {:X}", static_cast<u32>(debug_log->mem_type)));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
break;
case DebugLogValueType::RegisterRelAddr:
callbacks->CommandLog("Val Type: Register Relative Address");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
callbacks->CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
manager.CommandLog("Val Type: Register Relative Address");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
manager.CommandLog(fmt::format("Rel Addr: {:X}", debug_log->rel_address));
break;
case DebugLogValueType::RegisterOfsReg:
callbacks->CommandLog("Val Type: Register Offset Register");
callbacks->CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
callbacks->CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
manager.CommandLog("Val Type: Register Offset Register");
manager.CommandLog(fmt::format("A Reg Idx: {:X}", debug_log->addr_reg_index));
manager.CommandLog(fmt::format("O Reg Idx: {:X}", debug_log->ofs_reg_index));
break;
}
} else if (auto instr = std::get_if<UnrecognizedInstruction>(&opcode.opcode)) {
callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
manager.CommandLog(fmt::format("Unknown opcode: {:X}", static_cast<u32>(instr->opcode)));
}
}
DmntCheatVm::Callbacks::~Callbacks() = default;
bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode& out) {
// If we've ever seen a decode failure, return false.
bool valid = decode_success;
CheatVmOpcode opcode = {};
@ -634,7 +629,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode& out) {
return valid;
}
void DmntCheatVm::SkipConditionalBlock(bool is_if) {
void CheatVirtualMachine::SkipConditionalBlock(bool is_if) {
if (condition_depth > 0) {
// We want to continue until we're out of the current block.
const std::size_t desired_depth = condition_depth - 1;
@ -668,7 +663,7 @@ void DmntCheatVm::SkipConditionalBlock(bool is_if) {
}
}
u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) {
switch (bit_width) {
case 1:
return value.bit8;
@ -684,8 +679,8 @@ u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) {
}
}
u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address) {
u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address) {
switch (mem_type) {
case MemoryAccessType::MainNso:
default:
@ -699,7 +694,7 @@ u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata& metadata,
}
}
void DmntCheatVm::ResetState() {
void CheatVirtualMachine::ResetState() {
registers.fill(0);
saved_values.fill(0);
loop_tops.fill(0);
@ -708,7 +703,7 @@ void DmntCheatVm::ResetState() {
decode_success = true;
}
bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
bool CheatVirtualMachine::LoadProgram(std::span<const CheatEntry> entries) {
// Reset opcode count.
num_opcodes = 0;
@ -729,31 +724,31 @@ bool DmntCheatVm::LoadProgram(const std::vector<CheatEntry>& entries) {
return true;
}
void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
void CheatVirtualMachine::Execute(const CheatProcessMetadata& metadata) {
CheatVmOpcode cur_opcode{};
// Get Keys down.
u64 kDown = callbacks->HidKeysDown();
u64 kDown = manager.HidKeysDown();
callbacks->CommandLog("Started VM execution.");
callbacks->CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
callbacks->CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
manager.CommandLog("Started VM execution.");
manager.CommandLog(fmt::format("Main NSO: {:012X}", metadata.main_nso_extents.base));
manager.CommandLog(fmt::format("Heap: {:012X}", metadata.main_nso_extents.base));
manager.CommandLog(fmt::format("Keys Down: {:08X}", static_cast<u32>(kDown & 0x0FFFFFFF)));
// Clear VM state.
ResetState();
// Loop until program finishes.
while (DecodeNextOpcode(cur_opcode)) {
callbacks->CommandLog(
manager.CommandLog(
fmt::format("Instruction Ptr: {:04X}", static_cast<u32>(instruction_ptr)));
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
manager.CommandLog(fmt::format("Registers[{:02X}]: {:016X}", i, registers[i]));
}
for (std::size_t i = 0; i < NumRegisters; i++) {
callbacks->CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
manager.CommandLog(fmt::format("SavedRegs[{:02X}]: {:016X}", i, saved_values[i]));
}
LogOpcode(cur_opcode);
@ -773,7 +768,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, store_static->bit_width);
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
store_static->bit_width);
break;
}
} else if (auto begin_cond = std::get_if<BeginConditionalOpcode>(&cur_opcode.opcode)) {
@ -786,7 +782,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryReadUnsafe(src_address, &src_value, begin_cond->bit_width);
manager.ReadCheatProcessMemoryUnsafe(src_address, &src_value,
begin_cond->bit_width);
break;
}
// Check against condition.
@ -857,8 +854,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryReadUnsafe(src_address, &registers[ldr_memory->reg_index],
ldr_memory->bit_width);
manager.ReadCheatProcessMemoryUnsafe(src_address, &registers[ldr_memory->reg_index],
ldr_memory->bit_width);
break;
}
} else if (auto str_static = std::get_if<StoreStaticToAddressOpcode>(&cur_opcode.opcode)) {
@ -874,7 +871,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_static->bit_width);
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
str_static->bit_width);
break;
}
// Increment register if relevant.
@ -1032,7 +1030,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryWriteUnsafe(dst_address, &dst_value, str_register->bit_width);
manager.WriteCheatProcessMemoryUnsafe(dst_address, &dst_value,
str_register->bit_width);
break;
}
@ -1111,8 +1110,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryReadUnsafe(cond_address, &cond_value,
begin_reg_cond->bit_width);
manager.ReadCheatProcessMemoryUnsafe(cond_address, &cond_value,
begin_reg_cond->bit_width);
break;
}
}
@ -1205,9 +1204,9 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
static_registers[rw_static_reg->static_idx] = registers[rw_static_reg->idx];
}
} else if (std::holds_alternative<PauseProcessOpcode>(cur_opcode.opcode)) {
callbacks->PauseProcess();
manager.PauseCheatProcessUnsafe();
} else if (std::holds_alternative<ResumeProcessOpcode>(cur_opcode.opcode)) {
callbacks->ResumeProcess();
manager.ResumeCheatProcessUnsafe();
} else if (auto debug_log = std::get_if<DebugLogOpcode>(&cur_opcode.opcode)) {
// Read value from memory.
u64 log_value = 0;
@ -1254,7 +1253,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
case 2:
case 4:
case 8:
callbacks->MemoryReadUnsafe(val_address, &log_value, debug_log->bit_width);
manager.ReadCheatProcessMemoryUnsafe(val_address, &log_value,
debug_log->bit_width);
break;
}
}
@ -1265,4 +1265,4 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
}
}
} // namespace Core::Memory
} // namespace Service::DMNT

View file

@ -3,13 +3,14 @@
#pragma once
#include <span>
#include <variant>
#include <vector>
#include <fmt/printf.h>
#include "common/common_types.h"
#include "core/memory/dmnt_cheat_types.h"
namespace Core::Memory {
#include "common/common_types.h"
#include "core/hle/service/dmnt/dmnt_types.h"
namespace Service::DMNT {
class CheatProcessManager;
enum class CheatVmOpcodeType : u32 {
StoreStatic = 0,
@ -259,25 +260,8 @@ struct CheatVmOpcode {
opcode{};
};
class DmntCheatVm {
class CheatVirtualMachine {
public:
/// Helper Type for DmntCheatVm <=> yuzu Interface
class Callbacks {
public:
virtual ~Callbacks();
virtual void MemoryReadUnsafe(VAddr address, void* data, u64 size) = 0;
virtual void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) = 0;
virtual u64 HidKeysDown() = 0;
virtual void PauseProcess() = 0;
virtual void ResumeProcess() = 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;
static constexpr std::size_t NumReadableStaticRegisters = 0x80;
@ -285,18 +269,43 @@ public:
static constexpr std::size_t NumStaticRegisters =
NumReadableStaticRegisters + NumWritableStaticRegisters;
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_);
~DmntCheatVm();
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager);
~CheatVirtualMachine();
std::size_t GetProgramSize() const {
return this->num_opcodes;
}
bool LoadProgram(const std::vector<CheatEntry>& cheats);
bool LoadProgram(std::span<const CheatEntry> cheats);
void Execute(const CheatProcessMetadata& metadata);
u64 GetStaticRegister(std::size_t register_index) const {
return static_registers[register_index];
}
void SetStaticRegister(std::size_t register_index, u64 value) {
static_registers[register_index] = value;
}
void ResetStaticRegisters() {
static_registers = {};
}
private:
std::unique_ptr<Callbacks> callbacks;
bool DecodeNextOpcode(CheatVmOpcode& out);
void SkipConditionalBlock(bool is_if);
void ResetState();
// For implementing the DebugLog opcode.
void DebugLog(u32 log_id, u64 value) const;
void LogOpcode(const CheatVmOpcode& opcode) const;
static u64 GetVmInt(VmInt value, u32 bit_width);
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata,
MemoryAccessType mem_type, u64 rel_address);
CheatProcessManager& manager;
std::size_t num_opcodes = 0;
std::size_t instruction_ptr = 0;
@ -307,19 +316,6 @@ private:
std::array<u64, NumRegisters> saved_values{};
std::array<u64, NumStaticRegisters> static_registers{};
std::array<std::size_t, NumRegisters> loop_tops{};
bool DecodeNextOpcode(CheatVmOpcode& out);
void SkipConditionalBlock(bool is_if);
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 Core::Memory
}; // namespace Service::DMNT

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "core/core.h"
#include "core/hle/service/dmnt/cheat_interface.h"
#include "core/hle/service/dmnt/cheat_process_manager.h"
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
#include "core/hle/service/dmnt/dmnt.h"
#include "core/hle/service/server_manager.h"
namespace Service::DMNT {
void LoopProcess(Core::System& system) {
auto server_manager = std::make_unique<ServerManager>(system);
auto& cheat_manager = system.GetCheatManager();
auto cheat_vm = std::make_unique<CheatVirtualMachine>(cheat_manager);
cheat_manager.SetVirtualMachine(std::move(cheat_vm));
server_manager->RegisterNamedService("dmnt:cht",
std::make_shared<ICheatInterface>(system, cheat_manager));
ServerManager::RunServer(std::move(server_manager));
}
} // namespace Service::DMNT

View file

@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
namespace Core {
class System;
};
namespace Service::DMNT {
void LoopProcess(Core::System& system);
} // namespace Service::DMNT

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "core/hle/result.h"
namespace Service::DMNT {
constexpr Result ResultDebuggingDisabled(ErrorModule::DMNT, 2);
constexpr Result ResultCheatNotAttached(ErrorModule::DMNT, 6500);
constexpr Result ResultCheatNullBuffer(ErrorModule::DMNT, 6501);
constexpr Result ResultCheatInvalidBuffer(ErrorModule::DMNT, 6502);
constexpr Result ResultCheatUnknownId(ErrorModule::DMNT, 6503);
constexpr Result ResultCheatOutOfResource(ErrorModule::DMNT, 6504);
constexpr Result ResultCheatInvalid(ErrorModule::DMNT, 6505);
constexpr Result ResultCheatCannotDisable(ErrorModule::DMNT, 6506);
constexpr Result ResultFrozenAddressInvalidWidth(ErrorModule::DMNT, 6600);
constexpr Result ResultFrozenAddressAlreadyExists(ErrorModule::DMNT, 6601);
constexpr Result ResultFrozenAddressNotFound(ErrorModule::DMNT, 6602);
constexpr Result ResultFrozenAddressOutOfResource(ErrorModule::DMNT, 6603);
constexpr Result ResultVirtualMachineInvalidConditionDepth(ErrorModule::DMNT, 6700);
} // namespace Service::DMNT

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace Service::DMNT {
struct MemoryRegionExtents {
u64 base{};
u64 size{};
};
static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size");
struct CheatProcessMetadata {
u64 process_id{};
u64 program_id{};
MemoryRegionExtents main_nso_extents{};
MemoryRegionExtents heap_extents{};
MemoryRegionExtents alias_extents{};
MemoryRegionExtents aslr_extents{};
std::array<u8, 0x20> main_nso_build_id{};
};
static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size");
struct CheatDefinition {
std::array<char, 0x40> readable_name;
u32 num_opcodes;
std::array<u32, 0x100> opcodes;
};
static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size");
struct CheatEntry {
bool enabled;
u32 cheat_id;
CheatDefinition definition;
};
static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size");
static_assert(std::is_trivial_v<CheatEntry>, "CheatEntry type must be trivially copyable.");
struct FrozenAddressValue {
u64 value;
u8 width;
};
static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size");
struct FrozenAddressEntry {
u64 address;
FrozenAddressValue value;
};
static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size");
} // namespace Service::DMNT

View file

@ -13,6 +13,7 @@
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/btm/btm.h"
#include "core/hle/service/caps/caps.h"
#include "core/hle/service/dmnt/dmnt.h"
#include "core/hle/service/erpt/erpt.h"
#include "core/hle/service/es/es.h"
#include "core/hle/service/eupld/eupld.h"
@ -128,6 +129,7 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
kernel.RunOnGuestCoreProcess("spl", [&] { SPL::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("ssl", [&] { SSL::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("usb", [&] { USB::LoopProcess(system); });
kernel.RunOnGuestCoreProcess("dmnt", [&] { DMNT::LoopProcess(system); });
// clang-format on
}

View file

@ -1,288 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <locale>
#include "common/hex_util.h"
#include "common/microprofile.h"
#include "common/swap.h"
#include "core/arm/debug.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_process_page_table.h"
#include "core/hle/kernel/svc_types.h"
#include "core/hle/service/hid/hid_server.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
#include "core/memory/cheat_engine.h"
#include "hid_core/resource_manager.h"
#include "hid_core/resources/npad/npad.h"
namespace Core::Memory {
namespace {
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12};
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data,
std::size_t start_index, char match) {
auto end_index = start_index;
while (data[end_index] != match) {
++end_index;
if (end_index > data.size()) {
return {};
}
}
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);
}
return data.substr(start_index, end_index - start_index);
}
} // Anonymous namespace
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_)
: metadata{metadata_}, system{system_} {}
StandardVmCallbacks::~StandardVmCallbacks() = default;
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);
}
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) {
// Skip invalid memory write address
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) {
return;
}
if (system.ApplicationMemory().WriteBlock(address, data, size)) {
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size);
}
}
u64 StandardVmCallbacks::HidKeysDown() {
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid");
if (hid == nullptr) {
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!");
return 0;
}
const auto applet_resource = hid->GetResourceManager();
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) {
LOG_WARNING(CheatEngine,
"Attempted to read input state, but applet resource is not initialized!");
return 0;
}
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState();
return static_cast<u64>(press_state & HID::NpadButton::All);
}
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);
}
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);
}
bool StandardVmCallbacks::IsAddressInRange(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) &&
(in < metadata.alias_extents.base ||
in >= metadata.alias_extents.base + metadata.alias_extents.size) &&
(in < metadata.aslr_extents.base ||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) {
LOG_DEBUG(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 false; ///< Invalid addresses will hard crash
}
return true;
}
CheatParser::~CheatParser() = default;
TextCheatParser::~TextCheatParser() = default;
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const {
std::vector<CheatEntry> out(1);
std::optional<u64> current_entry;
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 {};
}
std::size_t name_size{};
const auto name = ExtractName(name_size, 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_size + 1;
} else if (data[i] == '[') {
current_entry = out.size();
out.emplace_back();
std::size_t name_size{};
const auto name = ExtractName(name_size, 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_size + 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 {};
}
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10));
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] =
value;
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(System& system_, std::vector<CheatEntry> cheats_,
const std::array<u8, 0x20>& build_id_)
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)},
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} {
metadata.main_nso_build_id = build_id_;
}
CheatEngine::~CheatEngine() {
core_timing.UnscheduleEvent(event);
}
void CheatEngine::Initialize() {
event = Core::Timing::CreateEvent(
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id),
[this](s64 time,
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
FrameCallback(ns_late);
return std::nullopt;
});
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event);
metadata.process_id = system.ApplicationProcess()->GetProcessId();
metadata.title_id = system.GetApplicationProcessProgramID();
const auto& page_table = system.ApplicationProcess()->GetPageTable();
metadata.heap_extents = {
.base = GetInteger(page_table.GetHeapRegionStart()),
.size = page_table.GetHeapRegionSize(),
};
metadata.aslr_extents = {
.base = GetInteger(page_table.GetAliasCodeRegionStart()),
.size = page_table.GetAliasCodeRegionSize(),
};
metadata.alias_extents = {
.base = GetInteger(page_table.GetAliasRegionStart()),
.size = page_table.GetAliasRegionSize(),
};
is_pending_reload.exchange(true);
}
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) {
metadata.main_nso_extents = {
.base = main_region_begin,
.size = main_region_size,
};
}
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) {
cheats = std::move(reload_cheats);
is_pending_reload.exchange(true);
}
MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) {
if (is_pending_reload.exchange(false)) {
vm.LoadProgram(cheats);
}
if (vm.GetProgramSize() == 0) {
return;
}
MICROPROFILE_SCOPE(Cheat_Engine);
vm.Execute(metadata);
}
} // namespace Core::Memory

View file

@ -1,88 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <atomic>
#include <chrono>
#include <memory>
#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 Core::Memory {
class StandardVmCallbacks : public DmntCheatVm::Callbacks {
public:
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_);
~StandardVmCallbacks() override;
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override;
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override;
u64 HidKeysDown() override;
void PauseProcess() override;
void ResumeProcess() override;
void DebugLog(u8 id, u64 value) override;
void CommandLog(std::string_view data) override;
private:
bool IsAddressInRange(VAddr address) const;
const CheatProcessMetadata& metadata;
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();
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0;
};
// CheatParser implementation that parses text files
class TextCheatParser final : public CheatParser {
public:
~TextCheatParser() override;
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override;
};
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
class CheatEngine final {
public:
CheatEngine(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> reload_cheats);
private:
void FrameCallback(std::chrono::nanoseconds ns_late);
DmntCheatVm vm;
CheatProcessMetadata metadata;
std::vector<CheatEntry> cheats;
std::atomic_bool is_pending_reload{false};
std::shared_ptr<Core::Timing::EventType> event;
Core::Timing::CoreTiming& core_timing;
Core::System& system;
};
} // namespace Core::Memory

View file

@ -1,37 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/common_types.h"
namespace Core::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 aslr_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 Core::Memory