From 012c0b01573807b4752d3993d0c7868bf37c6da5 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 18 Feb 2024 10:48:12 -0600 Subject: [PATCH] service: dmnt: Implement cheat manager and interface --- src/core/CMakeLists.txt | 17 +- src/core/core.cpp | 39 +- src/core/core.h | 11 +- src/core/file_sys/patch_manager.cpp | 11 +- src/core/file_sys/patch_manager.h | 8 +- src/core/hle/service/dmnt/cheat_interface.cpp | 237 +++++++ src/core/hle/service/dmnt/cheat_interface.h | 86 +++ src/core/hle/service/dmnt/cheat_parser.cpp | 117 ++++ src/core/hle/service/dmnt/cheat_parser.h | 24 + .../service/dmnt/cheat_process_manager.cpp | 597 ++++++++++++++++++ .../hle/service/dmnt/cheat_process_manager.h | 124 ++++ .../service/dmnt/cheat_virtual_machine.cpp} | 302 ++++----- .../service/dmnt/cheat_virtual_machine.h} | 78 ++- src/core/hle/service/dmnt/dmnt.cpp | 25 + src/core/hle/service/dmnt/dmnt.h | 14 + src/core/hle/service/dmnt/dmnt_results.h | 25 + src/core/hle/service/dmnt/dmnt_types.h | 54 ++ src/core/hle/service/services.cpp | 2 + src/core/memory/cheat_engine.cpp | 288 --------- src/core/memory/cheat_engine.h | 88 --- src/core/memory/dmnt_cheat_types.h | 37 -- 21 files changed, 1554 insertions(+), 630 deletions(-) create mode 100644 src/core/hle/service/dmnt/cheat_interface.cpp create mode 100644 src/core/hle/service/dmnt/cheat_interface.h create mode 100644 src/core/hle/service/dmnt/cheat_parser.cpp create mode 100644 src/core/hle/service/dmnt/cheat_parser.h create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.cpp create mode 100644 src/core/hle/service/dmnt/cheat_process_manager.h rename src/core/{memory/dmnt_cheat_vm.cpp => hle/service/dmnt/cheat_virtual_machine.cpp} (81%) rename src/core/{memory/dmnt_cheat_vm.h => hle/service/dmnt/cheat_virtual_machine.h} (87%) create mode 100644 src/core/hle/service/dmnt/dmnt.cpp create mode 100644 src/core/hle/service/dmnt/dmnt.h create mode 100644 src/core/hle/service/dmnt/dmnt_results.h create mode 100644 src/core/hle/service/dmnt/dmnt_types.h delete mode 100644 src/core/memory/cheat_engine.cpp delete mode 100644 src/core/memory/cheat_engine.h delete mode 100644 src/core/memory/dmnt_cheat_types.h diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1eb43d816e..1cce7674b1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/core.cpp b/src/core/core.cpp index 9e8936728a..effda9c175 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -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(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 cheat_engine; + std::unique_ptr cheat_manager; std::unique_ptr memory_freezer; std::array build_id{}; @@ -879,11 +879,20 @@ FileSys::VirtualFilesystem System::GetFilesystem() const { return impl->virtual_filesystem; } -void System::RegisterCheatList(const std::vector& list, +void System::RegisterCheatList(std::span list, const std::array& build_id, u64 main_region_begin, u64 main_region_size) { - impl->cheat_engine = std::make_unique(*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; } diff --git a/src/core/core.h b/src/core/core.h index 90826bd3a1..acb3160517 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -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& list, + void RegisterCheatList(std::span list, const std::array& 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(); diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 21d45235e4..0ed417cb7e 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -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> ReadCheatFileFromFolder( +std::optional> 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> ReadCheatFileFromFolder( return std::nullopt; } - const Core::Memory::TextCheatParser parser; + const Service::DMNT::CheatParser parser; return parser.Parse(std::string_view(reinterpret_cast(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 PatchManager::CreateCheatList( +std::vector 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 PatchManager::CreateCheatList( std::sort(patch_dirs.begin(), patch_dirs.end(), [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); - std::vector out; + std::vector out; for (const auto& subdir : patch_dirs) { if (std::find(disabled.cbegin(), disabled.cend(), subdir->GetName()) != disabled.cend()) { continue; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 552c0fbe23..0850e1a720 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -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 CreateCheatList( + [[nodiscard]] std::vector CreateCheatList( const BuildID& build_id) const; // Currently tracked RomFS patches: diff --git a/src/core/hle/service/dmnt/cheat_interface.cpp b/src/core/hle/service/dmnt/cheat_interface.cpp new file mode 100644 index 0000000000..59038946e5 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_interface.cpp @@ -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 out_has_cheat) { + LOG_INFO(CheatEngine, "called"); + *out_has_cheat = cheat_process_manager.HasCheatProcess(); + R_SUCCEED(); +} + +Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle out_event) { + LOG_INFO(CheatEngine, "called"); + *out_event = &cheat_process_manager.GetCheatProcessEvent(); + R_SUCCEED(); +} + +Result ICheatInterface::GetCheatProcessMetadata(Out 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 out_count) { + LOG_WARNING(CheatEngine, "(STUBBED) called"); + R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count)); +} + +Result ICheatInterface::GetCheatProcessMappings( + Out out_count, u64 offset, + OutArray 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 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 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 out_mapping, + u64 address) { + LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address); + R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address)); +} + +Result ICheatInterface::GetCheatCount(Out out_count) { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.GetCheatCount(*out_count)); +} + +Result ICheatInterface::GetCheats(Out out_count, u64 offset, + OutArray 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 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 out_cheat_id, bool is_enabled, + InLargeData 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 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 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 out_count) { + LOG_INFO(CheatEngine, "called"); + R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count)); +} + +Result ICheatInterface::GetFrozenAddresses( + Out out_count, u64 offset, + OutArray 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 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 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 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 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 diff --git a/src/core/hle/service/dmnt/cheat_interface.h b/src/core/hle/service/dmnt/cheat_interface.h new file mode 100644 index 0000000000..495b5c24cc --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_interface.h @@ -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 { +public: + explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager); + ~ICheatInterface() override; + +private: + Result HasCheatProcess(Out out_has_cheat); + Result GetCheatProcessEvent(OutCopyHandle out_event); + Result GetCheatProcessMetadata(Out out_metadata); + Result ForceOpenCheatProcess(); + Result PauseCheatProcess(); + Result ResumeCheatProcess(); + Result ForceCloseCheatProcess(); + + Result GetCheatProcessMappingCount(Out out_count); + Result GetCheatProcessMappings( + Out out_count, u64 offset, + OutArray out_mappings); + Result ReadCheatProcessMemory(u64 address, u64 size, + OutBuffer out_buffer); + Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer buffer); + + Result QueryCheatProcessMemory(Out out_mapping, u64 address); + Result GetCheatCount(Out out_count); + Result GetCheats(Out out_count, u64 offset, + OutArray out_cheats); + Result GetCheatById(OutLargeData out_cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + + Result AddCheat(Out out_cheat_id, bool enabled, + InLargeData cheat_definition); + Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(Out out_value, u8 register_index); + Result WriteStaticRegister(u8 register_index, u64 value); + Result ResetStaticRegisters(); + Result SetMasterCheat(InLargeData cheat_definition); + Result GetFrozenAddressCount(Out out_count); + Result GetFrozenAddresses( + Out out_count, u64 offset, + OutArray out_frozen_address); + Result GetFrozenAddress(Out out_frozen_address_entry, u64 address); + Result EnableFrozenAddress(Out out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + +private: + void InitializeCheatManager(); + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span out_data, size_t size); + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span data, size_t size); + + Result PauseCheatProcessUnsafe(); + Result ResumeCheatProcessUnsafe(); + + CheatProcessManager& cheat_process_manager; +}; + +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/cheat_parser.cpp b/src/core/hle/service/dmnt/cheat_parser.cpp new file mode 100644 index 0000000000..f20cd8cc20 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_parser.cpp @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include + +#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 CheatParser::Parse(std::string_view data) const { + std::vector out(1); + std::optional 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(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(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(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 diff --git a/src/core/hle/service/dmnt/cheat_parser.h b/src/core/hle/service/dmnt/cheat_parser.h new file mode 100644 index 0000000000..1c2870856d --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_parser.h @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +namespace Service::DMNT { +struct CheatEntry; + +class CheatParser final { +public: + CheatParser(); + ~CheatParser(); + + std::vector 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 diff --git a/src/core/hle/service/dmnt/cheat_process_manager.cpp b/src/core/hle/service/dmnt/cheat_process_manager.cpp new file mode 100644 index 0000000000..795adabef4 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_process_manager.cpp @@ -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 { + 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 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(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& 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 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 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 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 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 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 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(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("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(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 diff --git a/src/core/hle/service/dmnt/cheat_process_manager.h b/src/core/hle/service/dmnt/cheat_process_manager.h new file mode 100644 index 0000000000..442d959409 --- /dev/null +++ b/src/core/hle/service/dmnt/cheat_process_manager.h @@ -0,0 +1,124 @@ +// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include + +#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 vm); + + bool HasCheatProcess(); + Kernel::KReadableEvent& GetCheatProcessEvent() const; + Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata); + Result AttachToApplicationProcess(const std::array& 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 out_mappings); + Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span out_data); + Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size); + Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span data); + Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size); + + Result QueryCheatProcessMemory(Out mapping, u64 address); + Result GetCheatCount(u64& out_count); + Result GetCheats(u64& out_count, u64 offset, std::span 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 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 cheat_vm; + + bool enable_cheats_by_default = true; + bool always_save_cheat_toggles = false; + bool should_save_cheat_toggles = false; + std::array cheat_entries = {}; + // TODO: Replace with IntrusiveRedBlackTree + std::map frozen_addresses_map = {}; + + Core::System& system; + KernelHelpers::ServiceContext service_context; + std::shared_ptr update_event; + Core::Timing::CoreTiming& core_timing; +}; + +} // namespace Service::DMNT diff --git a/src/core/memory/dmnt_cheat_vm.cpp b/src/core/hle/service/dmnt/cheat_virtual_machine.cpp similarity index 81% rename from src/core/memory/dmnt_cheat_vm.cpp rename to src/core/hle/service/dmnt/cheat_virtual_machine.cpp index caceeec4fc..78ea6ae639 100644 --- a/src/core/memory/dmnt_cheat_vm.cpp +++ b/src/core/hle/service/dmnt/cheat_virtual_machine.cpp @@ -1,226 +1,221 @@ // SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + #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(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(log_id), value); +void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) const { + manager.DebugLog(static_cast(log_id), value); } -void DmntCheatVm::LogOpcode(const CheatVmOpcode& opcode) { +void CheatVirtualMachine::LogOpcode(const CheatVmOpcode& opcode) const { if (auto store_static = std::get_if(&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(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(&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(begin_cond->mem_type))); - callbacks->CommandLog( - fmt::format("Cond Type: {:X}", static_cast(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(begin_cond->mem_type))); + manager.CommandLog(fmt::format("Cond Type: {:X}", static_cast(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(opcode.opcode)) { - callbacks->CommandLog("Opcode: End Conditional"); + manager.CommandLog("Opcode: End Conditional"); } else if (auto ctrl_loop = std::get_if(&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(&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(&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(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(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(&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(&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(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(&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(&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(&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(str_register->mem_type))); break; case StoreRegisterOffsetType::MemImm: case StoreRegisterOffsetType::MemImmReg: - callbacks->CommandLog( + manager.CommandLog( fmt::format("Mem Type: {:X}", static_cast(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(&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(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(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(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(&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(save_restore_reg->op_type))); } else if (auto save_restore_regmask = std::get_if(&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(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(&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(&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(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(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(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(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(&opcode.opcode)) { - callbacks->CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(instr->opcode))); + manager.CommandLog(fmt::format("Unknown opcode: {:X}", static_cast(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& entries) { +bool CheatVirtualMachine::LoadProgram(std::span entries) { // Reset opcode count. num_opcodes = 0; @@ -729,31 +724,31 @@ bool DmntCheatVm::LoadProgram(const std::vector& 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(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(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(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(&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, ®isters[ldr_memory->reg_index], - ldr_memory->bit_width); + manager.ReadCheatProcessMemoryUnsafe(src_address, ®isters[ldr_memory->reg_index], + ldr_memory->bit_width); break; } } else if (auto str_static = std::get_if(&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(cur_opcode.opcode)) { - callbacks->PauseProcess(); + manager.PauseCheatProcessUnsafe(); } else if (std::holds_alternative(cur_opcode.opcode)) { - callbacks->ResumeProcess(); + manager.ResumeCheatProcessUnsafe(); } else if (auto debug_log = std::get_if(&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 diff --git a/src/core/memory/dmnt_cheat_vm.h b/src/core/hle/service/dmnt/cheat_virtual_machine.h similarity index 87% rename from src/core/memory/dmnt_cheat_vm.h rename to src/core/hle/service/dmnt/cheat_virtual_machine.h index 1c1ed1259b..fc74434d3a 100644 --- a/src/core/memory/dmnt_cheat_vm.h +++ b/src/core/hle/service/dmnt/cheat_virtual_machine.h @@ -3,13 +3,14 @@ #pragma once +#include #include -#include -#include -#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_); - ~DmntCheatVm(); + explicit CheatVirtualMachine(CheatProcessManager& cheat_manager); + ~CheatVirtualMachine(); std::size_t GetProgramSize() const { return this->num_opcodes; } - bool LoadProgram(const std::vector& cheats); + bool LoadProgram(std::span 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; + 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 saved_values{}; std::array static_registers{}; std::array 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 diff --git a/src/core/hle/service/dmnt/dmnt.cpp b/src/core/hle/service/dmnt/dmnt.cpp new file mode 100644 index 0000000000..f1461cbb08 --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt.cpp @@ -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(system); + + auto& cheat_manager = system.GetCheatManager(); + auto cheat_vm = std::make_unique(cheat_manager); + cheat_manager.SetVirtualMachine(std::move(cheat_vm)); + + server_manager->RegisterNamedService("dmnt:cht", + std::make_shared(system, cheat_manager)); + ServerManager::RunServer(std::move(server_manager)); +} + +} // namespace Service::DMNT diff --git a/src/core/hle/service/dmnt/dmnt.h b/src/core/hle/service/dmnt/dmnt.h new file mode 100644 index 0000000000..56094b353a --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt.h @@ -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 diff --git a/src/core/hle/service/dmnt/dmnt_results.h b/src/core/hle/service/dmnt/dmnt_results.h new file mode 100644 index 0000000000..fdc67f97a6 --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt_results.h @@ -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 diff --git a/src/core/hle/service/dmnt/dmnt_types.h b/src/core/hle/service/dmnt/dmnt_types.h new file mode 100644 index 0000000000..4872d402a7 --- /dev/null +++ b/src/core/hle/service/dmnt/dmnt_types.h @@ -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 main_nso_build_id{}; +}; +static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size"); + +struct CheatDefinition { + std::array readable_name; + u32 num_opcodes; + std::array 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 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 diff --git a/src/core/hle/service/services.cpp b/src/core/hle/service/services.cpp index 3defa4b31f..1842ae2c12 100644 --- a/src/core/hle/service/services.cpp +++ b/src/core/hle/service/services.cpp @@ -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, 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 } diff --git a/src/core/memory/cheat_engine.cpp b/src/core/memory/cheat_engine.cpp deleted file mode 100644 index d8921e5658..0000000000 --- a/src/core/memory/cheat_engine.cpp +++ /dev/null @@ -1,288 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#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("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(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 TextCheatParser::Parse(std::string_view data) const { - std::vector out(1); - std::optional 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(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(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(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 cheats_, - const std::array& build_id_) - : vm{std::make_unique(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 { - 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 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 diff --git a/src/core/memory/cheat_engine.h b/src/core/memory/cheat_engine.h deleted file mode 100644 index f52f2be7c3..0000000000 --- a/src/core/memory/cheat_engine.h +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include -#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 Parse(std::string_view data) const = 0; -}; - -// CheatParser implementation that parses text files -class TextCheatParser final : public CheatParser { -public: - ~TextCheatParser() override; - - [[nodiscard]] std::vector 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 cheats_, - const std::array& build_id_); - ~CheatEngine(); - - void Initialize(); - void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); - - void Reload(std::vector reload_cheats); - -private: - void FrameCallback(std::chrono::nanoseconds ns_late); - - DmntCheatVm vm; - CheatProcessMetadata metadata; - - std::vector cheats; - std::atomic_bool is_pending_reload{false}; - - std::shared_ptr event; - Core::Timing::CoreTiming& core_timing; - Core::System& system; -}; - -} // namespace Core::Memory diff --git a/src/core/memory/dmnt_cheat_types.h b/src/core/memory/dmnt_cheat_types.h deleted file mode 100644 index 64c072d3de..0000000000 --- a/src/core/memory/dmnt_cheat_types.h +++ /dev/null @@ -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 main_nso_build_id{}; -}; - -struct CheatDefinition { - std::array readable_name{}; - u32 num_opcodes{}; - std::array opcodes{}; -}; - -struct CheatEntry { - bool enabled{}; - u32 cheat_id{}; - CheatDefinition definition{}; -}; - -} // namespace Core::Memory