31 changed files with 3479 additions and 2107 deletions
-
91src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/AddonAdapter.kt
-
30src/android/app/src/main/java/org/yuzu/yuzu_emu/model/AddonViewModel.kt
-
23src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Patch.kt
-
3src/android/app/src/main/java/org/yuzu/yuzu_emu/model/PatchType.kt
-
8src/android/app/src/main/jni/native.cpp
-
2src/common/android/id_cache.cpp
-
17src/core/CMakeLists.txt
-
150src/core/core.cpp
-
12src/core/core.h
-
228src/core/file_sys/patch_manager.cpp
-
17src/core/file_sys/patch_manager.h
-
238src/core/hle/service/dmnt/cheat_interface.cpp
-
88src/core/hle/service/dmnt/cheat_interface.h
-
120src/core/hle/service/dmnt/cheat_parser.cpp
-
26src/core/hle/service/dmnt/cheat_parser.h
-
599src/core/hle/service/dmnt/cheat_process_manager.cpp
-
126src/core/hle/service/dmnt/cheat_process_manager.h
-
1269src/core/hle/service/dmnt/cheat_virtual_machine.cpp
-
323src/core/hle/service/dmnt/cheat_virtual_machine.h
-
26src/core/hle/service/dmnt/dmnt.cpp
-
15src/core/hle/service/dmnt/dmnt.h
-
26src/core/hle/service/dmnt/dmnt_results.h
-
55src/core/hle/service/dmnt/dmnt_types.h
-
2src/core/hle/service/services.cpp
-
286src/core/memory/cheat_engine.cpp
-
88src/core/memory/cheat_engine.h
-
37src/core/memory/dmnt_cheat_types.h
-
1268src/core/memory/dmnt_cheat_vm.cpp
-
330src/core/memory/dmnt_cheat_vm.h
-
4src/video_core/vulkan_common/vulkan_instance.cpp
-
79src/yuzu/configuration/configure_per_game_addons.cpp
@ -0,0 +1,238 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "core/hle/service/cmif_serialization.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
|
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
|
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
ICheatInterface::ICheatInterface(Core::System& system_, CheatProcessManager& manager) |
||||
|
: ServiceFramework{system_, "dmnt:cht"}, cheat_process_manager{manager} { |
||||
|
// clang-format off
|
||||
|
static const FunctionInfo functions[] = { |
||||
|
{65000, C<&ICheatInterface::HasCheatProcess>, "HasCheatProcess"}, |
||||
|
{65001, C<&ICheatInterface::GetCheatProcessEvent>, "GetCheatProcessEvent"}, |
||||
|
{65002, C<&ICheatInterface::GetCheatProcessMetadata>, "GetCheatProcessMetadata"}, |
||||
|
{65003, C<&ICheatInterface::ForceOpenCheatProcess>, "ForceOpenCheatProcess"}, |
||||
|
{65004, C<&ICheatInterface::PauseCheatProcess>, "PauseCheatProcess"}, |
||||
|
{65005, C<&ICheatInterface::ResumeCheatProcess>, "ResumeCheatProcess"}, |
||||
|
{65006, C<&ICheatInterface::ForceCloseCheatProcess>, "ForceCloseCheatProcess"}, |
||||
|
{65100, C<&ICheatInterface::GetCheatProcessMappingCount>, "GetCheatProcessMappingCount"}, |
||||
|
{65101, C<&ICheatInterface::GetCheatProcessMappings>, "GetCheatProcessMappings"}, |
||||
|
{65102, C<&ICheatInterface::ReadCheatProcessMemory>, "ReadCheatProcessMemory"}, |
||||
|
{65103, C<&ICheatInterface::WriteCheatProcessMemory>, "WriteCheatProcessMemory"}, |
||||
|
{65104, C<&ICheatInterface::QueryCheatProcessMemory>, "QueryCheatProcessMemory"}, |
||||
|
{65200, C<&ICheatInterface::GetCheatCount>, "GetCheatCount"}, |
||||
|
{65201, C<&ICheatInterface::GetCheats>, "GetCheats"}, |
||||
|
{65202, C<&ICheatInterface::GetCheatById>, "GetCheatById"}, |
||||
|
{65203, C<&ICheatInterface::ToggleCheat>, "ToggleCheat"}, |
||||
|
{65204, C<&ICheatInterface::AddCheat>, "AddCheat"}, |
||||
|
{65205, C<&ICheatInterface::RemoveCheat>, "RemoveCheat"}, |
||||
|
{65206, C<&ICheatInterface::ReadStaticRegister>, "ReadStaticRegister"}, |
||||
|
{65207, C<&ICheatInterface::WriteStaticRegister>, "WriteStaticRegister"}, |
||||
|
{65208, C<&ICheatInterface::ResetStaticRegisters>, "ResetStaticRegisters"}, |
||||
|
{65209, C<&ICheatInterface::SetMasterCheat>, "SetMasterCheat"}, |
||||
|
{65300, C<&ICheatInterface::GetFrozenAddressCount>, "GetFrozenAddressCount"}, |
||||
|
{65301, C<&ICheatInterface::GetFrozenAddresses>, "GetFrozenAddresses"}, |
||||
|
{65302, C<&ICheatInterface::GetFrozenAddress>, "GetFrozenAddress"}, |
||||
|
{65303, C<&ICheatInterface::EnableFrozenAddress>, "EnableFrozenAddress"}, |
||||
|
{65304, C<&ICheatInterface::DisableFrozenAddress>, "DisableFrozenAddress"}, |
||||
|
}; |
||||
|
// clang-format on
|
||||
|
|
||||
|
RegisterHandlers(functions); |
||||
|
} |
||||
|
|
||||
|
ICheatInterface::~ICheatInterface() = default; |
||||
|
|
||||
|
Result ICheatInterface::HasCheatProcess(Out<bool> out_has_cheat) { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
*out_has_cheat = cheat_process_manager.HasCheatProcess(); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
*out_event = &cheat_process_manager.GetCheatProcessEvent(); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata) { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.GetCheatProcessMetadata(*out_metadata)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ForceOpenCheatProcess() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_UNLESS(R_SUCCEEDED(cheat_process_manager.ForceOpenCheatProcess()), ResultCheatNotAttached); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::PauseCheatProcess() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.PauseCheatProcess()); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ResumeCheatProcess() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.ResumeCheatProcess()); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ForceCloseCheatProcess() { |
||||
|
LOG_WARNING(CheatEngine, "(STUBBED) called"); |
||||
|
R_RETURN(cheat_process_manager.ForceCloseCheatProcess()); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatProcessMappingCount(Out<u64> out_count) { |
||||
|
LOG_WARNING(CheatEngine, "(STUBBED) called"); |
||||
|
R_RETURN(cheat_process_manager.GetCheatProcessMappingCount(*out_count)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatProcessMappings( |
||||
|
Out<u64> out_count, u64 offset, |
||||
|
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings) { |
||||
|
LOG_INFO(CheatEngine, "called, offset={}", offset); |
||||
|
R_UNLESS(!out_mappings.empty(), ResultCheatNullBuffer); |
||||
|
R_RETURN(cheat_process_manager.GetCheatProcessMappings(*out_count, offset, out_mappings)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ReadCheatProcessMemory(u64 address, u64 size, |
||||
|
OutBuffer<BufferAttr_HipcMapAlias> out_buffer) { |
||||
|
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size); |
||||
|
R_UNLESS(!out_buffer.empty(), ResultCheatNullBuffer); |
||||
|
R_RETURN(cheat_process_manager.ReadCheatProcessMemory(address, size, out_buffer)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::WriteCheatProcessMemory(u64 address, u64 size, |
||||
|
InBuffer<BufferAttr_HipcMapAlias> buffer) { |
||||
|
LOG_DEBUG(CheatEngine, "called, address={}, size={}", address, size); |
||||
|
R_UNLESS(!buffer.empty(), ResultCheatNullBuffer); |
||||
|
R_RETURN(cheat_process_manager.WriteCheatProcessMemory(address, size, buffer)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, |
||||
|
u64 address) { |
||||
|
LOG_WARNING(CheatEngine, "(STUBBED) called, address={}", address); |
||||
|
R_RETURN(cheat_process_manager.QueryCheatProcessMemory(out_mapping, address)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatCount(Out<u64> out_count) { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.GetCheatCount(*out_count)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheats(Out<u64> out_count, u64 offset, |
||||
|
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats) { |
||||
|
LOG_INFO(CheatEngine, "called, offset={}", offset); |
||||
|
R_UNLESS(!out_cheats.empty(), ResultCheatNullBuffer); |
||||
|
R_RETURN(cheat_process_manager.GetCheats(*out_count, offset, out_cheats)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, |
||||
|
u32 cheat_id) { |
||||
|
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); |
||||
|
R_RETURN(cheat_process_manager.GetCheatById(out_cheat, cheat_id)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ToggleCheat(u32 cheat_id) { |
||||
|
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); |
||||
|
R_RETURN(cheat_process_manager.ToggleCheat(cheat_id)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::AddCheat( |
||||
|
Out<u32> out_cheat_id, bool is_enabled, |
||||
|
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) { |
||||
|
LOG_INFO(CheatEngine, "called, is_enabled={}", is_enabled); |
||||
|
R_RETURN(cheat_process_manager.AddCheat(*out_cheat_id, is_enabled, *cheat_definition)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::RemoveCheat(u32 cheat_id) { |
||||
|
LOG_INFO(CheatEngine, "called, cheat_id={}", cheat_id); |
||||
|
R_RETURN(cheat_process_manager.RemoveCheat(cheat_id)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ReadStaticRegister(Out<u64> out_value, u8 register_index) { |
||||
|
LOG_DEBUG(CheatEngine, "called, register_index={}", register_index); |
||||
|
R_RETURN(cheat_process_manager.ReadStaticRegister(*out_value, register_index)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::WriteStaticRegister(u8 register_index, u64 value) { |
||||
|
LOG_DEBUG(CheatEngine, "called, register_index={}, value={}", register_index, value); |
||||
|
R_RETURN(cheat_process_manager.WriteStaticRegister(register_index, value)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ResetStaticRegisters() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.ResetStaticRegisters()); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::SetMasterCheat( |
||||
|
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition) { |
||||
|
LOG_INFO(CheatEngine, "called, name={}, num_opcodes={}", cheat_definition->readable_name.data(), |
||||
|
cheat_definition->num_opcodes); |
||||
|
R_RETURN(cheat_process_manager.SetMasterCheat(*cheat_definition)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetFrozenAddressCount(Out<u64> out_count) { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.GetFrozenAddressCount(*out_count)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetFrozenAddresses( |
||||
|
Out<u64> out_count, u64 offset, |
||||
|
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address) { |
||||
|
LOG_INFO(CheatEngine, "called, offset={}", offset); |
||||
|
R_UNLESS(!out_frozen_address.empty(), ResultCheatNullBuffer); |
||||
|
R_RETURN(cheat_process_manager.GetFrozenAddresses(*out_count, offset, out_frozen_address)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, |
||||
|
u64 address) { |
||||
|
LOG_INFO(CheatEngine, "called, address={}", address); |
||||
|
R_RETURN(cheat_process_manager.GetFrozenAddress(*out_frozen_address_entry, address)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width) { |
||||
|
LOG_INFO(CheatEngine, "called, address={}, width={}", address, width); |
||||
|
R_UNLESS(width > 0, ResultFrozenAddressInvalidWidth); |
||||
|
R_UNLESS(width <= sizeof(u64), ResultFrozenAddressInvalidWidth); |
||||
|
R_UNLESS((width & (width - 1)) == 0, ResultFrozenAddressInvalidWidth); |
||||
|
R_RETURN(cheat_process_manager.EnableFrozenAddress(*out_value, address, width)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::DisableFrozenAddress(u64 address) { |
||||
|
LOG_INFO(CheatEngine, "called, address={}", address); |
||||
|
R_RETURN(cheat_process_manager.DisableFrozenAddress(address)); |
||||
|
} |
||||
|
|
||||
|
void ICheatInterface::InitializeCheatManager() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, |
||||
|
size_t size) { |
||||
|
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size); |
||||
|
R_RETURN(cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, &out_data, size)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, |
||||
|
size_t size) { |
||||
|
LOG_DEBUG(CheatEngine, "called, process_addr={}, size={}", process_addr, size); |
||||
|
R_RETURN(cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, &data, size)); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::PauseCheatProcessUnsafe() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.PauseCheatProcessUnsafe()); |
||||
|
} |
||||
|
|
||||
|
Result ICheatInterface::ResumeCheatProcessUnsafe() { |
||||
|
LOG_INFO(CheatEngine, "called"); |
||||
|
R_RETURN(cheat_process_manager.ResumeCheatProcessUnsafe()); |
||||
|
} |
||||
|
} // namespace Service::DMNT
|
||||
@ -0,0 +1,88 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "core/hle/service/cmif_types.h" |
||||
|
#include "core/hle/service/kernel_helpers.h" |
||||
|
#include "core/hle/service/service.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
class KReadableEvent; |
||||
|
} // namespace Kernel |
||||
|
|
||||
|
namespace Kernel::Svc { |
||||
|
struct MemoryInfo; |
||||
|
} |
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
struct CheatDefinition; |
||||
|
struct CheatEntry; |
||||
|
struct CheatProcessMetadata; |
||||
|
struct FrozenAddressEntry; |
||||
|
class CheatProcessManager; |
||||
|
|
||||
|
class ICheatInterface final : public ServiceFramework<ICheatInterface> { |
||||
|
public: |
||||
|
explicit ICheatInterface(Core::System& system_, CheatProcessManager& manager); |
||||
|
~ICheatInterface() override; |
||||
|
|
||||
|
private: |
||||
|
Result HasCheatProcess(Out<bool> out_has_cheat); |
||||
|
Result GetCheatProcessEvent(OutCopyHandle<Kernel::KReadableEvent> out_event); |
||||
|
Result GetCheatProcessMetadata(Out<CheatProcessMetadata> out_metadata); |
||||
|
Result ForceOpenCheatProcess(); |
||||
|
Result PauseCheatProcess(); |
||||
|
Result ResumeCheatProcess(); |
||||
|
Result ForceCloseCheatProcess(); |
||||
|
|
||||
|
Result GetCheatProcessMappingCount(Out<u64> out_count); |
||||
|
Result GetCheatProcessMappings( |
||||
|
Out<u64> out_count, u64 offset, |
||||
|
OutArray<Kernel::Svc::MemoryInfo, BufferAttr_HipcMapAlias> out_mappings); |
||||
|
Result ReadCheatProcessMemory(u64 address, u64 size, |
||||
|
OutBuffer<BufferAttr_HipcMapAlias> out_buffer); |
||||
|
Result WriteCheatProcessMemory(u64 address, u64 size, InBuffer<BufferAttr_HipcMapAlias> buffer); |
||||
|
|
||||
|
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> out_mapping, u64 address); |
||||
|
Result GetCheatCount(Out<u64> out_count); |
||||
|
Result GetCheats(Out<u64> out_count, u64 offset, |
||||
|
OutArray<CheatEntry, BufferAttr_HipcMapAlias> out_cheats); |
||||
|
Result GetCheatById(OutLargeData<CheatEntry, BufferAttr_HipcMapAlias> out_cheat, u32 cheat_id); |
||||
|
Result ToggleCheat(u32 cheat_id); |
||||
|
|
||||
|
Result AddCheat(Out<u32> out_cheat_id, bool enabled, |
||||
|
InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition); |
||||
|
Result RemoveCheat(u32 cheat_id); |
||||
|
Result ReadStaticRegister(Out<u64> out_value, u8 register_index); |
||||
|
Result WriteStaticRegister(u8 register_index, u64 value); |
||||
|
Result ResetStaticRegisters(); |
||||
|
Result SetMasterCheat(InLargeData<CheatDefinition, BufferAttr_HipcMapAlias> cheat_definition); |
||||
|
Result GetFrozenAddressCount(Out<u64> out_count); |
||||
|
Result GetFrozenAddresses( |
||||
|
Out<u64> out_count, u64 offset, |
||||
|
OutArray<FrozenAddressEntry, BufferAttr_HipcMapAlias> out_frozen_address); |
||||
|
Result GetFrozenAddress(Out<FrozenAddressEntry> out_frozen_address_entry, u64 address); |
||||
|
Result EnableFrozenAddress(Out<u64> out_value, u64 address, u64 width); |
||||
|
Result DisableFrozenAddress(u64 address); |
||||
|
|
||||
|
private: |
||||
|
void InitializeCheatManager(); |
||||
|
|
||||
|
Result ReadCheatProcessMemoryUnsafe(u64 process_addr, std::span<u8> out_data, size_t size); |
||||
|
Result WriteCheatProcessMemoryUnsafe(u64 process_addr, std::span<const u8> data, size_t size); |
||||
|
|
||||
|
Result PauseCheatProcessUnsafe(); |
||||
|
Result ResumeCheatProcessUnsafe(); |
||||
|
|
||||
|
CheatProcessManager& cheat_process_manager; |
||||
|
}; |
||||
|
} // namespace Service::DMNT |
||||
@ -0,0 +1,120 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include <algorithm>
|
||||
|
#include <cctype>
|
||||
|
#include <optional>
|
||||
|
#include <string>
|
||||
|
|
||||
|
#include "core/hle/service/dmnt/cheat_parser.h"
|
||||
|
|
||||
|
#include "core/hle/service/dmnt/dmnt_types.h"
|
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
CheatParser::CheatParser() {} |
||||
|
|
||||
|
CheatParser::~CheatParser() = default; |
||||
|
|
||||
|
std::vector<CheatEntry> CheatParser::Parse(std::string_view data) const { |
||||
|
std::vector<CheatEntry> out(1); |
||||
|
std::optional<u64> current_entry; |
||||
|
|
||||
|
for (std::size_t i = 0; i < data.size(); ++i) { |
||||
|
if (std::isspace(data[i])) { |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (data[i] == '{') { |
||||
|
current_entry = 0; |
||||
|
|
||||
|
if (out[*current_entry].definition.num_opcodes > 0) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
std::size_t name_size{}; |
||||
|
const auto name = ExtractName(name_size, data, i + 1, '}'); |
||||
|
if (name.empty()) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), |
||||
|
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), |
||||
|
name.size())); |
||||
|
out[*current_entry] |
||||
|
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = |
||||
|
'\0'; |
||||
|
|
||||
|
i += name_size + 1; |
||||
|
} else if (data[i] == '[') { |
||||
|
current_entry = out.size(); |
||||
|
out.emplace_back(); |
||||
|
|
||||
|
std::size_t name_size{}; |
||||
|
const auto name = ExtractName(name_size, data, i + 1, ']'); |
||||
|
if (name.empty()) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), |
||||
|
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), |
||||
|
name.size())); |
||||
|
out[*current_entry] |
||||
|
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = |
||||
|
'\0'; |
||||
|
|
||||
|
i += name_size + 1; |
||||
|
} else if (std::isxdigit(data[i])) { |
||||
|
if (!current_entry || out[*current_entry].definition.num_opcodes >= |
||||
|
out[*current_entry].definition.opcodes.size()) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const auto hex = std::string(data.substr(i, 8)); |
||||
|
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); |
||||
|
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = |
||||
|
value; |
||||
|
|
||||
|
i += 7; // 7 because the for loop will increment by 1 more
|
||||
|
} 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
|
||||
@ -0,0 +1,26 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <string_view> |
||||
|
#include <vector> |
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
struct CheatEntry; |
||||
|
|
||||
|
class CheatParser final { |
||||
|
public: |
||||
|
CheatParser(); |
||||
|
~CheatParser(); |
||||
|
|
||||
|
std::vector<CheatEntry> Parse(std::string_view data) const; |
||||
|
|
||||
|
private: |
||||
|
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data, |
||||
|
std::size_t start_index, char match) const; |
||||
|
}; |
||||
|
} // namespace Service::DMNT |
||||
@ -0,0 +1,599 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "core/arm/debug.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/core_timing.h"
|
||||
|
#include "core/hle/service/cmif_serialization.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
|
#include "core/hle/service/dmnt/dmnt_results.h"
|
||||
|
#include "core/hle/service/hid/hid_server.h"
|
||||
|
#include "core/hle/service/sm/sm.h"
|
||||
|
#include "hid_core/resource_manager.h"
|
||||
|
#include "hid_core/resources/npad/npad.h"
|
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; |
||||
|
|
||||
|
CheatProcessManager::CheatProcessManager(Core::System& system_) |
||||
|
: system{system_}, service_context{system_, "dmnt:cht"}, core_timing{system_.CoreTiming()} { |
||||
|
update_event = Core::Timing::CreateEvent("CheatEngine::FrameCallback", |
||||
|
[this](s64 time, std::chrono::nanoseconds ns_late) |
||||
|
-> std::optional<std::chrono::nanoseconds> { |
||||
|
FrameCallback(ns_late); |
||||
|
return std::nullopt; |
||||
|
}); |
||||
|
|
||||
|
for (size_t i = 0; i < MaxCheatCount; i++) { |
||||
|
ResetCheatEntry(i); |
||||
|
} |
||||
|
|
||||
|
cheat_vm = std::make_unique<CheatVirtualMachine>(*this); |
||||
|
|
||||
|
cheat_process_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent"); |
||||
|
unsafe_break_event = service_context.CreateEvent("CheatProcessManager::ProcessEvent"); |
||||
|
} |
||||
|
|
||||
|
CheatProcessManager::~CheatProcessManager() { |
||||
|
service_context.CloseEvent(cheat_process_event); |
||||
|
service_context.CloseEvent(unsafe_break_event); |
||||
|
core_timing.UnscheduleEvent(update_event); |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm) { |
||||
|
if (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) { |
||||
|
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; |
||||
|
} |
||||
|
|
||||
|
cheat_process_metadata = {}; |
||||
|
|
||||
|
ResetAllCheatEntries(); |
||||
|
|
||||
|
{ |
||||
|
auto it = frozen_addresses_map.begin(); |
||||
|
while (it != frozen_addresses_map.end()) { |
||||
|
it = frozen_addresses_map.erase(it); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
cheat_process_event->Signal(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::EnsureCheatProcess() { |
||||
|
R_UNLESS(HasActiveCheatProcess(), ResultCheatNotAttached); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::SetNeedsReloadVm(bool reload) { |
||||
|
needs_reload_vm = reload; |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::ResetCheatEntry(size_t i) { |
||||
|
if (i < MaxCheatCount) { |
||||
|
cheat_entries[i] = {}; |
||||
|
cheat_entries[i].cheat_id = static_cast<u32>(i); |
||||
|
|
||||
|
SetNeedsReloadVm(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::ResetAllCheatEntries() { |
||||
|
for (size_t i = 0; i < MaxCheatCount; i++) { |
||||
|
ResetCheatEntry(i); |
||||
|
} |
||||
|
|
||||
|
cheat_vm->ResetStaticRegisters(); |
||||
|
} |
||||
|
|
||||
|
CheatEntry* CheatProcessManager::GetCheatEntryById(size_t i) { |
||||
|
if (i < MaxCheatCount) { |
||||
|
return cheat_entries.data() + i; |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
CheatEntry* CheatProcessManager::GetCheatEntryByReadableName(const char* readable_name) { |
||||
|
for (size_t i = 1; i < MaxCheatCount; i++) { |
||||
|
if (std::strncmp(cheat_entries[i].definition.readable_name.data(), readable_name, |
||||
|
sizeof(cheat_entries[i].definition.readable_name)) == 0) { |
||||
|
return cheat_entries.data() + i; |
||||
|
} |
||||
|
} |
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
CheatEntry* CheatProcessManager::GetFreeCheatEntry() { |
||||
|
// Check all non-master cheats for availability.
|
||||
|
for (size_t i = 1; i < MaxCheatCount; i++) { |
||||
|
if (cheat_entries[i].definition.num_opcodes == 0) { |
||||
|
return cheat_entries.data() + i; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return nullptr; |
||||
|
} |
||||
|
|
||||
|
bool CheatProcessManager::HasCheatProcess() { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
return HasActiveCheatProcess(); |
||||
|
} |
||||
|
|
||||
|
Kernel::KReadableEvent& CheatProcessManager::GetCheatProcessEvent() const { |
||||
|
return cheat_process_event->GetReadableEvent(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, |
||||
|
VAddr main_region_begin, |
||||
|
u64 main_region_size) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
{ |
||||
|
if (this->HasActiveCheatProcess()) { |
||||
|
this->CloseActiveCheatProcess(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
cheat_process_metadata.process_id = system.ApplicationProcess()->GetProcessId(); |
||||
|
|
||||
|
{ |
||||
|
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(), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
{ |
||||
|
cheat_process_metadata.main_nso_extents = { |
||||
|
.base = main_region_begin, |
||||
|
.size = main_region_size, |
||||
|
}; |
||||
|
cheat_process_metadata.main_nso_build_id = build_id; |
||||
|
} |
||||
|
|
||||
|
cheat_process_debug_handle = cheat_process_metadata.process_id; |
||||
|
|
||||
|
broken_unsafe = false; |
||||
|
unsafe_break_event->Signal(); |
||||
|
|
||||
|
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, update_event); |
||||
|
LOG_INFO(CheatEngine, "Cheat engine started"); |
||||
|
|
||||
|
// Signal to our fans.
|
||||
|
cheat_process_event->Signal(); |
||||
|
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheatProcessMetadata(CheatProcessMetadata& out_metadata) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
out_metadata = cheat_process_metadata; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ForceOpenCheatProcess() { |
||||
|
// R_RETURN(AttachToApplicationProcess(false));
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::PauseCheatProcess() { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
R_RETURN(PauseCheatProcessUnsafe()); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::PauseCheatProcessUnsafe() { |
||||
|
broken_unsafe = true; |
||||
|
unsafe_break_event->Clear(); |
||||
|
if (system.ApplicationProcess()->IsSuspended()) { |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
R_RETURN(system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused)); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ResumeCheatProcess() { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
R_RETURN(ResumeCheatProcessUnsafe()); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ResumeCheatProcessUnsafe() { |
||||
|
broken_unsafe = true; |
||||
|
unsafe_break_event->Clear(); |
||||
|
if (!system.ApplicationProcess()->IsSuspended()) { |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ForceCloseCheatProcess() { |
||||
|
CloseActiveCheatProcess(); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheatProcessMappingCount(u64& out_count) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(this->EnsureCheatProcess()); |
||||
|
|
||||
|
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
|
||||
|
out_count = 0; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheatProcessMappings( |
||||
|
u64& out_count, u64 offset, std::span<Kernel::Svc::MemoryInfo> out_mappings) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(this->EnsureCheatProcess()); |
||||
|
|
||||
|
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
|
||||
|
out_count = 0; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ReadCheatProcessMemory(u64 process_address, u64 size, |
||||
|
std::span<u8> out_data) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
R_RETURN(ReadCheatProcessMemoryUnsafe(process_address, &out_data, size)); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, |
||||
|
size_t size) { |
||||
|
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) { |
||||
|
std::memset(out_data, 0, size); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
system.ApplicationMemory().ReadBlock(process_address, out_data, size); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::WriteCheatProcessMemory(u64 process_address, u64 size, |
||||
|
std::span<const u8> data) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
R_RETURN(WriteCheatProcessMemoryUnsafe(process_address, &data, size)); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, |
||||
|
size_t size) { |
||||
|
if (!system.ApplicationMemory().IsValidVirtualAddress(process_address)) { |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
if (system.ApplicationMemory().WriteBlock(process_address, data, size)) { |
||||
|
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), process_address, size); |
||||
|
} |
||||
|
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, |
||||
|
u64 address) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(this->EnsureCheatProcess()); |
||||
|
|
||||
|
// TODO: Call svc::QueryDebugProcessMemory
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheatCount(u64& out_count) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
out_count = std::count_if(cheat_entries.begin(), cheat_entries.end(), |
||||
|
[](const auto& entry) { return entry.definition.num_opcodes != 0; }); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheats(u64& out_count, u64 offset, |
||||
|
std::span<CheatEntry> out_cheats) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
size_t count = 0, total_count = 0; |
||||
|
for (size_t i = 0; i < MaxCheatCount && count < out_cheats.size(); i++) { |
||||
|
if (cheat_entries[i].definition.num_opcodes) { |
||||
|
total_count++; |
||||
|
if (total_count > offset) { |
||||
|
out_cheats[count++] = cheat_entries[i]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
out_count = count; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetCheatById(CheatEntry* out_cheat, u32 cheat_id) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
const CheatEntry* entry = GetCheatEntryById(cheat_id); |
||||
|
R_UNLESS(entry != nullptr, ResultCheatUnknownId); |
||||
|
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId); |
||||
|
|
||||
|
*out_cheat = *entry; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::ToggleCheat(u32 cheat_id) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
CheatEntry* entry = GetCheatEntryById(cheat_id); |
||||
|
R_UNLESS(entry != nullptr, ResultCheatUnknownId); |
||||
|
R_UNLESS(entry->definition.num_opcodes != 0, ResultCheatUnknownId); |
||||
|
|
||||
|
R_UNLESS(cheat_id != 0, ResultCheatCannotDisable); |
||||
|
|
||||
|
entry->enabled = !entry->enabled; |
||||
|
|
||||
|
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; |
||||
|
|
||||
|
SetNeedsReloadVm(true); |
||||
|
|
||||
|
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, |
||||
|
}; |
||||
|
|
||||
|
SetNeedsReloadVm(true); |
||||
|
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetFrozenAddressCount(u64& out_count) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
out_count = std::distance(frozen_addresses_map.begin(), frozen_addresses_map.end()); |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetFrozenAddresses(u64& out_count, u64 offset, |
||||
|
std::span<FrozenAddressEntry> out_frozen_address) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
u64 total_count = 0, written_count = 0; |
||||
|
for (const auto& [address, value] : frozen_addresses_map) { |
||||
|
if (written_count >= out_frozen_address.size()) { |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (offset <= total_count) { |
||||
|
out_frozen_address[written_count].address = address; |
||||
|
out_frozen_address[written_count].value = value; |
||||
|
written_count++; |
||||
|
} |
||||
|
total_count++; |
||||
|
} |
||||
|
|
||||
|
out_count = written_count; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, |
||||
|
u64 address) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
const auto it = frozen_addresses_map.find(address); |
||||
|
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound); |
||||
|
|
||||
|
out_frozen_address_entry = { |
||||
|
.address = it->first, |
||||
|
.value = it->second, |
||||
|
}; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::EnableFrozenAddress(u64& out_value, u64 address, u64 width) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
const auto it = frozen_addresses_map.find(address); |
||||
|
R_UNLESS(it == frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists); |
||||
|
|
||||
|
FrozenAddressValue value{}; |
||||
|
value.width = static_cast<u8>(width); |
||||
|
R_TRY(ReadCheatProcessMemoryUnsafe(address, &value.value, width)); |
||||
|
|
||||
|
frozen_addresses_map.insert({address, value}); |
||||
|
out_value = value.value; |
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
Result CheatProcessManager::DisableFrozenAddress(u64 address) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
R_TRY(EnsureCheatProcess()); |
||||
|
|
||||
|
const auto it = frozen_addresses_map.find(address); |
||||
|
R_UNLESS(it != frozen_addresses_map.end(), ResultFrozenAddressNotFound); |
||||
|
|
||||
|
frozen_addresses_map.erase(it); |
||||
|
|
||||
|
R_SUCCEED(); |
||||
|
} |
||||
|
|
||||
|
u64 CheatProcessManager::HidKeysDown() const { |
||||
|
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid"); |
||||
|
if (hid == nullptr) { |
||||
|
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const auto applet_resource = hid->GetResourceManager(); |
||||
|
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) { |
||||
|
LOG_WARNING(CheatEngine, |
||||
|
"Attempted to read input state, but applet resource is not initialized!"); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState(); |
||||
|
return static_cast<u64>(press_state & Core::HID::NpadButton::All); |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::DebugLog(u8 id, u64 value) const { |
||||
|
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::CommandLog(std::string_view data) const { |
||||
|
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", |
||||
|
data.back() == '\n' ? data.substr(0, data.size() - 1) : data); |
||||
|
} |
||||
|
|
||||
|
void CheatProcessManager::FrameCallback(std::chrono::nanoseconds ns_late) { |
||||
|
std::scoped_lock lk(cheat_lock); |
||||
|
|
||||
|
if (cheat_vm == nullptr) { |
||||
|
LOG_DEBUG(CheatEngine, "FrameCallback: VM is null"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (needs_reload_vm) { |
||||
|
LOG_INFO(CheatEngine, "Reloading cheat VM with {} entries", cheat_entries.size()); |
||||
|
|
||||
|
size_t enabled_count = 0; |
||||
|
for (const auto& entry : cheat_entries) { |
||||
|
if (entry.enabled && entry.definition.num_opcodes > 0) { |
||||
|
enabled_count++; |
||||
|
LOG_INFO(CheatEngine, " Cheat '{}': {} opcodes, enabled={}", |
||||
|
entry.definition.readable_name.data(), |
||||
|
entry.definition.num_opcodes, entry.enabled); |
||||
|
} |
||||
|
} |
||||
|
LOG_INFO(CheatEngine, "Total enabled cheats: {}", enabled_count); |
||||
|
|
||||
|
cheat_vm->LoadProgram(cheat_entries); |
||||
|
LOG_INFO(CheatEngine, "Cheat VM loaded, program size: {}", cheat_vm->GetProgramSize()); |
||||
|
needs_reload_vm = false; |
||||
|
} |
||||
|
|
||||
|
if (cheat_vm->GetProgramSize() == 0) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
cheat_vm->Execute(cheat_process_metadata); |
||||
|
} |
||||
|
} // namespace Service::DMNT
|
||||
@ -0,0 +1,126 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
#include <span> |
||||
|
|
||||
|
#include "core/hle/service/cmif_types.h" |
||||
|
#include "core/hle/service/dmnt/dmnt_types.h" |
||||
|
#include "core/hle/service/kernel_helpers.h" |
||||
|
#include "core/hle/service/service.h" |
||||
|
|
||||
|
#include "common/intrusive_red_black_tree.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Kernel { |
||||
|
class KEvent; |
||||
|
class KReadableEvent; |
||||
|
} // namespace Kernel |
||||
|
|
||||
|
namespace Kernel::Svc { |
||||
|
struct MemoryInfo; |
||||
|
} |
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
class CheatVirtualMachine; |
||||
|
|
||||
|
class CheatProcessManager final { |
||||
|
public: |
||||
|
static constexpr size_t MaxCheatCount = 0x80; |
||||
|
static constexpr size_t MaxFrozenAddressCount = 0x80; |
||||
|
|
||||
|
CheatProcessManager(Core::System& system_); |
||||
|
~CheatProcessManager(); |
||||
|
|
||||
|
void SetVirtualMachine(std::unique_ptr<CheatVirtualMachine> vm); |
||||
|
|
||||
|
bool HasCheatProcess(); |
||||
|
Kernel::KReadableEvent& GetCheatProcessEvent() const; |
||||
|
Result GetCheatProcessMetadata(CheatProcessMetadata& out_metadata); |
||||
|
Result AttachToApplicationProcess(const std::array<u8, 0x20>& build_id, VAddr main_region_begin, |
||||
|
u64 main_region_size); |
||||
|
Result ForceOpenCheatProcess(); |
||||
|
Result PauseCheatProcess(); |
||||
|
Result PauseCheatProcessUnsafe(); |
||||
|
Result ResumeCheatProcess(); |
||||
|
Result ResumeCheatProcessUnsafe(); |
||||
|
Result ForceCloseCheatProcess(); |
||||
|
|
||||
|
Result GetCheatProcessMappingCount(u64& out_count); |
||||
|
Result GetCheatProcessMappings(u64& out_count, u64 offset, |
||||
|
std::span<Kernel::Svc::MemoryInfo> out_mappings); |
||||
|
Result ReadCheatProcessMemory(u64 process_address, u64 size, std::span<u8> out_data); |
||||
|
Result ReadCheatProcessMemoryUnsafe(u64 process_address, void* out_data, size_t size); |
||||
|
Result WriteCheatProcessMemory(u64 process_address, u64 size, std::span<const u8> data); |
||||
|
Result WriteCheatProcessMemoryUnsafe(u64 process_address, const void* data, size_t size); |
||||
|
|
||||
|
Result QueryCheatProcessMemory(Out<Kernel::Svc::MemoryInfo> mapping, u64 address); |
||||
|
Result GetCheatCount(u64& out_count); |
||||
|
Result GetCheats(u64& out_count, u64 offset, std::span<CheatEntry> out_cheats); |
||||
|
Result GetCheatById(CheatEntry* out_cheat, u32 cheat_id); |
||||
|
Result ToggleCheat(u32 cheat_id); |
||||
|
|
||||
|
Result AddCheat(u32& out_cheat_id, bool enabled, const CheatDefinition& cheat_definition); |
||||
|
Result RemoveCheat(u32 cheat_id); |
||||
|
Result ReadStaticRegister(u64& out_value, u64 register_index); |
||||
|
Result WriteStaticRegister(u64 register_index, u64 value); |
||||
|
Result ResetStaticRegisters(); |
||||
|
Result SetMasterCheat(const CheatDefinition& cheat_definition); |
||||
|
Result GetFrozenAddressCount(u64& out_count); |
||||
|
Result GetFrozenAddresses(u64& out_count, u64 offset, |
||||
|
std::span<FrozenAddressEntry> out_frozen_address); |
||||
|
Result GetFrozenAddress(FrozenAddressEntry& out_frozen_address_entry, u64 address); |
||||
|
Result EnableFrozenAddress(u64& out_value, u64 address, u64 width); |
||||
|
Result DisableFrozenAddress(u64 address); |
||||
|
|
||||
|
u64 HidKeysDown() const; |
||||
|
void DebugLog(u8 id, u64 value) const; |
||||
|
void CommandLog(std::string_view data) const; |
||||
|
|
||||
|
private: |
||||
|
bool HasActiveCheatProcess(); |
||||
|
void CloseActiveCheatProcess(); |
||||
|
Result EnsureCheatProcess(); |
||||
|
void SetNeedsReloadVm(bool reload); |
||||
|
void ResetCheatEntry(size_t i); |
||||
|
void ResetAllCheatEntries(); |
||||
|
CheatEntry* GetCheatEntryById(size_t i); |
||||
|
CheatEntry* GetCheatEntryByReadableName(const char* readable_name); |
||||
|
CheatEntry* GetFreeCheatEntry(); |
||||
|
|
||||
|
void FrameCallback(std::chrono::nanoseconds ns_late); |
||||
|
|
||||
|
static constexpr u64 InvalidHandle = 0; |
||||
|
|
||||
|
mutable std::mutex cheat_lock; |
||||
|
Kernel::KEvent* unsafe_break_event; |
||||
|
|
||||
|
Kernel::KEvent* cheat_process_event; |
||||
|
u64 cheat_process_debug_handle = InvalidHandle; |
||||
|
CheatProcessMetadata cheat_process_metadata = {}; |
||||
|
|
||||
|
bool broken_unsafe = false; |
||||
|
bool needs_reload_vm = false; |
||||
|
std::unique_ptr<CheatVirtualMachine> cheat_vm; |
||||
|
|
||||
|
bool enable_cheats_by_default = true; |
||||
|
bool always_save_cheat_toggles = false; |
||||
|
bool should_save_cheat_toggles = false; |
||||
|
std::array<CheatEntry, MaxCheatCount> cheat_entries = {}; |
||||
|
// TODO: Replace with IntrusiveRedBlackTree |
||||
|
std::map<u64, FrozenAddressValue> frozen_addresses_map = {}; |
||||
|
|
||||
|
Core::System& system; |
||||
|
KernelHelpers::ServiceContext service_context; |
||||
|
std::shared_ptr<Core::Timing::EventType> update_event; |
||||
|
Core::Timing::CoreTiming& core_timing; |
||||
|
}; |
||||
|
} // namespace Service::DMNT |
||||
1269
src/core/hle/service/dmnt/cheat_virtual_machine.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,323 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <span> |
||||
|
#include <variant> |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
#include "core/hle/service/dmnt/dmnt_types.h" |
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
class CheatProcessManager; |
||||
|
|
||||
|
enum class CheatVmOpcodeType : u32 { |
||||
|
StoreStatic = 0, |
||||
|
BeginConditionalBlock = 1, |
||||
|
EndConditionalBlock = 2, |
||||
|
ControlLoop = 3, |
||||
|
LoadRegisterStatic = 4, |
||||
|
LoadRegisterMemory = 5, |
||||
|
StoreStaticToAddress = 6, |
||||
|
PerformArithmeticStatic = 7, |
||||
|
BeginKeypressConditionalBlock = 8, |
||||
|
|
||||
|
// These are not implemented by Gateway's VM. |
||||
|
PerformArithmeticRegister = 9, |
||||
|
StoreRegisterToAddress = 10, |
||||
|
Reserved11 = 11, |
||||
|
|
||||
|
// This is a meta entry, and not a real opcode. |
||||
|
// This is to facilitate multi-nybble instruction decoding. |
||||
|
ExtendedWidth = 12, |
||||
|
|
||||
|
// Extended width opcodes. |
||||
|
BeginRegisterConditionalBlock = 0xC0, |
||||
|
SaveRestoreRegister = 0xC1, |
||||
|
SaveRestoreRegisterMask = 0xC2, |
||||
|
ReadWriteStaticRegister = 0xC3, |
||||
|
|
||||
|
// This is a meta entry, and not a real opcode. |
||||
|
// This is to facilitate multi-nybble instruction decoding. |
||||
|
DoubleExtendedWidth = 0xF0, |
||||
|
|
||||
|
// Double-extended width opcodes. |
||||
|
PauseProcess = 0xFF0, |
||||
|
ResumeProcess = 0xFF1, |
||||
|
DebugLog = 0xFFF, |
||||
|
}; |
||||
|
|
||||
|
enum class MemoryAccessType : u32 { |
||||
|
MainNso = 0, |
||||
|
Heap = 1, |
||||
|
Alias = 2, |
||||
|
Aslr = 3, |
||||
|
}; |
||||
|
|
||||
|
enum class ConditionalComparisonType : u32 { |
||||
|
GT = 1, |
||||
|
GE = 2, |
||||
|
LT = 3, |
||||
|
LE = 4, |
||||
|
EQ = 5, |
||||
|
NE = 6, |
||||
|
}; |
||||
|
|
||||
|
enum class RegisterArithmeticType : u32 { |
||||
|
Addition = 0, |
||||
|
Subtraction = 1, |
||||
|
Multiplication = 2, |
||||
|
LeftShift = 3, |
||||
|
RightShift = 4, |
||||
|
|
||||
|
// These are not supported by Gateway's VM. |
||||
|
LogicalAnd = 5, |
||||
|
LogicalOr = 6, |
||||
|
LogicalNot = 7, |
||||
|
LogicalXor = 8, |
||||
|
|
||||
|
None = 9, |
||||
|
}; |
||||
|
|
||||
|
enum class StoreRegisterOffsetType : u32 { |
||||
|
None = 0, |
||||
|
Reg = 1, |
||||
|
Imm = 2, |
||||
|
MemReg = 3, |
||||
|
MemImm = 4, |
||||
|
MemImmReg = 5, |
||||
|
}; |
||||
|
|
||||
|
enum class CompareRegisterValueType : u32 { |
||||
|
MemoryRelAddr = 0, |
||||
|
MemoryOfsReg = 1, |
||||
|
RegisterRelAddr = 2, |
||||
|
RegisterOfsReg = 3, |
||||
|
StaticValue = 4, |
||||
|
OtherRegister = 5, |
||||
|
}; |
||||
|
|
||||
|
enum class SaveRestoreRegisterOpType : u32 { |
||||
|
Restore = 0, |
||||
|
Save = 1, |
||||
|
ClearSaved = 2, |
||||
|
ClearRegs = 3, |
||||
|
}; |
||||
|
|
||||
|
enum class DebugLogValueType : u32 { |
||||
|
MemoryRelAddr = 0, |
||||
|
MemoryOfsReg = 1, |
||||
|
RegisterRelAddr = 2, |
||||
|
RegisterOfsReg = 3, |
||||
|
RegisterValue = 4, |
||||
|
}; |
||||
|
|
||||
|
union VmInt { |
||||
|
u8 bit8; |
||||
|
u16 bit16; |
||||
|
u32 bit32; |
||||
|
u64 bit64; |
||||
|
}; |
||||
|
|
||||
|
struct StoreStaticOpcode { |
||||
|
u32 bit_width{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
u32 offset_register{}; |
||||
|
u64 rel_address{}; |
||||
|
VmInt value{}; |
||||
|
}; |
||||
|
|
||||
|
struct BeginConditionalOpcode { |
||||
|
u32 bit_width{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
ConditionalComparisonType cond_type{}; |
||||
|
u64 rel_address{}; |
||||
|
VmInt value{}; |
||||
|
}; |
||||
|
|
||||
|
struct EndConditionalOpcode { |
||||
|
bool is_else; |
||||
|
}; |
||||
|
|
||||
|
struct ControlLoopOpcode { |
||||
|
bool start_loop{}; |
||||
|
u32 reg_index{}; |
||||
|
u32 num_iters{}; |
||||
|
}; |
||||
|
|
||||
|
struct LoadRegisterStaticOpcode { |
||||
|
u32 reg_index{}; |
||||
|
u64 value{}; |
||||
|
}; |
||||
|
|
||||
|
struct LoadRegisterMemoryOpcode { |
||||
|
u32 bit_width{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
u32 reg_index{}; |
||||
|
bool load_from_reg{}; |
||||
|
u64 rel_address{}; |
||||
|
}; |
||||
|
|
||||
|
struct StoreStaticToAddressOpcode { |
||||
|
u32 bit_width{}; |
||||
|
u32 reg_index{}; |
||||
|
bool increment_reg{}; |
||||
|
bool add_offset_reg{}; |
||||
|
u32 offset_reg_index{}; |
||||
|
u64 value{}; |
||||
|
}; |
||||
|
|
||||
|
struct PerformArithmeticStaticOpcode { |
||||
|
u32 bit_width{}; |
||||
|
u32 reg_index{}; |
||||
|
RegisterArithmeticType math_type{}; |
||||
|
u32 value{}; |
||||
|
}; |
||||
|
|
||||
|
struct BeginKeypressConditionalOpcode { |
||||
|
u32 key_mask{}; |
||||
|
}; |
||||
|
|
||||
|
struct PerformArithmeticRegisterOpcode { |
||||
|
u32 bit_width{}; |
||||
|
RegisterArithmeticType math_type{}; |
||||
|
u32 dst_reg_index{}; |
||||
|
u32 src_reg_1_index{}; |
||||
|
u32 src_reg_2_index{}; |
||||
|
bool has_immediate{}; |
||||
|
VmInt value{}; |
||||
|
}; |
||||
|
|
||||
|
struct StoreRegisterToAddressOpcode { |
||||
|
u32 bit_width{}; |
||||
|
u32 str_reg_index{}; |
||||
|
u32 addr_reg_index{}; |
||||
|
bool increment_reg{}; |
||||
|
StoreRegisterOffsetType ofs_type{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
u32 ofs_reg_index{}; |
||||
|
u64 rel_address{}; |
||||
|
}; |
||||
|
|
||||
|
struct BeginRegisterConditionalOpcode { |
||||
|
u32 bit_width{}; |
||||
|
ConditionalComparisonType cond_type{}; |
||||
|
u32 val_reg_index{}; |
||||
|
CompareRegisterValueType comp_type{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
u32 addr_reg_index{}; |
||||
|
u32 other_reg_index{}; |
||||
|
u32 ofs_reg_index{}; |
||||
|
u64 rel_address{}; |
||||
|
VmInt value{}; |
||||
|
}; |
||||
|
|
||||
|
struct SaveRestoreRegisterOpcode { |
||||
|
u32 dst_index{}; |
||||
|
u32 src_index{}; |
||||
|
SaveRestoreRegisterOpType op_type{}; |
||||
|
}; |
||||
|
|
||||
|
struct SaveRestoreRegisterMaskOpcode { |
||||
|
SaveRestoreRegisterOpType op_type{}; |
||||
|
std::array<bool, 0x10> should_operate{}; |
||||
|
}; |
||||
|
|
||||
|
struct ReadWriteStaticRegisterOpcode { |
||||
|
u32 static_idx{}; |
||||
|
u32 idx{}; |
||||
|
}; |
||||
|
|
||||
|
struct PauseProcessOpcode {}; |
||||
|
|
||||
|
struct ResumeProcessOpcode {}; |
||||
|
|
||||
|
struct DebugLogOpcode { |
||||
|
u32 bit_width{}; |
||||
|
u32 log_id{}; |
||||
|
DebugLogValueType val_type{}; |
||||
|
MemoryAccessType mem_type{}; |
||||
|
u32 addr_reg_index{}; |
||||
|
u32 val_reg_index{}; |
||||
|
u32 ofs_reg_index{}; |
||||
|
u64 rel_address{}; |
||||
|
}; |
||||
|
|
||||
|
struct UnrecognizedInstruction { |
||||
|
CheatVmOpcodeType opcode{}; |
||||
|
}; |
||||
|
|
||||
|
struct CheatVmOpcode { |
||||
|
bool begin_conditional_block{}; |
||||
|
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, |
||||
|
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, |
||||
|
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, |
||||
|
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, |
||||
|
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, |
||||
|
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode, |
||||
|
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction> |
||||
|
opcode{}; |
||||
|
}; |
||||
|
|
||||
|
class CheatVirtualMachine { |
||||
|
public: |
||||
|
static constexpr std::size_t MaximumProgramOpcodeCount = 0x400; |
||||
|
static constexpr std::size_t NumRegisters = 0x10; |
||||
|
static constexpr std::size_t NumReadableStaticRegisters = 0x80; |
||||
|
static constexpr std::size_t NumWritableStaticRegisters = 0x80; |
||||
|
static constexpr std::size_t NumStaticRegisters = |
||||
|
NumReadableStaticRegisters + NumWritableStaticRegisters; |
||||
|
|
||||
|
explicit CheatVirtualMachine(CheatProcessManager& cheat_manager); |
||||
|
~CheatVirtualMachine(); |
||||
|
|
||||
|
std::size_t GetProgramSize() const { |
||||
|
return this->num_opcodes; |
||||
|
} |
||||
|
|
||||
|
bool LoadProgram(std::span<const CheatEntry> cheats); |
||||
|
void Execute(const CheatProcessMetadata& metadata); |
||||
|
|
||||
|
u64 GetStaticRegister(std::size_t register_index) const { |
||||
|
return static_registers[register_index]; |
||||
|
} |
||||
|
|
||||
|
void SetStaticRegister(std::size_t register_index, u64 value) { |
||||
|
static_registers[register_index] = value; |
||||
|
} |
||||
|
|
||||
|
void ResetStaticRegisters() { |
||||
|
static_registers = {}; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
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; |
||||
|
std::size_t condition_depth = 0; |
||||
|
bool decode_success = false; |
||||
|
std::array<u32, MaximumProgramOpcodeCount> program{}; |
||||
|
std::array<u64, NumRegisters> registers{}; |
||||
|
std::array<u64, NumRegisters> saved_values{}; |
||||
|
std::array<u64, NumStaticRegisters> static_registers{}; |
||||
|
std::array<std::size_t, NumRegisters> loop_tops{}; |
||||
|
}; |
||||
|
}// namespace Service::DMNT |
||||
@ -0,0 +1,26 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
|
||||
|
#include "core/core.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_interface.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_process_manager.h"
|
||||
|
#include "core/hle/service/dmnt/cheat_virtual_machine.h"
|
||||
|
#include "core/hle/service/dmnt/dmnt.h"
|
||||
|
#include "core/hle/service/server_manager.h"
|
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
void LoopProcess(Core::System& system) { |
||||
|
auto server_manager = std::make_unique<ServerManager>(system); |
||||
|
|
||||
|
auto& cheat_manager = system.GetCheatManager(); |
||||
|
auto cheat_vm = std::make_unique<CheatVirtualMachine>(cheat_manager); |
||||
|
cheat_manager.SetVirtualMachine(std::move(cheat_vm)); |
||||
|
|
||||
|
server_manager->RegisterNamedService("dmnt:cht", |
||||
|
std::make_shared<ICheatInterface>(system, cheat_manager)); |
||||
|
ServerManager::RunServer(std::move(server_manager)); |
||||
|
} |
||||
|
} // namespace Service::DMNT
|
||||
@ -0,0 +1,15 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// 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 |
||||
@ -0,0 +1,26 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// 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 |
||||
@ -0,0 +1,55 @@ |
|||||
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-3.0-or-later |
||||
|
|
||||
|
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include "common/common_types.h" |
||||
|
|
||||
|
namespace Service::DMNT { |
||||
|
struct MemoryRegionExtents { |
||||
|
u64 base{}; |
||||
|
u64 size{}; |
||||
|
}; |
||||
|
static_assert(sizeof(MemoryRegionExtents) == 0x10, "MemoryRegionExtents is an invalid size"); |
||||
|
|
||||
|
struct CheatProcessMetadata { |
||||
|
u64 process_id{}; |
||||
|
u64 program_id{}; |
||||
|
MemoryRegionExtents main_nso_extents{}; |
||||
|
MemoryRegionExtents heap_extents{}; |
||||
|
MemoryRegionExtents alias_extents{}; |
||||
|
MemoryRegionExtents aslr_extents{}; |
||||
|
std::array<u8, 0x20> main_nso_build_id{}; |
||||
|
}; |
||||
|
static_assert(sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata is an invalid size"); |
||||
|
|
||||
|
struct CheatDefinition { |
||||
|
std::array<char, 0x40> readable_name; |
||||
|
u32 num_opcodes; |
||||
|
std::array<u32, 0x100> opcodes; |
||||
|
}; |
||||
|
static_assert(sizeof(CheatDefinition) == 0x444, "CheatDefinition is an invalid size"); |
||||
|
|
||||
|
struct CheatEntry { |
||||
|
bool enabled; |
||||
|
u32 cheat_id; |
||||
|
CheatDefinition definition; |
||||
|
}; |
||||
|
static_assert(sizeof(CheatEntry) == 0x44C, "CheatEntry is an invalid size"); |
||||
|
static_assert(std::is_trivial_v<CheatEntry>, "CheatEntry type must be trivially copyable."); |
||||
|
|
||||
|
struct FrozenAddressValue { |
||||
|
u64 value; |
||||
|
u8 width; |
||||
|
}; |
||||
|
static_assert(sizeof(FrozenAddressValue) == 0x10, "FrozenAddressValue is an invalid size"); |
||||
|
|
||||
|
struct FrozenAddressEntry { |
||||
|
u64 address; |
||||
|
FrozenAddressValue value; |
||||
|
}; |
||||
|
static_assert(sizeof(FrozenAddressEntry) == 0x18, "FrozenAddressEntry is an invalid size"); |
||||
|
} // namespace Service::DMNT |
||||
@ -1,286 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||
|
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||
|
|
||||
#include <locale>
|
|
||||
#include "common/hex_util.h"
|
|
||||
#include "common/swap.h"
|
|
||||
#include "core/arm/debug.h"
|
|
||||
#include "core/core.h"
|
|
||||
#include "core/core_timing.h"
|
|
||||
#include "core/hle/kernel/k_page_table.h"
|
|
||||
#include "core/hle/kernel/k_process.h"
|
|
||||
#include "core/hle/kernel/k_process_page_table.h"
|
|
||||
#include "core/hle/kernel/svc_types.h"
|
|
||||
#include "core/hle/service/hid/hid_server.h"
|
|
||||
#include "core/hle/service/sm/sm.h"
|
|
||||
#include "core/memory.h"
|
|
||||
#include "core/memory/cheat_engine.h"
|
|
||||
#include "hid_core/resource_manager.h"
|
|
||||
#include "hid_core/resources/npad/npad.h"
|
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
namespace { |
|
||||
constexpr auto CHEAT_ENGINE_NS = std::chrono::nanoseconds{1000000000 / 12}; |
|
||||
|
|
||||
std::string_view ExtractName(std::size_t& out_name_size, std::string_view data, |
|
||||
std::size_t start_index, char match) { |
|
||||
auto end_index = start_index; |
|
||||
while (data[end_index] != match) { |
|
||||
++end_index; |
|
||||
if (end_index > data.size()) { |
|
||||
return {}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
out_name_size = end_index - start_index; |
|
||||
|
|
||||
// Clamp name if it's too big
|
|
||||
if (out_name_size > sizeof(CheatDefinition::readable_name)) { |
|
||||
end_index = start_index + sizeof(CheatDefinition::readable_name); |
|
||||
} |
|
||||
|
|
||||
return data.substr(start_index, end_index - start_index); |
|
||||
} |
|
||||
} // Anonymous namespace
|
|
||||
|
|
||||
StandardVmCallbacks::StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_) |
|
||||
: metadata{metadata_}, system{system_} {} |
|
||||
|
|
||||
StandardVmCallbacks::~StandardVmCallbacks() = default; |
|
||||
|
|
||||
void StandardVmCallbacks::MemoryReadUnsafe(VAddr address, void* data, u64 size) { |
|
||||
// Return zero on invalid address
|
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { |
|
||||
std::memset(data, 0, size); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
system.ApplicationMemory().ReadBlock(address, data, size); |
|
||||
} |
|
||||
|
|
||||
void StandardVmCallbacks::MemoryWriteUnsafe(VAddr address, const void* data, u64 size) { |
|
||||
// Skip invalid memory write address
|
|
||||
if (!IsAddressInRange(address) || !system.ApplicationMemory().IsValidVirtualAddress(address)) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
if (system.ApplicationMemory().WriteBlock(address, data, size)) { |
|
||||
Core::InvalidateInstructionCacheRange(system.ApplicationProcess(), address, size); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
u64 StandardVmCallbacks::HidKeysDown() { |
|
||||
const auto hid = system.ServiceManager().GetService<Service::HID::IHidServer>("hid"); |
|
||||
if (hid == nullptr) { |
|
||||
LOG_WARNING(CheatEngine, "Attempted to read input state, but hid is not initialized!"); |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
const auto applet_resource = hid->GetResourceManager(); |
|
||||
if (applet_resource == nullptr || applet_resource->GetNpad() == nullptr) { |
|
||||
LOG_WARNING(CheatEngine, |
|
||||
"Attempted to read input state, but applet resource is not initialized!"); |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
const auto press_state = applet_resource->GetNpad()->GetAndResetPressState(); |
|
||||
return static_cast<u64>(press_state & HID::NpadButton::All); |
|
||||
} |
|
||||
|
|
||||
void StandardVmCallbacks::PauseProcess() { |
|
||||
if (system.ApplicationProcess()->IsSuspended()) { |
|
||||
return; |
|
||||
} |
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Paused); |
|
||||
} |
|
||||
|
|
||||
void StandardVmCallbacks::ResumeProcess() { |
|
||||
if (!system.ApplicationProcess()->IsSuspended()) { |
|
||||
return; |
|
||||
} |
|
||||
system.ApplicationProcess()->SetActivity(Kernel::Svc::ProcessActivity::Runnable); |
|
||||
} |
|
||||
|
|
||||
void StandardVmCallbacks::DebugLog(u8 id, u64 value) { |
|
||||
LOG_INFO(CheatEngine, "Cheat triggered DebugLog: ID '{:01X}' Value '{:016X}'", id, value); |
|
||||
} |
|
||||
|
|
||||
void StandardVmCallbacks::CommandLog(std::string_view data) { |
|
||||
LOG_DEBUG(CheatEngine, "[DmntCheatVm]: {}", |
|
||||
data.back() == '\n' ? data.substr(0, data.size() - 1) : data); |
|
||||
} |
|
||||
|
|
||||
bool StandardVmCallbacks::IsAddressInRange(VAddr in) const { |
|
||||
if ((in < metadata.main_nso_extents.base || |
|
||||
in >= metadata.main_nso_extents.base + metadata.main_nso_extents.size) && |
|
||||
(in < metadata.heap_extents.base || |
|
||||
in >= metadata.heap_extents.base + metadata.heap_extents.size) && |
|
||||
(in < metadata.alias_extents.base || |
|
||||
in >= metadata.alias_extents.base + metadata.alias_extents.size) && |
|
||||
(in < metadata.aslr_extents.base || |
|
||||
in >= metadata.aslr_extents.base + metadata.aslr_extents.size)) { |
|
||||
LOG_DEBUG(CheatEngine, |
|
||||
"Cheat attempting to access memory at invalid address={:016X}, if this " |
|
||||
"persists, " |
|
||||
"the cheat may be incorrect. However, this may be normal early in execution if " |
|
||||
"the game has not properly set up yet.", |
|
||||
in); |
|
||||
return false; ///< Invalid addresses will hard crash
|
|
||||
} |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
CheatParser::~CheatParser() = default; |
|
||||
|
|
||||
TextCheatParser::~TextCheatParser() = default; |
|
||||
|
|
||||
std::vector<CheatEntry> TextCheatParser::Parse(std::string_view data) const { |
|
||||
std::vector<CheatEntry> out(1); |
|
||||
std::optional<u64> current_entry; |
|
||||
|
|
||||
for (std::size_t i = 0; i < data.size(); ++i) { |
|
||||
if (::isspace(data[i])) { |
|
||||
continue; |
|
||||
} |
|
||||
|
|
||||
if (data[i] == '{') { |
|
||||
current_entry = 0; |
|
||||
|
|
||||
if (out[*current_entry].definition.num_opcodes > 0) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
std::size_t name_size{}; |
|
||||
const auto name = ExtractName(name_size, data, i + 1, '}'); |
|
||||
if (name.empty()) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), |
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), |
|
||||
name.size())); |
|
||||
out[*current_entry] |
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = |
|
||||
'\0'; |
|
||||
|
|
||||
i += name_size + 1; |
|
||||
} else if (data[i] == '[') { |
|
||||
current_entry = out.size(); |
|
||||
out.emplace_back(); |
|
||||
|
|
||||
std::size_t name_size{}; |
|
||||
const auto name = ExtractName(name_size, data, i + 1, ']'); |
|
||||
if (name.empty()) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
std::memcpy(out[*current_entry].definition.readable_name.data(), name.data(), |
|
||||
std::min<std::size_t>(out[*current_entry].definition.readable_name.size(), |
|
||||
name.size())); |
|
||||
out[*current_entry] |
|
||||
.definition.readable_name[out[*current_entry].definition.readable_name.size() - 1] = |
|
||||
'\0'; |
|
||||
|
|
||||
i += name_size + 1; |
|
||||
} else if (::isxdigit(data[i])) { |
|
||||
if (!current_entry || out[*current_entry].definition.num_opcodes >= |
|
||||
out[*current_entry].definition.opcodes.size()) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
const auto hex = std::string(data.substr(i, 8)); |
|
||||
if (!std::all_of(hex.begin(), hex.end(), ::isxdigit)) { |
|
||||
return {}; |
|
||||
} |
|
||||
|
|
||||
const auto value = static_cast<u32>(std::strtoul(hex.c_str(), nullptr, 0x10)); |
|
||||
out[*current_entry].definition.opcodes[out[*current_entry].definition.num_opcodes++] = |
|
||||
value; |
|
||||
|
|
||||
i += 8; |
|
||||
} else { |
|
||||
return {}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
out[0].enabled = out[0].definition.num_opcodes > 0; |
|
||||
out[0].cheat_id = 0; |
|
||||
|
|
||||
for (u32 i = 1; i < out.size(); ++i) { |
|
||||
out[i].enabled = out[i].definition.num_opcodes > 0; |
|
||||
out[i].cheat_id = i; |
|
||||
} |
|
||||
|
|
||||
return out; |
|
||||
} |
|
||||
|
|
||||
CheatEngine::CheatEngine(System& system_, std::vector<CheatEntry> cheats_, |
|
||||
const std::array<u8, 0x20>& build_id_) |
|
||||
: vm{std::make_unique<StandardVmCallbacks>(system_, metadata)}, |
|
||||
cheats(std::move(cheats_)), core_timing{system_.CoreTiming()}, system{system_} { |
|
||||
metadata.main_nso_build_id = build_id_; |
|
||||
} |
|
||||
|
|
||||
CheatEngine::~CheatEngine() { |
|
||||
core_timing.UnscheduleEvent(event); |
|
||||
} |
|
||||
|
|
||||
void CheatEngine::Initialize() { |
|
||||
event = Core::Timing::CreateEvent( |
|
||||
"CheatEngine::FrameCallback::" + Common::HexToString(metadata.main_nso_build_id), |
|
||||
[this](s64 time, |
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { |
|
||||
FrameCallback(ns_late); |
|
||||
return std::nullopt; |
|
||||
}); |
|
||||
core_timing.ScheduleLoopingEvent(CHEAT_ENGINE_NS, CHEAT_ENGINE_NS, event); |
|
||||
|
|
||||
metadata.process_id = system.ApplicationProcess()->GetProcessId(); |
|
||||
metadata.title_id = system.GetApplicationProcessProgramID(); |
|
||||
|
|
||||
const auto& page_table = system.ApplicationProcess()->GetPageTable(); |
|
||||
metadata.heap_extents = { |
|
||||
.base = GetInteger(page_table.GetHeapRegionStart()), |
|
||||
.size = page_table.GetHeapRegionSize(), |
|
||||
}; |
|
||||
metadata.aslr_extents = { |
|
||||
.base = GetInteger(page_table.GetAliasCodeRegionStart()), |
|
||||
.size = page_table.GetAliasCodeRegionSize(), |
|
||||
}; |
|
||||
metadata.alias_extents = { |
|
||||
.base = GetInteger(page_table.GetAliasRegionStart()), |
|
||||
.size = page_table.GetAliasRegionSize(), |
|
||||
}; |
|
||||
|
|
||||
is_pending_reload.exchange(true); |
|
||||
} |
|
||||
|
|
||||
void CheatEngine::SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size) { |
|
||||
metadata.main_nso_extents = { |
|
||||
.base = main_region_begin, |
|
||||
.size = main_region_size, |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
void CheatEngine::Reload(std::vector<CheatEntry> reload_cheats) { |
|
||||
cheats = std::move(reload_cheats); |
|
||||
is_pending_reload.exchange(true); |
|
||||
} |
|
||||
|
|
||||
void CheatEngine::FrameCallback(std::chrono::nanoseconds ns_late) { |
|
||||
if (is_pending_reload.exchange(false)) { |
|
||||
vm.LoadProgram(cheats); |
|
||||
} |
|
||||
|
|
||||
if (vm.GetProgramSize() == 0) { |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
vm.Execute(metadata); |
|
||||
} |
|
||||
|
|
||||
} // namespace Core::Memory
|
|
||||
@ -1,88 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <atomic> |
|
||||
#include <chrono> |
|
||||
#include <memory> |
|
||||
#include <vector> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/memory/dmnt_cheat_types.h" |
|
||||
#include "core/memory/dmnt_cheat_vm.h" |
|
||||
|
|
||||
namespace Core { |
|
||||
class System; |
|
||||
} |
|
||||
|
|
||||
namespace Core::Timing { |
|
||||
class CoreTiming; |
|
||||
struct EventType; |
|
||||
} // namespace Core::Timing |
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
|
|
||||
class StandardVmCallbacks : public DmntCheatVm::Callbacks { |
|
||||
public: |
|
||||
StandardVmCallbacks(System& system_, const CheatProcessMetadata& metadata_); |
|
||||
~StandardVmCallbacks() override; |
|
||||
|
|
||||
void MemoryReadUnsafe(VAddr address, void* data, u64 size) override; |
|
||||
void MemoryWriteUnsafe(VAddr address, const void* data, u64 size) override; |
|
||||
u64 HidKeysDown() override; |
|
||||
void PauseProcess() override; |
|
||||
void ResumeProcess() override; |
|
||||
void DebugLog(u8 id, u64 value) override; |
|
||||
void CommandLog(std::string_view data) override; |
|
||||
|
|
||||
private: |
|
||||
bool IsAddressInRange(VAddr address) const; |
|
||||
|
|
||||
const CheatProcessMetadata& metadata; |
|
||||
Core::System& system; |
|
||||
}; |
|
||||
|
|
||||
// Intermediary class that parses a text file or other disk format for storing cheats into a |
|
||||
// CheatList object, that can be used for execution. |
|
||||
class CheatParser { |
|
||||
public: |
|
||||
virtual ~CheatParser(); |
|
||||
|
|
||||
[[nodiscard]] virtual std::vector<CheatEntry> Parse(std::string_view data) const = 0; |
|
||||
}; |
|
||||
|
|
||||
// CheatParser implementation that parses text files |
|
||||
class TextCheatParser final : public CheatParser { |
|
||||
public: |
|
||||
~TextCheatParser() override; |
|
||||
|
|
||||
[[nodiscard]] std::vector<CheatEntry> Parse(std::string_view data) const override; |
|
||||
}; |
|
||||
|
|
||||
// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming |
|
||||
class CheatEngine final { |
|
||||
public: |
|
||||
CheatEngine(System& system_, std::vector<CheatEntry> cheats_, |
|
||||
const std::array<u8, 0x20>& build_id_); |
|
||||
~CheatEngine(); |
|
||||
|
|
||||
void Initialize(); |
|
||||
void SetMainMemoryParameters(VAddr main_region_begin, u64 main_region_size); |
|
||||
|
|
||||
void Reload(std::vector<CheatEntry> reload_cheats); |
|
||||
|
|
||||
private: |
|
||||
void FrameCallback(std::chrono::nanoseconds ns_late); |
|
||||
|
|
||||
DmntCheatVm vm; |
|
||||
CheatProcessMetadata metadata; |
|
||||
|
|
||||
std::vector<CheatEntry> cheats; |
|
||||
std::atomic_bool is_pending_reload{false}; |
|
||||
|
|
||||
std::shared_ptr<Core::Timing::EventType> event; |
|
||||
Core::Timing::CoreTiming& core_timing; |
|
||||
Core::System& system; |
|
||||
}; |
|
||||
|
|
||||
} // namespace Core::Memory |
|
||||
@ -1,37 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include "common/common_types.h" |
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
|
|
||||
struct MemoryRegionExtents { |
|
||||
u64 base{}; |
|
||||
u64 size{}; |
|
||||
}; |
|
||||
|
|
||||
struct CheatProcessMetadata { |
|
||||
u64 process_id{}; |
|
||||
u64 title_id{}; |
|
||||
MemoryRegionExtents main_nso_extents{}; |
|
||||
MemoryRegionExtents heap_extents{}; |
|
||||
MemoryRegionExtents alias_extents{}; |
|
||||
MemoryRegionExtents aslr_extents{}; |
|
||||
std::array<u8, 0x20> main_nso_build_id{}; |
|
||||
}; |
|
||||
|
|
||||
struct CheatDefinition { |
|
||||
std::array<char, 0x40> readable_name{}; |
|
||||
u32 num_opcodes{}; |
|
||||
std::array<u32, 0x100> opcodes{}; |
|
||||
}; |
|
||||
|
|
||||
struct CheatEntry { |
|
||||
bool enabled{}; |
|
||||
u32 cheat_id{}; |
|
||||
CheatDefinition definition{}; |
|
||||
}; |
|
||||
|
|
||||
} // namespace Core::Memory |
|
||||
1268
src/core/memory/dmnt_cheat_vm.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -1,330 +0,0 @@ |
|||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later |
|
||||
|
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project |
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later |
|
||||
|
|
||||
#pragma once |
|
||||
|
|
||||
#include <variant> |
|
||||
#include <vector> |
|
||||
#include <memory> |
|
||||
|
|
||||
#include <fmt/printf.h> |
|
||||
#include "common/common_types.h" |
|
||||
#include "core/memory/dmnt_cheat_types.h" |
|
||||
|
|
||||
namespace Core::Memory { |
|
||||
|
|
||||
enum class CheatVmOpcodeType : u32 { |
|
||||
StoreStatic = 0, |
|
||||
BeginConditionalBlock = 1, |
|
||||
EndConditionalBlock = 2, |
|
||||
ControlLoop = 3, |
|
||||
LoadRegisterStatic = 4, |
|
||||
LoadRegisterMemory = 5, |
|
||||
StoreStaticToAddress = 6, |
|
||||
PerformArithmeticStatic = 7, |
|
||||
BeginKeypressConditionalBlock = 8, |
|
||||
|
|
||||
// These are not implemented by Gateway's VM. |
|
||||
PerformArithmeticRegister = 9, |
|
||||
StoreRegisterToAddress = 10, |
|
||||
Reserved11 = 11, |
|
||||
|
|
||||
// This is a meta entry, and not a real opcode. |
|
||||
// This is to facilitate multi-nybble instruction decoding. |
|
||||
ExtendedWidth = 12, |
|
||||
|
|
||||
// Extended width opcodes. |
|
||||
BeginRegisterConditionalBlock = 0xC0, |
|
||||
SaveRestoreRegister = 0xC1, |
|
||||
SaveRestoreRegisterMask = 0xC2, |
|
||||
ReadWriteStaticRegister = 0xC3, |
|
||||
|
|
||||
// This is a meta entry, and not a real opcode. |
|
||||
// This is to facilitate multi-nybble instruction decoding. |
|
||||
DoubleExtendedWidth = 0xF0, |
|
||||
|
|
||||
// Double-extended width opcodes. |
|
||||
PauseProcess = 0xFF0, |
|
||||
ResumeProcess = 0xFF1, |
|
||||
DebugLog = 0xFFF, |
|
||||
}; |
|
||||
|
|
||||
enum class MemoryAccessType : u32 { |
|
||||
MainNso = 0, |
|
||||
Heap = 1, |
|
||||
Alias = 2, |
|
||||
Aslr = 3, |
|
||||
}; |
|
||||
|
|
||||
enum class ConditionalComparisonType : u32 { |
|
||||
GT = 1, |
|
||||
GE = 2, |
|
||||
LT = 3, |
|
||||
LE = 4, |
|
||||
EQ = 5, |
|
||||
NE = 6, |
|
||||
}; |
|
||||
|
|
||||
enum class RegisterArithmeticType : u32 { |
|
||||
Addition = 0, |
|
||||
Subtraction = 1, |
|
||||
Multiplication = 2, |
|
||||
LeftShift = 3, |
|
||||
RightShift = 4, |
|
||||
|
|
||||
// These are not supported by Gateway's VM. |
|
||||
LogicalAnd = 5, |
|
||||
LogicalOr = 6, |
|
||||
LogicalNot = 7, |
|
||||
LogicalXor = 8, |
|
||||
|
|
||||
None = 9, |
|
||||
}; |
|
||||
|
|
||||
enum class StoreRegisterOffsetType : u32 { |
|
||||
None = 0, |
|
||||
Reg = 1, |
|
||||
Imm = 2, |
|
||||
MemReg = 3, |
|
||||
MemImm = 4, |
|
||||
MemImmReg = 5, |
|
||||
}; |
|
||||
|
|
||||
enum class CompareRegisterValueType : u32 { |
|
||||
MemoryRelAddr = 0, |
|
||||
MemoryOfsReg = 1, |
|
||||
RegisterRelAddr = 2, |
|
||||
RegisterOfsReg = 3, |
|
||||
StaticValue = 4, |
|
||||
OtherRegister = 5, |
|
||||
}; |
|
||||
|
|
||||
enum class SaveRestoreRegisterOpType : u32 { |
|
||||
Restore = 0, |
|
||||
Save = 1, |
|
||||
ClearSaved = 2, |
|
||||
ClearRegs = 3, |
|
||||
}; |
|
||||
|
|
||||
enum class DebugLogValueType : u32 { |
|
||||
MemoryRelAddr = 0, |
|
||||
MemoryOfsReg = 1, |
|
||||
RegisterRelAddr = 2, |
|
||||
RegisterOfsReg = 3, |
|
||||
RegisterValue = 4, |
|
||||
}; |
|
||||
|
|
||||
union VmInt { |
|
||||
u8 bit8; |
|
||||
u16 bit16; |
|
||||
u32 bit32; |
|
||||
u64 bit64; |
|
||||
}; |
|
||||
|
|
||||
struct StoreStaticOpcode { |
|
||||
u32 bit_width{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
u32 offset_register{}; |
|
||||
u64 rel_address{}; |
|
||||
VmInt value{}; |
|
||||
}; |
|
||||
|
|
||||
struct BeginConditionalOpcode { |
|
||||
u32 bit_width{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
ConditionalComparisonType cond_type{}; |
|
||||
u64 rel_address{}; |
|
||||
VmInt value{}; |
|
||||
}; |
|
||||
|
|
||||
struct EndConditionalOpcode { |
|
||||
bool is_else; |
|
||||
}; |
|
||||
|
|
||||
struct ControlLoopOpcode { |
|
||||
bool start_loop{}; |
|
||||
u32 reg_index{}; |
|
||||
u32 num_iters{}; |
|
||||
}; |
|
||||
|
|
||||
struct LoadRegisterStaticOpcode { |
|
||||
u32 reg_index{}; |
|
||||
u64 value{}; |
|
||||
}; |
|
||||
|
|
||||
struct LoadRegisterMemoryOpcode { |
|
||||
u32 bit_width{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
u32 reg_index{}; |
|
||||
bool load_from_reg{}; |
|
||||
u64 rel_address{}; |
|
||||
}; |
|
||||
|
|
||||
struct StoreStaticToAddressOpcode { |
|
||||
u32 bit_width{}; |
|
||||
u32 reg_index{}; |
|
||||
bool increment_reg{}; |
|
||||
bool add_offset_reg{}; |
|
||||
u32 offset_reg_index{}; |
|
||||
u64 value{}; |
|
||||
}; |
|
||||
|
|
||||
struct PerformArithmeticStaticOpcode { |
|
||||
u32 bit_width{}; |
|
||||
u32 reg_index{}; |
|
||||
RegisterArithmeticType math_type{}; |
|
||||
u32 value{}; |
|
||||
}; |
|
||||
|
|
||||
struct BeginKeypressConditionalOpcode { |
|
||||
u32 key_mask{}; |
|
||||
}; |
|
||||
|
|
||||
struct PerformArithmeticRegisterOpcode { |
|
||||
u32 bit_width{}; |
|
||||
RegisterArithmeticType math_type{}; |
|
||||
u32 dst_reg_index{}; |
|
||||
u32 src_reg_1_index{}; |
|
||||
u32 src_reg_2_index{}; |
|
||||
bool has_immediate{}; |
|
||||
VmInt value{}; |
|
||||
}; |
|
||||
|
|
||||
struct StoreRegisterToAddressOpcode { |
|
||||
u32 bit_width{}; |
|
||||
u32 str_reg_index{}; |
|
||||
u32 addr_reg_index{}; |
|
||||
bool increment_reg{}; |
|
||||
StoreRegisterOffsetType ofs_type{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
u32 ofs_reg_index{}; |
|
||||
u64 rel_address{}; |
|
||||
}; |
|
||||
|
|
||||
struct BeginRegisterConditionalOpcode { |
|
||||
u32 bit_width{}; |
|
||||
ConditionalComparisonType cond_type{}; |
|
||||
u32 val_reg_index{}; |
|
||||
CompareRegisterValueType comp_type{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
u32 addr_reg_index{}; |
|
||||
u32 other_reg_index{}; |
|
||||
u32 ofs_reg_index{}; |
|
||||
u64 rel_address{}; |
|
||||
VmInt value{}; |
|
||||
}; |
|
||||
|
|
||||
struct SaveRestoreRegisterOpcode { |
|
||||
u32 dst_index{}; |
|
||||
u32 src_index{}; |
|
||||
SaveRestoreRegisterOpType op_type{}; |
|
||||
}; |
|
||||
|
|
||||
struct SaveRestoreRegisterMaskOpcode { |
|
||||
SaveRestoreRegisterOpType op_type{}; |
|
||||
std::array<bool, 0x10> should_operate{}; |
|
||||
}; |
|
||||
|
|
||||
struct ReadWriteStaticRegisterOpcode { |
|
||||
u32 static_idx{}; |
|
||||
u32 idx{}; |
|
||||
}; |
|
||||
|
|
||||
struct PauseProcessOpcode {}; |
|
||||
|
|
||||
struct ResumeProcessOpcode {}; |
|
||||
|
|
||||
struct DebugLogOpcode { |
|
||||
u32 bit_width{}; |
|
||||
u32 log_id{}; |
|
||||
DebugLogValueType val_type{}; |
|
||||
MemoryAccessType mem_type{}; |
|
||||
u32 addr_reg_index{}; |
|
||||
u32 val_reg_index{}; |
|
||||
u32 ofs_reg_index{}; |
|
||||
u64 rel_address{}; |
|
||||
}; |
|
||||
|
|
||||
struct UnrecognizedInstruction { |
|
||||
CheatVmOpcodeType opcode{}; |
|
||||
}; |
|
||||
|
|
||||
struct CheatVmOpcode { |
|
||||
bool begin_conditional_block{}; |
|
||||
std::variant<StoreStaticOpcode, BeginConditionalOpcode, EndConditionalOpcode, ControlLoopOpcode, |
|
||||
LoadRegisterStaticOpcode, LoadRegisterMemoryOpcode, StoreStaticToAddressOpcode, |
|
||||
PerformArithmeticStaticOpcode, BeginKeypressConditionalOpcode, |
|
||||
PerformArithmeticRegisterOpcode, StoreRegisterToAddressOpcode, |
|
||||
BeginRegisterConditionalOpcode, SaveRestoreRegisterOpcode, |
|
||||
SaveRestoreRegisterMaskOpcode, ReadWriteStaticRegisterOpcode, PauseProcessOpcode, |
|
||||
ResumeProcessOpcode, DebugLogOpcode, UnrecognizedInstruction> |
|
||||
opcode{}; |
|
||||
}; |
|
||||
|
|
||||
class DmntCheatVm { |
|
||||
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; |
|
||||
static constexpr std::size_t NumWritableStaticRegisters = 0x80; |
|
||||
static constexpr std::size_t NumStaticRegisters = |
|
||||
NumReadableStaticRegisters + NumWritableStaticRegisters; |
|
||||
|
|
||||
explicit DmntCheatVm(std::unique_ptr<Callbacks> callbacks_); |
|
||||
~DmntCheatVm(); |
|
||||
|
|
||||
std::size_t GetProgramSize() const { |
|
||||
return this->num_opcodes; |
|
||||
} |
|
||||
|
|
||||
bool LoadProgram(const std::vector<CheatEntry>& cheats); |
|
||||
void Execute(const CheatProcessMetadata& metadata); |
|
||||
|
|
||||
private: |
|
||||
std::unique_ptr<Callbacks> callbacks; |
|
||||
|
|
||||
std::size_t num_opcodes = 0; |
|
||||
std::size_t instruction_ptr = 0; |
|
||||
std::size_t condition_depth = 0; |
|
||||
bool decode_success = false; |
|
||||
std::array<u32, MaximumProgramOpcodeCount> program{}; |
|
||||
std::array<u64, NumRegisters> registers{}; |
|
||||
std::array<u64, NumRegisters> saved_values{}; |
|
||||
std::array<u64, NumStaticRegisters> static_registers{}; |
|
||||
std::array<std::size_t, NumRegisters> loop_tops{}; |
|
||||
|
|
||||
bool DecodeNextOpcode(CheatVmOpcode& out); |
|
||||
void SkipConditionalBlock(bool is_if); |
|
||||
void ResetState(); |
|
||||
|
|
||||
// For implementing the DebugLog opcode. |
|
||||
void DebugLog(u32 log_id, u64 value); |
|
||||
|
|
||||
void LogOpcode(const CheatVmOpcode& opcode); |
|
||||
|
|
||||
static u64 GetVmInt(VmInt value, u32 bit_width); |
|
||||
static u64 GetCheatProcessAddress(const CheatProcessMetadata& metadata, |
|
||||
MemoryAccessType mem_type, u64 rel_address); |
|
||||
}; |
|
||||
|
|
||||
}; // namespace Core::Memory |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue