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