diff --git a/stratosphere/dmnt/Makefile b/stratosphere/dmnt/Makefile index 71c014dc5..61cc0123e 100644 --- a/stratosphere/dmnt/Makefile +++ b/stratosphere/dmnt/Makefile @@ -26,7 +26,7 @@ endif #--------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := source +SOURCES := source source/cheat source/cheat/impl DATA := data INCLUDES := include ../../common/include EXEFS_SRC := exefs_src diff --git a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp new file mode 100644 index 000000000..e0e5956fe --- /dev/null +++ b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "dmnt_cheat_service.hpp" +#include "impl/dmnt_cheat_api.hpp" + +namespace sts::dmnt::cheat { + + /* ========================================================================================= */ + /* ==================================== Meta Commands ==================================== */ + /* ========================================================================================= */ + + void CheatService::HasCheatProcess(Out out) { + out.SetValue(dmnt::cheat::impl::GetHasActiveCheatProcess()); + } + + void CheatService::GetCheatProcessEvent(Out out_event) { + out_event.SetValue(dmnt::cheat::impl::GetCheatProcessEventHandle()); + } + + Result CheatService::GetCheatProcessMetadata(Out out_metadata) { + return dmnt::cheat::impl::GetCheatProcessMetadata(out_metadata.GetPointer()); + } + + Result CheatService::ForceOpenCheatProcess() { + if (R_FAILED(dmnt::cheat::impl::ForceOpenCheatProcess())) { + return ResultDmntCheatNotAttached; + } + + return ResultSuccess; + } + + /* ========================================================================================= */ + /* =================================== Memory Commands =================================== */ + /* ========================================================================================= */ + + Result CheatService::GetCheatProcessMappingCount(Out out_count) { + return dmnt::cheat::impl::GetCheatProcessMappingCount(out_count.GetPointer()); + } + + Result CheatService::GetCheatProcessMappings(OutBuffer mappings, Out out_count, u64 offset) { + if (mappings.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + return dmnt::cheat::impl::GetCheatProcessMappings(mappings.buffer, mappings.num_elements, out_count.GetPointer(), offset); + } + + Result CheatService::ReadCheatProcessMemory(OutBuffer buffer, u64 address, u64 out_size) { + if (buffer.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + return dmnt::cheat::impl::ReadCheatProcessMemory(address, buffer.buffer, std::min(out_size, buffer.num_elements)); + } + + Result CheatService::WriteCheatProcessMemory(InBuffer buffer, u64 address, u64 in_size) { + if (buffer.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + return dmnt::cheat::impl::WriteCheatProcessMemory(address, buffer.buffer, std::min(in_size, buffer.num_elements)); + } + + Result CheatService::QueryCheatProcessMemory(Out mapping, u64 address) { + return dmnt::cheat::impl::QueryCheatProcessMemory(mapping.GetPointer(), address); + } + + /* ========================================================================================= */ + /* =================================== Cheat Commands ==================================== */ + /* ========================================================================================= */ + + Result CheatService::GetCheatCount(Out out_count) { + return dmnt::cheat::impl::GetCheatCount(out_count.GetPointer()); + } + + Result CheatService::GetCheats(OutBuffer cheats, Out out_count, u64 offset) { + if (cheats.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + return dmnt::cheat::impl::GetCheats(cheats.buffer, cheats.num_elements, out_count.GetPointer(), offset); + } + + Result CheatService::GetCheatById(OutBuffer cheat, u32 cheat_id) { + if (cheat.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + if (cheat.num_elements < 1) { + return ResultDmntCheatInvalidBuffer; + } + + return dmnt::cheat::impl::GetCheatById(cheat.buffer, cheat_id); + } + + Result CheatService::ToggleCheat(u32 cheat_id) { + return dmnt::cheat::impl::ToggleCheat(cheat_id); + } + + Result CheatService::AddCheat(InBuffer cheat, Out out_cheat_id, bool enabled) { + if (cheat.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + if (cheat.num_elements < 1) { + return ResultDmntCheatInvalidBuffer; + } + + return dmnt::cheat::impl::AddCheat(out_cheat_id.GetPointer(), cheat.buffer, enabled); + } + + Result CheatService::RemoveCheat(u32 cheat_id) { + return dmnt::cheat::impl::RemoveCheat(cheat_id); + } + + /* ========================================================================================= */ + /* =================================== Address Commands ================================== */ + /* ========================================================================================= */ + + Result CheatService::GetFrozenAddressCount(Out out_count) { + return dmnt::cheat::impl::GetFrozenAddressCount(out_count.GetPointer()); + } + + Result CheatService::GetFrozenAddresses(OutBuffer frz_addrs, Out out_count, u64 offset) { + if (frz_addrs.buffer == nullptr) { + return ResultDmntCheatNullBuffer; + } + + return dmnt::cheat::impl::GetFrozenAddresses(frz_addrs.buffer, frz_addrs.num_elements, out_count.GetPointer(), offset); + } + + Result CheatService::GetFrozenAddress(Out entry, u64 address) { + return dmnt::cheat::impl::GetFrozenAddress(entry.GetPointer(), address); + } + + Result CheatService::EnableFrozenAddress(Out out_value, u64 address, u64 width) { + switch (width) { + case 1: + case 2: + case 4: + case 8: + break; + default: + return ResultDmntCheatInvalidFreezeWidth; + } + + return dmnt::cheat::impl::EnableFrozenAddress(out_value.GetPointer(), address, width); + } + + Result CheatService::DisableFrozenAddress(u64 address) { + return dmnt::cheat::impl::DisableFrozenAddress(address); + } + +} diff --git a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp new file mode 100644 index 000000000..2cb29cc6b --- /dev/null +++ b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace sts::dmnt::cheat { + + class CheatService final : public IServiceObject { + private: + enum class CommandId { + /* Meta */ + HasCheatProcess = 65000, + GetCheatProcessEvent = 65001, + GetCheatProcessMetadata = 65002, + ForceOpenCheatProcess = 65003, + + /* Interact with Memory */ + GetCheatProcessMappingCount = 65100, + GetCheatProcessMappings = 65101, + ReadCheatProcessMemory = 65102, + WriteCheatProcessMemory = 65103, + QueryCheatProcessMemory = 65104, + + /* Interact with Cheats */ + GetCheatCount = 65200, + GetCheats = 65201, + GetCheatById = 65202, + ToggleCheat = 65203, + AddCheat = 65204, + RemoveCheat = 65205, + + /* Interact with Frozen Addresses */ + GetFrozenAddressCount = 65300, + GetFrozenAddresses = 65301, + GetFrozenAddress = 65302, + EnableFrozenAddress = 65303, + DisableFrozenAddress = 65304, + }; + private: + void HasCheatProcess(Out out); + void GetCheatProcessEvent(Out out_event); + Result GetCheatProcessMetadata(Out out_metadata); + Result ForceOpenCheatProcess(); + + Result GetCheatProcessMappingCount(Out out_count); + Result GetCheatProcessMappings(OutBuffer mappings, Out out_count, u64 offset); + Result ReadCheatProcessMemory(OutBuffer buffer, u64 address, u64 out_size); + Result WriteCheatProcessMemory(InBuffer buffer, u64 address, u64 in_size); + Result QueryCheatProcessMemory(Out mapping, u64 address); + + Result GetCheatCount(Out out_count); + Result GetCheats(OutBuffer cheats, Out out_count, u64 offset); + Result GetCheatById(OutBuffer cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + Result AddCheat(InBuffer cheat, Out out_cheat_id, bool enabled); + Result RemoveCheat(u32 cheat_id); + + Result GetFrozenAddressCount(Out out_count); + Result GetFrozenAddresses(OutBuffer addresses, Out out_count, u64 offset); + Result GetFrozenAddress(Out entry, u64 address); + Result EnableFrozenAddress(Out out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(CheatService, HasCheatProcess), + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessEvent), + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMetadata), + MAKE_SERVICE_COMMAND_META(CheatService, ForceOpenCheatProcess), + + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMappingCount), + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatProcessMappings), + MAKE_SERVICE_COMMAND_META(CheatService, ReadCheatProcessMemory), + MAKE_SERVICE_COMMAND_META(CheatService, WriteCheatProcessMemory), + MAKE_SERVICE_COMMAND_META(CheatService, QueryCheatProcessMemory), + + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatCount), + MAKE_SERVICE_COMMAND_META(CheatService, GetCheats), + MAKE_SERVICE_COMMAND_META(CheatService, GetCheatById), + MAKE_SERVICE_COMMAND_META(CheatService, ToggleCheat), + MAKE_SERVICE_COMMAND_META(CheatService, AddCheat), + MAKE_SERVICE_COMMAND_META(CheatService, RemoveCheat), + + MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddressCount), + MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddresses), + MAKE_SERVICE_COMMAND_META(CheatService, GetFrozenAddress), + MAKE_SERVICE_COMMAND_META(CheatService, EnableFrozenAddress), + MAKE_SERVICE_COMMAND_META(CheatService, DisableFrozenAddress), + }; + }; + +} diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp new file mode 100644 index 000000000..c315b8dec --- /dev/null +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp @@ -0,0 +1,1084 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "dmnt_cheat_api.hpp" +#include "dmnt_cheat_vm.hpp" +#include "dmnt_cheat_debug_events_manager.hpp" +#include + +namespace sts::dmnt::cheat::impl { + + namespace { + + /* Helper definitions. */ + constexpr size_t MaxCheatCount = 0x80; + constexpr size_t MaxFrozenAddressCount = 0x80; + + /* Manager class. */ + class CheatProcessManager { + private: + HosMutex cheat_lock; + HosSignal debug_events_signal; + HosThread detect_thread, debug_events_thread; + IEvent *cheat_process_event; + Handle cheat_process_debug_handle = INVALID_HANDLE; + CheatProcessMetadata cheat_process_metadata = {}; + + HosThread vm_thread; + bool needs_reload_vm = false; + CheatVirtualMachine cheat_vm; + + bool enable_cheats_by_default = true; + bool always_save_cheat_toggles = false; + bool should_save_cheat_toggles = false; + CheatEntry cheat_entries[MaxCheatCount]; + std::map frozen_addresses_map; + private: + static void DetectLaunchThread(void *_this); + static void VirtualMachineThread(void *_this); + static void DebugEventsThread(void *_this); + + Result AttachToApplicationProcess(bool on_process_launch); + + bool ParseCheats(const char *s, size_t len); + bool LoadCheats(const ncm::TitleId title_id, const u8 *build_id); + bool ParseCheatToggles(const char *s, size_t len); + bool LoadCheatToggles(const ncm::TitleId title_id); + void SaveCheatToggles(const ncm::TitleId title_id); + + bool GetNeedsReloadVm() const { + return this->needs_reload_vm; + } + + void SetNeedsReloadVm(bool reload) { + this->needs_reload_vm = reload; + } + + + void ResetCheatEntry(size_t i) { + if (i < MaxCheatCount) { + std::memset(&this->cheat_entries[i], 0, sizeof(this->cheat_entries[i])); + this->cheat_entries[i].cheat_id = i; + + this->SetNeedsReloadVm(true); + } + } + + void ResetAllCheatEntries() { + for (size_t i = 0; i < MaxCheatCount; i++) { + this->ResetCheatEntry(i); + } + } + + CheatEntry *GetCheatEntryById(size_t i) { + if (i < MaxCheatCount) { + return &this->cheat_entries[i]; + } + + return nullptr; + } + + CheatEntry *GetCheatEntryByReadableName(const char *readable_name) { + /* Check all non-master cheats for match. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (std::strncmp(this->cheat_entries[i].definition.readable_name, readable_name, sizeof(this->cheat_entries[i].definition.readable_name)) == 0) { + return &this->cheat_entries[i]; + } + } + + return nullptr; + } + + CheatEntry *GetFreeCheatEntry() { + /* Check all non-master cheats for availability. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (this->cheat_entries[i].definition.num_opcodes == 0) { + return &this->cheat_entries[i]; + } + } + + return nullptr; + } + + void CloseActiveCheatProcess() { + if (this->cheat_process_debug_handle != INVALID_HANDLE) { + /* Close resources. */ + R_ASSERT(svcCloseHandle(this->cheat_process_debug_handle)); + this->cheat_process_debug_handle = INVALID_HANDLE; + + /* Save cheat toggles. */ + if (this->always_save_cheat_toggles || this->should_save_cheat_toggles) { + this->SaveCheatToggles(this->cheat_process_metadata.title_id); + this->should_save_cheat_toggles = false; + } + + /* Clear metadata. */ + static_assert(std::is_podcheat_process_metadata)>::value, "CheatProcessMetadata definition!"); + std::memset(&this->cheat_process_metadata, 0, sizeof(this->cheat_process_metadata)); + + /* Clear cheat list. */ + this->ResetAllCheatEntries(); + + /* Clear frozen addresses. */ + this->frozen_addresses_map.clear(); + + /* Signal to our fans. */ + this->cheat_process_event->Signal(); + } + } + + bool HasActiveCheatProcess() { + /* Note: This function *MUST* be called only with the cheat lock held. */ + u64 tmp; + bool has_cheat_process = this->cheat_process_debug_handle != INVALID_HANDLE; + has_cheat_process &= R_SUCCEEDED(svcGetProcessId(&tmp, this->cheat_process_debug_handle)); + + if (!has_cheat_process) { + this->CloseActiveCheatProcess(); + } + + return has_cheat_process; + } + + Result EnsureCheatProcess() { + if (!this->HasActiveCheatProcess()) { + return ResultDmntCheatNotAttached; + } + return ResultSuccess; + } + + Handle GetCheatProcessHandle() const { + return this->cheat_process_debug_handle; + } + + Handle HookToCreateApplicationProcess() const { + Handle h = INVALID_HANDLE; + R_ASSERT(pm::dmnt::HookToCreateApplicationProcess(&h)); + return h; + } + + void StartProcess(u64 process_id) const { + R_ASSERT(pm::dmnt::StartProcess(process_id)); + } + + public: + CheatProcessManager() { + /* Create cheat process detection event. */ + this->cheat_process_event = CreateWriteOnlySystemEvent(); + + /* Learn whether we should enable cheats by default. */ + { + u8 en; + if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", &en, sizeof(en)))) { + this->enable_cheats_by_default = (en != 0); + } + + if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_always_save_cheat_toggles", &en, sizeof(en)))) { + this->always_save_cheat_toggles = (en != 0); + } + } + + /* Spawn application detection thread, spawn cheat vm thread. */ + R_ASSERT(this->detect_thread.Initialize(&CheatProcessManager::DetectLaunchThread, this, 0x4000, 39)); + R_ASSERT(this->vm_thread.Initialize(&CheatProcessManager::VirtualMachineThread, this, 0x4000, 48)); + R_ASSERT(this->debug_events_thread.Initialize(&CheatProcessManager::DebugEventsThread, this, 0x4000, 48)); + + /* Start threads. */ + R_ASSERT(this->detect_thread.Start()); + R_ASSERT(this->vm_thread.Start()); + R_ASSERT(this->debug_events_thread.Start()); + } + + bool GetHasActiveCheatProcess() { + std::scoped_lock lk(this->cheat_lock); + + return this->HasActiveCheatProcess(); + } + + Handle GetCheatProcessEventHandle() { + return this->cheat_process_event->GetHandle(); + } + + Result GetCheatProcessMetadata(CheatProcessMetadata *out) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + std::memcpy(out, &this->cheat_process_metadata, sizeof(*out)); + return ResultSuccess; + } + + Result ForceOpenCheatProcess() { + return this->AttachToApplicationProcess(false); + } + + Result ReadCheatProcessMemoryUnsafe(u64 proc_addr, void *out_data, size_t size) { + return svcReadDebugProcessMemory(out_data, this->GetCheatProcessHandle(), proc_addr, size); + } + + Result WriteCheatProcessMemoryUnsafe(u64 proc_addr, const void *data, size_t size) { + R_TRY(svcWriteDebugProcessMemory(this->GetCheatProcessHandle(), data, proc_addr, size)); + + for (auto& [address, value] : this->frozen_addresses_map) { + /* Map is ordered, so break when we can. */ + if (address >= proc_addr + size) { + break; + } + + /* Check if we need to write. */ + if (proc_addr <= address && address < proc_addr + size) { + const size_t offset = (address - proc_addr); + const size_t copy_size = std::min(sizeof(value.value), size - offset); + std::memcpy(&value.value, reinterpret_cast(reinterpret_cast(data) + offset), copy_size); + } + } + + return ResultSuccess; + } + + Result GetCheatProcessMappingCount(u64 *out_count) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + MemoryInfo mem_info; + u64 address = 0, count = 0; + do { + mem_info.perm = Perm_None; + u32 tmp; + if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, this->GetCheatProcessHandle(), address))) { + break; + } + + if (mem_info.perm != Perm_None) { + count++; + } + + address = mem_info.addr + mem_info.size; + } while (address != 0); + + *out_count = count; + return ResultSuccess; + } + + Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + MemoryInfo mem_info; + u64 address = 0, total_count = 0, written_count = 0; + do { + mem_info.perm = Perm_None; + u32 tmp; + if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, this->GetCheatProcessHandle(), address))) { + break; + } + + if (mem_info.perm != Perm_None) { + if (offset <= total_count && written_count < max_count) { + mappings[written_count++] = mem_info; + } + total_count++; + } + + address = mem_info.addr + mem_info.size; + } while (address != 0 && written_count < max_count); + + *out_count = written_count; + return ResultSuccess; + } + + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + return this->ReadCheatProcessMemoryUnsafe(proc_addr, out_data, size); + } + + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + return this->WriteCheatProcessMemoryUnsafe(proc_addr, data, size); + } + + Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + u32 tmp; + return svcQueryDebugProcessMemory(mapping, &tmp, this->GetCheatProcessHandle(), address); + } + + Result GetCheatCount(u64 *out_count) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + size_t count = 0; + for (size_t i = 0; i < MaxCheatCount; i++) { + if (this->cheat_entries[i].definition.num_opcodes) { + count++; + } + } + + *out_count = count; + return ResultSuccess; + } + + Result GetCheats(CheatEntry *out_cheats, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + size_t count = 0, total_count = 0; + for (size_t i = 0; i < MaxCheatCount && count < max_count; i++) { + if (this->cheat_entries[i].definition.num_opcodes) { + total_count++; + if (total_count > offset) { + out_cheats[count++] = this->cheat_entries[i]; + } + } + } + + *out_count = count; + return ResultSuccess; + } + + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const CheatEntry *entry = this->GetCheatEntryById(cheat_id); + if (entry == nullptr || entry->definition.num_opcodes == 0) { + return ResultDmntCheatUnknownChtId; + } + + *out_cheat = *entry; + return ResultSuccess; + } + + Result ToggleCheat(u32 cheat_id) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + CheatEntry *entry = this->GetCheatEntryById(cheat_id); + if (entry == nullptr || entry->definition.num_opcodes == 0) { + return ResultDmntCheatUnknownChtId; + } + + if (cheat_id == 0) { + return ResultDmntCheatCannotDisableMasterCheat; + } + + entry->enabled = !entry->enabled; + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + return ResultSuccess; + } + + Result AddCheat(u32 *out_id, const CheatDefinition *def, bool enabled) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + if (def->num_opcodes == 0 || def->num_opcodes > util::size(def->opcodes)) { + return ResultDmntCheatInvalidCheat; + } + + CheatEntry *new_entry = this->GetFreeCheatEntry(); + if (new_entry == nullptr) { + return ResultDmntCheatOutOfCheats; + } + + new_entry->enabled = enabled; + new_entry->definition = *def; + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + return ResultSuccess; + } + + Result RemoveCheat(u32 cheat_id) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + if (cheat_id >= MaxCheatCount) { + return ResultDmntCheatUnknownChtId; + } + + this->ResetCheatEntry(cheat_id); + + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + return ResultSuccess; + } + + Result GetFrozenAddressCount(u64 *out_count) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + *out_count = this->frozen_addresses_map.size(); + return ResultSuccess; + } + + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + u64 total_count = 0, written_count = 0; + for (auto const& [address, value] : this->frozen_addresses_map) { + if (written_count >= max_count) { + break; + } + + if (offset <= total_count) { + frz_addrs[written_count].address = address; + frz_addrs[written_count].value = value; + written_count++; + } + total_count++; + } + + *out_count = written_count; + return ResultSuccess; + } + + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const auto it = this->frozen_addresses_map.find(address); + if (it == this->frozen_addresses_map.end()) { + return ResultDmntCheatAddressNotFrozen; + } + + frz_addr->address = it->first; + frz_addr->value = it->second; + return ResultSuccess; + } + + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + if (this->frozen_addresses_map.size() >= MaxFrozenAddressCount) { + return ResultDmntCheatTooManyFrozenAddresses; + } + + const auto it = this->frozen_addresses_map.find(address); + if (it != this->frozen_addresses_map.end()) { + return ResultDmntCheatAddressAlreadyFrozen; + } + + FrozenAddressValue value = {}; + value.width = width; + R_TRY(this->ReadCheatProcessMemoryUnsafe(address, &value.value, width)); + + this->frozen_addresses_map[address] = value; + *out_value = value.value; + return ResultSuccess; + } + + Result DisableFrozenAddress(u64 address) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + const auto it = this->frozen_addresses_map.find(address); + if (it == this->frozen_addresses_map.end()) { + return ResultDmntCheatAddressNotFrozen; + } + + this->frozen_addresses_map.erase(it); + return ResultSuccess; + } + + }; + + void CheatProcessManager::DetectLaunchThread(void *_this) { + CheatProcessManager *this_ptr = reinterpret_cast(_this); + Event hook; + while (true) { + eventLoadRemote(&hook, this_ptr->HookToCreateApplicationProcess(), true); + if (R_SUCCEEDED(eventWait(&hook, U64_MAX))) { + this_ptr->AttachToApplicationProcess(true); + } + eventClose(&hook); + } + } + + void CheatProcessManager::DebugEventsThread(void *_this) { + CheatProcessManager *this_ptr = reinterpret_cast(_this); + while (true) { + /* Atomically wait (and clear) signal for new process. */ + this_ptr->debug_events_signal.Wait(true); + while (R_SUCCEEDED(svcWaitSynchronizationSingle(this_ptr->GetCheatProcessHandle(), U64_MAX))) { + std::scoped_lock lk(this_ptr->cheat_lock); + + /* Handle any pending debug events. */ + if (this_ptr->HasActiveCheatProcess()) { + dmnt::cheat::impl::ContinueCheatProcess(this_ptr->GetCheatProcessHandle()); + } + } + } + } + + void CheatProcessManager::VirtualMachineThread(void *_this) { + CheatProcessManager *this_ptr = reinterpret_cast(_this); + while (true) { + /* Apply cheats. */ + { + std::scoped_lock lk(this_ptr->cheat_lock); + + if (this_ptr->HasActiveCheatProcess()) { + /* Execute VM. */ + if (!this_ptr->GetNeedsReloadVm() || this_ptr->cheat_vm.LoadProgram(this_ptr->cheat_entries, util::size(this_ptr->cheat_entries))) { + this_ptr->SetNeedsReloadVm(false); + + /* Execute program only if it has opcodes. */ + if (this_ptr->cheat_vm.GetProgramSize()) { + this_ptr->cheat_vm.Execute(&this_ptr->cheat_process_metadata); + } + } + + /* Apply frozen addresses. */ + for (auto const& [address, value] : this_ptr->frozen_addresses_map) { + /* Use Write SVC directly, to avoid the usual frozen address update logic. */ + svcWriteDebugProcessMemory(this_ptr->GetCheatProcessHandle(), &value.value, address, value.width); + } + } + } + + /* Sleep until next potential execution. */ + constexpr u64 ONE_SECOND = 1'000'000'000ul; + constexpr u64 TIMES_PER_SECOND = 12; + constexpr u64 DELAY_TIME = ONE_SECOND / TIMES_PER_SECOND; + svcSleepThread(DELAY_TIME); + } + } + + #define R_ASSERT_IF_NEW_PROCESS(res) \ + if (on_process_launch) { \ + R_ASSERT(res); \ + } else { \ + R_TRY(res); \ + } + + Result CheatProcessManager::AttachToApplicationProcess(bool on_process_launch) { + std::scoped_lock lk(this->cheat_lock); + + /* Close the active process, if needed. */ + { + if (this->HasActiveCheatProcess()) { + /* When forcing attach, we're done. */ + if (!on_process_launch) { + return ResultSuccess; + } + } + + /* Detach from the current process, if it's open. */ + this->CloseActiveCheatProcess(); + } + + /* Knock out the debug events thread. */ + R_ASSERT(this->debug_events_thread.CancelSynchronization()); + + /* Get the application process's ID. */ + R_ASSERT_IF_NEW_PROCESS(pm::dmnt::GetApplicationProcessId(&this->cheat_process_metadata.process_id)); + auto proc_guard = SCOPE_GUARD { + if (on_process_launch) { + this->StartProcess(this->cheat_process_metadata.process_id); + } + this->cheat_process_metadata.process_id = 0; + }; + + /* Get process handle, use it to learn memory extents. */ + { + Handle proc_h = INVALID_HANDLE; + ncm::TitleLocation loc = {}; + ON_SCOPE_EXIT { if (proc_h != INVALID_HANDLE) { R_ASSERT(svcCloseHandle(proc_h)); } }; + + R_ASSERT_IF_NEW_PROCESS(pm::dmnt::AtmosphereGetProcessInfo(&proc_h, &loc, this->cheat_process_metadata.process_id)); + this->cheat_process_metadata.title_id = loc.title_id; + + { + map::AddressSpaceInfo as_info; + R_ASSERT(map::GetProcessAddressSpaceInfo(&as_info, proc_h)); + this->cheat_process_metadata.heap_extents.base = as_info.heap_base; + this->cheat_process_metadata.heap_extents.size = as_info.heap_size; + this->cheat_process_metadata.alias_extents.base = as_info.alias_base; + this->cheat_process_metadata.alias_extents.size = as_info.alias_size; + this->cheat_process_metadata.aslr_extents.base = as_info.aslr_base; + this->cheat_process_metadata.aslr_extents.size = as_info.aslr_size; + } + } + + /* If new process launch, we may not want to actually attach. */ + if (on_process_launch) { + if (!cfg::IsCheatEnableKeyHeld(this->cheat_process_metadata.title_id)) { + return ResultDmntCheatNotAttached; + } + } + + /* Get module information from loader. */ + { + LoaderModuleInfo proc_modules[2]; + u32 num_modules; + + /* TODO: ldr::dmnt:: */ + R_ASSERT_IF_NEW_PROCESS(ldrDmntGetModuleInfos(this->cheat_process_metadata.process_id, proc_modules, util::size(proc_modules), &num_modules)); + + /* All applications must have two modules. */ + /* Only accept one (which means we're attaching to HBL) */ + /* if we aren't auto-attaching. */ + const LoaderModuleInfo *proc_module = nullptr; + if (num_modules == 2) { + proc_module = &proc_modules[1]; + } else if (num_modules == 1 && !on_process_launch) { + proc_module = &proc_modules[0]; + } else { + return ResultDmntCheatNotAttached; + } + + this->cheat_process_metadata.main_nso_extents.base = proc_module->base_address; + this->cheat_process_metadata.main_nso_extents.size = proc_module->size; + std::memcpy(this->cheat_process_metadata.main_nso_build_id, proc_module->build_id, sizeof(this->cheat_process_metadata.main_nso_build_id)); + } + + /* Read cheats off the SD. */ + if (!this->LoadCheats(this->cheat_process_metadata.title_id, this->cheat_process_metadata.main_nso_build_id) || + !this->LoadCheatToggles(this->cheat_process_metadata.title_id)) { + /* If new process launch, require success. */ + if (on_process_launch) { + return ResultDmntCheatNotAttached; + } + } + + /* Open a debug handle. */ + R_ASSERT_IF_NEW_PROCESS(svcDebugActiveProcess(&this->cheat_process_debug_handle, this->cheat_process_metadata.process_id)); + + /* Cancel process guard. */ + proc_guard.Cancel(); + + /* If new process, start the process. */ + if (on_process_launch) { + this->StartProcess(this->cheat_process_metadata.process_id); + } + + /* Signal to the debug events thread. */ + this->debug_events_signal.Signal(); + + /* Signal to our fans. */ + this->cheat_process_event->Signal(); + + return ResultSuccess; + } + + #undef R_ASSERT_IF_NEW_PROCESS + + bool CheatProcessManager::ParseCheats(const char *s, size_t len) { + /* Trigger a VM reload. */ + this->SetNeedsReloadVm(true); + + /* Parse the input string. */ + size_t i = 0; + CheatEntry *cur_entry = nullptr; + while (i < len) { + if (std::isspace(static_cast(s[i]))) { + /* Just ignore whitespace. */ + i++; + } else if (s[i] == '[') { + /* Parse a readable cheat name. */ + cur_entry = this->GetFreeCheatEntry(); + if (cur_entry == nullptr) { + return false; + } + + /* Extract name bounds. */ + size_t j = i + 1; + while (s[j] != ']') { + j++; + if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = (j - i - 1); + std::memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len); + cur_entry->definition.readable_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + } else if (s[i] == '{') { + /* We're parsing a master cheat. */ + cur_entry = &this->cheat_entries[0]; + + /* There can only be one master cheat. */ + if (cur_entry->definition.num_opcodes > 0) { + return false; + } + + /* Extract name bounds */ + size_t j = i + 1; + while (s[j] != '}') { + j++; + if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = (j - i - 1); + memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len); + cur_entry->definition.readable_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + } else if (std::isxdigit(static_cast(s[i]))) { + /* Make sure that we have a cheat open. */ + if (cur_entry == nullptr) { + return false; + } + + /* Bounds check the opcode count. */ + if (cur_entry->definition.num_opcodes >= util::size(cur_entry->definition.opcodes)) { + return false; + } + + /* We're parsing an instruction, so validate it's 8 hex digits. */ + for (size_t j = 1; j < 8; j++) { + /* Validate 8 hex chars. */ + if (i + j >= len || !std::isxdigit(static_cast(s[i+j]))) { + return false; + } + } + + /* Parse the new opcode. */ + char hex_str[9] = {0}; + std::memcpy(hex_str, &s[i], 8); + cur_entry->definition.opcodes[cur_entry->definition.num_opcodes++] = std::strtoul(hex_str, NULL, 16); + + /* Skip onwards. */ + i += 8; + } else { + /* Unexpected character encountered. */ + return false; + } + } + + /* Master cheat can't be disabled. */ + if (this->cheat_entries[0].definition.num_opcodes > 0) { + this->cheat_entries[0].enabled = true; + } + + /* Enable all entries we parsed. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (this->cheat_entries[i].definition.num_opcodes > 0) { + this->cheat_entries[i].enabled = this->enable_cheats_by_default; + } + } + + return true; + } + + bool CheatProcessManager::ParseCheatToggles(const char *s, size_t len) { + size_t i = 0; + char cur_cheat_name[sizeof(CheatEntry::definition.readable_name)]; + char toggle[8]; + + while (i < len) { + if (std::isspace(static_cast(s[i]))) { + /* Just ignore whitespace. */ + i++; + } else if (s[i] == '[') { + /* Extract name bounds. */ + size_t j = i + 1; + while (s[j] != ']') { + j++; + if (j >= len || (j - i - 1) >= sizeof(cur_cheat_name)) { + return false; + } + } + + /* s[i+1:j] is cheat name. */ + const size_t cheat_name_len = (j - i - 1); + std::memcpy(cur_cheat_name, &s[i+1], cheat_name_len); + cur_cheat_name[cheat_name_len] = 0; + + /* Skip onwards. */ + i = j + 1; + + /* Skip whitespace. */ + while (std::isspace(static_cast(s[i]))) { + i++; + } + + /* Parse whether to toggle. */ + j = i + 1; + while (!std::isspace(static_cast(s[j]))) { + j++; + if (j >= len || (j - i) >= sizeof(toggle)) { + return false; + } + } + + /* s[i:j] is toggle. */ + const size_t toggle_len = (j - i); + std::memcpy(toggle, &s[i], toggle_len); + toggle[toggle_len] = 0; + + /* Allow specifying toggle for not present cheat. */ + CheatEntry *entry = this->GetCheatEntryByReadableName(cur_cheat_name); + if (entry != nullptr) { + if (strcasecmp(toggle, "1") == 0 || strcasecmp(toggle, "true") == 0 || strcasecmp(toggle, "on") == 0) { + entry->enabled = true; + } else if (strcasecmp(toggle, "0") == 0 || strcasecmp(toggle, "false") == 0 || strcasecmp(toggle, "off") == 0) { + entry->enabled = false; + } + } + + /* Skip onwards. */ + i = j + 1; + } else { + /* Unexpected character encountered. */ + return false; + } + } + + return true; + } + + bool CheatProcessManager::LoadCheats(const ncm::TitleId title_id, const u8 *build_id) { + /* Reset existing entries. */ + this->ResetAllCheatEntries(); + + /* Open the file for title/build_id. */ + FILE *f_cht = nullptr; + { + char path[FS_MAX_PATH+1] = {0}; + std::snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/%02x%02x%02x%02x%02x%02x%02x%02x.txt", static_cast(title_id), + build_id[0], build_id[1], build_id[2], build_id[3], build_id[4], build_id[5], build_id[6], build_id[7]); + + f_cht = fopen(path, "rb"); + } + + /* Check for open failure. */ + if (f_cht == nullptr) { + return false; + } + ON_SCOPE_EXIT { fclose(f_cht); }; + + /* Get file size. */ + fseek(f_cht, 0L, SEEK_END); + const size_t cht_sz = ftell(f_cht); + fseek(f_cht, 0L, SEEK_SET); + + /* Allocate cheat txt buffer. */ + char *cht_txt = reinterpret_cast(std::malloc(cht_sz + 1)); + if (cht_txt == nullptr) { + return false; + } + ON_SCOPE_EXIT { std::free(cht_txt); }; + + /* Read cheats into buffer. */ + if (fread(cht_txt, 1, cht_sz, f_cht) != cht_sz) { + return false; + } + cht_txt[cht_sz] = 0; + + /* Parse cheat buffer. */ + return this->ParseCheats(cht_txt, std::strlen(cht_txt)); + } + + bool CheatProcessManager::LoadCheatToggles(const ncm::TitleId title_id) { + /* Open the file for title_id. */ + FILE *f_tg = nullptr; + { + char path[FS_MAX_PATH+1] = {0}; + std::snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", static_cast(title_id)); + f_tg = fopen(path, "rb"); + } + + /* Unless we successfully parse, don't save toggles on close. */ + this->should_save_cheat_toggles = false; + + /* Check for null, which is allowed. */ + if (f_tg == nullptr) { + return true; + } + ON_SCOPE_EXIT { fclose(f_tg); }; + + /* Get file size. */ + fseek(f_tg, 0L, SEEK_END); + const size_t tg_sz = ftell(f_tg); + fseek(f_tg, 0L, SEEK_SET); + + /* Allocate toggle txt buffer. */ + char *tg_txt = reinterpret_cast(std::malloc(tg_sz + 1)); + if (tg_txt == nullptr) { + return false; + } + ON_SCOPE_EXIT { std::free(tg_txt); }; + + /* Read cheats into buffer. */ + if (fread(tg_txt, 1, tg_sz, f_tg) != tg_sz) { + return false; + } + tg_txt[tg_sz] = 0; + + /* Parse toggle buffer. */ + this->should_save_cheat_toggles = this->ParseCheatToggles(tg_txt, std::strlen(tg_txt)); + return this->should_save_cheat_toggles; + } + + void CheatProcessManager::SaveCheatToggles(const ncm::TitleId title_id) { + /* Open the file for title_id. */ + FILE *f_tg = nullptr; + { + char path[FS_MAX_PATH+1] = {0}; + std::snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", static_cast(title_id)); + if ((f_tg = fopen(path, "wb")) == nullptr) { + return; + } + } + ON_SCOPE_EXIT { fclose(f_tg); }; + + /* Save all non-master cheats. */ + for (size_t i = 1; i < MaxCheatCount; i++) { + if (this->cheat_entries[i].definition.num_opcodes != 0) { + fprintf(f_tg, "[%s]\n", this->cheat_entries[i].definition.readable_name); + if (this->cheat_entries[i].enabled) { + fprintf(f_tg, "true\n"); + } else { + fprintf(f_tg, "false\n"); + } + } + } + } + + + /* Manager global. */ + CheatProcessManager g_cheat_process_manager; + + } + + bool GetHasActiveCheatProcess() { + return g_cheat_process_manager.GetHasActiveCheatProcess(); + } + + Handle GetCheatProcessEventHandle() { + return g_cheat_process_manager.GetCheatProcessEventHandle(); + } + + Result GetCheatProcessMetadata(CheatProcessMetadata *out) { + return g_cheat_process_manager.GetCheatProcessMetadata(out); + } + + Result ForceOpenCheatProcess() { + return g_cheat_process_manager.ForceOpenCheatProcess(); + } + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size) { + return g_cheat_process_manager.ReadCheatProcessMemoryUnsafe(process_addr, out_data, size); + } + + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size) { + return g_cheat_process_manager.WriteCheatProcessMemoryUnsafe(process_addr, data, size); + } + + Result GetCheatProcessMappingCount(u64 *out_count) { + return g_cheat_process_manager.GetCheatProcessMappingCount(out_count); + } + + Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) { + return g_cheat_process_manager.GetCheatProcessMappings(mappings, max_count, out_count, offset); + } + + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) { + return g_cheat_process_manager.ReadCheatProcessMemory(proc_addr, out_data, size); + } + + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) { + return g_cheat_process_manager.WriteCheatProcessMemory(proc_addr, data, size); + } + + Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address) { + return g_cheat_process_manager.QueryCheatProcessMemory(mapping, address); + } + + Result GetCheatCount(u64 *out_count) { + return g_cheat_process_manager.GetCheatCount(out_count); + } + + Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset) { + return g_cheat_process_manager.GetCheats(cheats, max_count, out_count, offset); + } + + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id) { + return g_cheat_process_manager.GetCheatById(out_cheat, cheat_id); + } + + Result ToggleCheat(u32 cheat_id) { + return g_cheat_process_manager.ToggleCheat(cheat_id); + } + + Result AddCheat(u32 *out_id, const CheatDefinition *def, bool enabled) { + return g_cheat_process_manager.AddCheat(out_id, def, enabled); + } + + Result RemoveCheat(u32 cheat_id) { + return g_cheat_process_manager.RemoveCheat(cheat_id); + } + + Result GetFrozenAddressCount(u64 *out_count) { + return g_cheat_process_manager.GetFrozenAddressCount(out_count); + } + + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) { + return g_cheat_process_manager.GetFrozenAddresses(frz_addrs, max_count, out_count, offset); + } + + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) { + return g_cheat_process_manager.GetFrozenAddress(frz_addr, address); + } + + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width) { + return g_cheat_process_manager.EnableFrozenAddress(out_value, address, width); + } + + Result DisableFrozenAddress(u64 address) { + return g_cheat_process_manager.DisableFrozenAddress(address); + } + +} diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp new file mode 100644 index 000000000..19d037c84 --- /dev/null +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace sts::dmnt::cheat::impl { + + bool GetHasActiveCheatProcess(); + Handle GetCheatProcessEventHandle(); + Result GetCheatProcessMetadata(CheatProcessMetadata *out); + Result ForceOpenCheatProcess(); + + Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size); + Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size); + + Result GetCheatProcessMappingCount(u64 *out_count); + Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset); + Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size); + Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size); + Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address); + + Result GetCheatCount(u64 *out_count); + Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset); + Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id); + Result ToggleCheat(u32 cheat_id); + Result AddCheat(u32 *out_id, const CheatDefinition *def, bool enabled); + Result RemoveCheat(u32 cheat_id); + + Result GetFrozenAddressCount(u64 *out_count); + Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset); + Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address); + Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width); + Result DisableFrozenAddress(u64 address); + +} diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp new file mode 100644 index 000000000..bf490010f --- /dev/null +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "dmnt_cheat_debug_events_manager.hpp" + +/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */ + +namespace sts::dmnt::cheat::impl { + + namespace { + + class DebugEventsManager { + public: + static constexpr size_t NumCores = 4; + private: + HosMessageQueue message_queues[NumCores]; + HosThread threads[NumCores]; + HosSignal continued_signal; + private: + static void PerCoreThreadFunction(void *_this) { + /* This thread will wait on the appropriate message queue. */ + DebugEventsManager *this_ptr = reinterpret_cast(_this); + const u32 current_core = svcGetCurrentProcessorNumber(); + while (true) { + /* Receive handle. */ + Handle debug_handle = this_ptr->WaitReceiveHandle(current_core); + + /* Continue events on the correct core. */ + R_ASSERT(this_ptr->ContinueDebugEvent(debug_handle)); + + /* Signal that we've continued. */ + this_ptr->SignalContinued(); + } + } + + u32 GetTargetCore(const svc::DebugEventInfo &dbg_event, Handle debug_handle) { + /* If we don't need to continue on a specific core, use the system core. */ + u32 target_core = NumCores - 1; + + /* Retrieve correct core for new thread event. */ + if (dbg_event.type == svc::DebugEventType::AttachThread) { + u64 out64 = 0; + u32 out32 = 0; + R_ASSERT(svcGetDebugThreadParam(&out64, &out32, debug_handle, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore)); + target_core = out32; + } + + return target_core; + } + + void SendHandle(const svc::DebugEventInfo &dbg_event, Handle debug_handle) { + this->message_queues[GetTargetCore(dbg_event, debug_handle)].Send(static_cast(debug_handle)); + } + + Handle WaitReceiveHandle(u32 core_id) { + uintptr_t x = 0; + this->message_queues[core_id].Receive(&x); + return static_cast(x); + } + + Result ContinueDebugEvent(Handle debug_handle) { + if (GetRuntimeFirmwareVersion() >= FirmwareVersion_300) { + return svcContinueDebugEvent(debug_handle, 5, nullptr, 0); + } else { + return svcLegacyContinueDebugEvent(debug_handle, 5, 0); + } + } + + void WaitContinued() { + this->continued_signal.Wait(); + this->continued_signal.Reset(); + } + + void SignalContinued() { + this->continued_signal.Signal(); + } + + public: + DebugEventsManager() : message_queues{HosMessageQueue(1), HosMessageQueue(1), HosMessageQueue(1), HosMessageQueue(1)} { + for (size_t i = 0; i < NumCores; i++) { + /* Create thread. */ + R_ASSERT(this->threads[i].Initialize(&DebugEventsManager::PerCoreThreadFunction, reinterpret_cast(this), 0x1000, 24, i)); + + /* Set core mask. */ + R_ASSERT(svcSetThreadCoreMask(this->threads[i].GetHandle(), i, (1u << i))); + + /* Start thread. */ + R_ASSERT(this->threads[i].Start()); + } + } + + void ContinueCheatProcess(Handle cheat_dbg_hnd) { + /* Loop getting all debug events. */ + svc::DebugEventInfo d; + while (R_SUCCEEDED(svcGetDebugEvent(reinterpret_cast(&d), cheat_dbg_hnd))) { + /* ... */ + } + + /* Send handle to correct core, wait for continue to finish. */ + this->SendHandle(d, cheat_dbg_hnd); + this->WaitContinued(); + } + }; + + /* Manager global. */ + DebugEventsManager g_events_manager; + + } + + void ContinueCheatProcess(Handle cheat_dbg_hnd) { + g_events_manager.ContinueCheatProcess(cheat_dbg_hnd); + } + +} diff --git a/stratosphere/dmnt/source/dmnt_config.hpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp similarity index 71% rename from stratosphere/dmnt/source/dmnt_config.hpp rename to stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp index c9a68b6f0..047ca3cdd 100644 --- a/stratosphere/dmnt/source/dmnt_config.hpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_debug_events_manager.hpp @@ -16,16 +16,10 @@ #pragma once #include +#include -struct OverrideKey { - u64 key_combination; - bool override_by_default; -}; +namespace sts::dmnt::cheat::impl { -class DmntConfigManager { - public: - static void RefreshConfiguration(); + void ContinueCheatProcess(Handle cheat_dbg_hnd); - static OverrideKey GetTitleCheatEnableKey(u64 tid); - static bool HasCheatEnableButton(u64 tid); -}; +} diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp new file mode 100644 index 000000000..9d889892b --- /dev/null +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp @@ -0,0 +1,1213 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include "dmnt_cheat_vm.hpp" +#include "dmnt_cheat_api.hpp" + +namespace sts::dmnt::cheat::impl { + + void CheatVirtualMachine::DebugLog(u32 log_id, u64 value) { + /* Just unconditionally try to create the log folder. */ + mkdir("/atmosphere/cheat_vm_logs", 0777); + FILE *f_log = NULL; + { + char log_path[FS_MAX_PATH]; + snprintf(log_path, sizeof(log_path), "/atmosphere/cheat_vm_logs/%x.log", log_id); + f_log = fopen(log_path, "ab"); + } + if (f_log != NULL) { + ON_SCOPE_EXIT { fclose(f_log); }; + fprintf(f_log, "%016lx\n", value); + } + } + + void CheatVirtualMachine::OpenDebugLogFile() { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + CloseDebugLogFile(); + this->debug_log_file = fopen("cheat_vm_log.txt", "ab"); + #endif + } + + void CheatVirtualMachine::CloseDebugLogFile() { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + if (this->debug_log_file != NULL) { + fclose(this->debug_log_file); + this->debug_log_file = NULL; + } + #endif + } + + void CheatVirtualMachine::LogToDebugFile(const char *format, ...) { + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + if (this->debug_log_file != NULL) { + va_list arglist; + va_start(arglist, format); + vfprintf(this->debug_log_file, format, arglist); + va_end(arglist); + } + #endif + } + + void CheatVirtualMachine::LogOpcode(const CheatVmOpcode *opcode) { + #ifndef DMNT_CHEAT_VM_DEBUG_LOG + return; + #endif + switch (opcode->opcode) { + case CheatVmOpcodeType_StoreStatic: + this->LogToDebugFile("Opcode: Store Static\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->store_static.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode->store_static.mem_type); + this->LogToDebugFile("Reg Idx: %x\n", opcode->store_static.offset_register); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->store_static.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode->store_static.value.bit64); + break; + case CheatVmOpcodeType_BeginConditionalBlock: + this->LogToDebugFile("Opcode: Begin Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type); + this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address); + this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64); + break; + case CheatVmOpcodeType_EndConditionalBlock: + this->LogToDebugFile("Opcode: End Conditional\n"); + break; + case CheatVmOpcodeType_ControlLoop: + if (opcode->ctrl_loop.start_loop) { + this->LogToDebugFile("Opcode: Start Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); + this->LogToDebugFile("Num Iters: %x\n", opcode->ctrl_loop.num_iters); + } else { + this->LogToDebugFile("Opcode: End Loop\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + this->LogToDebugFile("Opcode: Load Register Static\n"); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_static.reg_index); + this->LogToDebugFile("Value: %lx\n", opcode->ldr_static.value); + break; + case CheatVmOpcodeType_LoadRegisterMemory: + this->LogToDebugFile("Opcode: Load Register Memory\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->ldr_memory.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_memory.reg_index); + this->LogToDebugFile("Mem Type: %x\n", opcode->ldr_memory.mem_type); + this->LogToDebugFile("From Reg: %d\n", opcode->ldr_memory.load_from_reg); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->ldr_memory.rel_address); + break; + case CheatVmOpcodeType_StoreStaticToAddress: + this->LogToDebugFile("Opcode: Store Static to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->str_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->str_static.reg_index); + if (opcode->str_static.add_offset_reg) { + this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_static.offset_reg_index); + } + this->LogToDebugFile("Incr Reg: %d\n", opcode->str_static.increment_reg); + this->LogToDebugFile("Value: %lx\n", opcode->str_static.value); + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + this->LogToDebugFile("Opcode: Perform Static Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_static.bit_width); + this->LogToDebugFile("Reg Idx: %x\n", opcode->perform_math_static.reg_index); + this->LogToDebugFile("Math Type: %x\n", opcode->perform_math_static.math_type); + this->LogToDebugFile("Value: %lx\n", opcode->perform_math_static.value); + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + this->LogToDebugFile("Opcode: Begin Keypress Conditional\n"); + this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask); + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + this->LogToDebugFile("Opcode: Perform Register Arithmetic\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width); + this->LogToDebugFile("Dst Idx: %x\n", opcode->perform_math_reg.dst_reg_index); + this->LogToDebugFile("Src1 Idx: %x\n", opcode->perform_math_reg.src_reg_1_index); + if (opcode->perform_math_reg.has_immediate) { + this->LogToDebugFile("Value: %lx\n", opcode->perform_math_reg.value.bit64); + } else { + this->LogToDebugFile("Src2 Idx: %x\n", opcode->perform_math_reg.src_reg_2_index); + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + this->LogToDebugFile("Opcode: Store Register to Address\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->str_register.bit_width); + this->LogToDebugFile("S Reg Idx: %x\n", opcode->str_register.str_reg_index); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->str_register.addr_reg_index); + this->LogToDebugFile("Incr Reg: %d\n", opcode->str_register.increment_reg); + switch (opcode->str_register.ofs_type) { + case StoreRegisterOffsetType_None: + break; + case StoreRegisterOffsetType_Reg: + this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_register.ofs_reg_index); + break; + case StoreRegisterOffsetType_Imm: + this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); + break; + case StoreRegisterOffsetType_MemReg: + this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); + break; + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + this->LogToDebugFile("Opcode: Begin Register Conditional\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->begin_reg_cond.bit_width); + this->LogToDebugFile("Cond Type: %x\n", opcode->begin_reg_cond.cond_type); + this->LogToDebugFile("V Reg Idx: %x\n", opcode->begin_reg_cond.val_reg_index); + switch (opcode->begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + this->LogToDebugFile("Comp Type: Static Value\n"); + this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64); + break; + case CompareRegisterValueType_OtherRegister: + this->LogToDebugFile("Comp Type: Other Register\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index); + break; + case CompareRegisterValueType_MemoryRelAddr: + this->LogToDebugFile("Comp Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + this->LogToDebugFile("Comp Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + case CompareRegisterValueType_RegisterRelAddr: + this->LogToDebugFile("Comp Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_RegisterOfsReg: + this->LogToDebugFile("Comp Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + this->LogToDebugFile("Opcode: Save or Restore Register\n"); + this->LogToDebugFile("Dst Idx: %x\n", opcode->save_restore_reg.dst_index); + this->LogToDebugFile("Src Idx: %x\n", opcode->save_restore_reg.src_index); + this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_reg.op_type); + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + this->LogToDebugFile("Opcode: Save or Restore Register Mask\n"); + this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_regmask.op_type); + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Act[%02x]: %d\n", i, opcode->save_restore_regmask.should_operate[i]); + } + break; + case CheatVmOpcodeType_DebugLog: + this->LogToDebugFile("Opcode: Debug Log\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->debug_log.bit_width); + this->LogToDebugFile("Log ID: %x\n", opcode->debug_log.log_id); + this->LogToDebugFile("Val Type: %x\n", opcode->debug_log.val_type); + switch (opcode->debug_log.val_type) { + case DebugLogValueType_RegisterValue: + this->LogToDebugFile("Val Type: Register Value\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->debug_log.val_reg_index); + break; + case DebugLogValueType_MemoryRelAddr: + this->LogToDebugFile("Val Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + this->LogToDebugFile("Val Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + case DebugLogValueType_RegisterRelAddr: + this->LogToDebugFile("Val Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_RegisterOfsReg: + this->LogToDebugFile("Val Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + } + default: + this->LogToDebugFile("Unknown opcode: %x\n", opcode->opcode); + break; + } + } + + bool CheatVirtualMachine::DecodeNextOpcode(CheatVmOpcode *out) { + /* If we've ever seen a decode failure, return false. */ + bool valid = this->decode_success; + CheatVmOpcode opcode = {}; + ON_SCOPE_EXIT { + this->decode_success &= valid; + if (valid) { + *out = opcode; + } + }; + + /* Helper function for getting instruction dwords. */ + auto GetNextDword = [&]() { + if (this->instruction_ptr >= this->num_opcodes) { + valid = false; + return static_cast(0); + } + return this->program[this->instruction_ptr++]; + }; + + /* Helper function for parsing a VmInt. */ + auto GetNextVmInt = [&](const u32 bit_width) { + VmInt val = {0}; + + const u32 first_dword = GetNextDword(); + switch (bit_width) { + case 1: + val.bit8 = (u8)first_dword; + break; + case 2: + val.bit16 = (u16)first_dword; + break; + case 4: + val.bit32 = first_dword; + break; + case 8: + val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); + break; + } + + return val; + }; + + /* Read opcode. */ + const u32 first_dword = GetNextDword(); + if (!valid) { + return valid; + } + + opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF)); + if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) { + opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); + } + if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { + opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); + } + + /* detect condition start. */ + switch (opcode.opcode) { + case CheatVmOpcodeType_BeginConditionalBlock: + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + opcode.begin_conditional_block = true; + break; + default: + opcode.begin_conditional_block = false; + break; + } + + switch (opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: + { + /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.store_static.bit_width = (first_dword >> 24) & 0xF; + opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.store_static.offset_register = ((first_dword >> 16) & 0xF); + opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width); + } + break; + case CheatVmOpcodeType_BeginConditionalBlock: + { + /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF; + opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + opcode.begin_cond.value = GetNextVmInt(opcode.store_static.bit_width); + } + break; + case CheatVmOpcodeType_EndConditionalBlock: + { + /* 20000000 */ + /* There's actually nothing left to process here! */ + } + break; + case CheatVmOpcodeType_ControlLoop: + { + /* 300R0000 VVVVVVVV */ + /* 310R0000 */ + /* Parse register, whether loop start or loop end. */ + opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; + opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); + + /* Read number of iters if loop start. */ + if (opcode.ctrl_loop.start_loop) { + opcode.ctrl_loop.num_iters = GetNextDword(); + } + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + { + /* 400R0000 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } + break; + case CheatVmOpcodeType_LoadRegisterMemory: + { + /* 5TMRI0AA AAAAAAAA */ + /* Read additional words. */ + const u32 second_dword = GetNextDword(); + opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF; + opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); + opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF); + opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); + } + break; + case CheatVmOpcodeType_StoreStaticToAddress: + { + /* 6T0RIor0 VVVVVVVV VVVVVVVV */ + /* Read additional words. */ + opcode.str_static.bit_width = (first_dword >> 24) & 0xF; + opcode.str_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; + opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF); + opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); + } + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + { + /* 7T0RC000 VVVVVVVV */ + /* Read additional words. */ + opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF); + opcode.perform_math_static.value = GetNextDword(); + } + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + { + /* 8kkkkkkk */ + /* Just parse the mask. */ + opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; + } + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + { + /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */ + opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF; + opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF); + opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); + opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); + opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; + if (opcode.perform_math_reg.has_immediate) { + opcode.perform_math_reg.src_reg_2_index = 0; + opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width); + } else { + opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); + } + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + { + /* ATSRIOxa (aaaaaaaa) */ + /* A = opcode 10 */ + /* T = bit width */ + /* S = src register index */ + /* R = address register index */ + /* I = 1 if increment address register, 0 if not increment address register */ + /* O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, + 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + Relative Address */ + /* x = offset register (for offset type 1), memory type (for offset type 3) */ + /* a = relative address (for offset type 2+3) */ + opcode.str_register.bit_width = (first_dword >> 24) & 0xF; + opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF); + opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF); + opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; + opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF)); + opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); + switch (opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + case StoreRegisterOffsetType_Reg: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Imm: + opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case StoreRegisterOffsetType_MemReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + break; + case StoreRegisterOffsetType_MemImm: + case StoreRegisterOffsetType_MemImmReg: + opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + default: + opcode.str_register.ofs_type = StoreRegisterOffsetType_None; + break; + } + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + { + /* C0TcSX## */ + /* C0TcS0Ma aaaaaaaa */ + /* C0TcS1Mr */ + /* C0TcS2Ra aaaaaaaa */ + /* C0TcS3Rr */ + /* C0TcS400 VVVVVVVV (VVVVVVVV) */ + /* C0TcS5X0 */ + /* C0 = opcode 0xC0 */ + /* T = bit width */ + /* c = condition type. */ + /* S = source register. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = static value, 5 = other register. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = other register. */ + /* V = value. */ + opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; + opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); + opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); + opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF); + + switch (opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width); + break; + case CompareRegisterValueType_OtherRegister: + opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); + break; + case CompareRegisterValueType_MemoryRelAddr: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_MemoryOfsReg: + opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + case CompareRegisterValueType_RegisterRelAddr: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case CompareRegisterValueType_RegisterOfsReg: + opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); + break; + } + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + { + /* C10D0Sx0 */ + /* C1 = opcode 0xC1 */ + /* D = destination index. */ + /* S = source index. */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring a register. */ + /* NOTE: If we add more save slots later, current encoding is backwards compatible. */ + opcode.save_restore_reg.dst_index = (first_dword >> 16) & 0xF; + opcode.save_restore_reg.src_index = (first_dword >> 8) & 0xF; + opcode.save_restore_reg.op_type = (SaveRestoreRegisterOpType)((first_dword >> 4) & 0xF); + } + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + { + /* C2x0XXXX */ + /* C2 = opcode 0xC2 */ + /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. */ + /* X = 16-bit bitmask, bit i --> save or restore register i. */ + opcode.save_restore_regmask.op_type = (SaveRestoreRegisterOpType)((first_dword >> 20) & 0xF); + for (size_t i = 0; i < NumRegisters; i++) { + opcode.save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; + } + } + break; + case CheatVmOpcodeType_DebugLog: + { + /* FFFTIX## */ + /* FFFTI0Ma aaaaaaaa */ + /* FFFTI1Mr */ + /* FFFTI2Ra aaaaaaaa */ + /* FFFTI3Rr */ + /* FFFTI4X0 */ + /* FFF = opcode 0xFFF */ + /* T = bit width. */ + /* I = log id. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = register value. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = value register. */ + opcode.debug_log.bit_width = (first_dword >> 16) & 0xF; + opcode.debug_log.log_id = ((first_dword >> 12) & 0xF); + opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF); + + switch (opcode.debug_log.val_type) { + case DebugLogValueType_RegisterValue: + opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType_MemoryRelAddr: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_MemoryOfsReg: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType_RegisterRelAddr: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_RegisterOfsReg: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + } + break; + case CheatVmOpcodeType_ExtendedWidth: + case CheatVmOpcodeType_DoubleExtendedWidth: + default: + /* Unrecognized instruction cannot be decoded. */ + valid = false; + break; + } + + /* End decoding. */ + return valid; + } + + void CheatVirtualMachine::SkipConditionalBlock() { + if (this->condition_depth > 0) { + /* We want to continue until we're out of the current block. */ + const size_t desired_depth = this->condition_depth - 1; + + CheatVmOpcode skip_opcode; + while (this->condition_depth > desired_depth && this->DecodeNextOpcode(&skip_opcode)) { + /* Decode instructions until we see end of the current conditional block. */ + /* NOTE: This is broken in gateway's implementation. */ + /* Gateway currently checks for "0x2" instead of "0x20000000" */ + /* In addition, they do a linear scan instead of correctly decoding opcodes. */ + /* This causes issues if "0x2" appears as an immediate in the conditional block... */ + + /* We also support nesting of conditional blocks, and Gateway does not. */ + if (skip_opcode.begin_conditional_block) { + this->condition_depth++; + } else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) { + this->condition_depth--; + } + } + } else { + /* Skipping, but this->condition_depth = 0. */ + /* This is an error condition. */ + /* However, I don't actually believe it is possible for this to happen. */ + /* I guess we'll throw a fatal error here, so as to encourage me to fix the VM */ + /* in the event that someone triggers it? I don't know how you'd do that. */ + fatalSimple(ResultDmntCheatVmInvalidCondDepth); + } + } + + u64 CheatVirtualMachine::GetVmInt(VmInt value, u32 bit_width) { + switch (bit_width) { + case 1: + return value.bit8; + case 2: + return value.bit16; + case 4: + return value.bit32; + case 8: + return value.bit64; + default: + /* Invalid bit width -> return 0. */ + return 0; + } + } + + u64 CheatVirtualMachine::GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address) { + switch (mem_type) { + case MemoryAccessType_MainNso: + default: + return metadata->main_nso_extents.base + rel_address; + case MemoryAccessType_Heap: + return metadata->heap_extents.base + rel_address; + } + } + + void CheatVirtualMachine::ResetState() { + for (size_t i = 0; i < CheatVirtualMachine::NumRegisters; i++) { + this->registers[i] = 0; + this->saved_values[i] = 0; + this->loop_tops[i] = 0; + } + this->instruction_ptr = 0; + this->condition_depth = 0; + this->decode_success = true; + } + + bool CheatVirtualMachine::LoadProgram(const CheatEntry *cheats, size_t num_cheats) { + /* Reset opcode count. */ + this->num_opcodes = 0; + + for (size_t i = 0; i < num_cheats; i++) { + if (cheats[i].enabled) { + /* Bounds check. */ + if (cheats[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) { + this->num_opcodes = 0; + return false; + } + + for (size_t n = 0; n < cheats[i].definition.num_opcodes; n++) { + this->program[this->num_opcodes++] = cheats[i].definition.opcodes[n]; + } + } + } + + return true; + } + + void CheatVirtualMachine::Execute(const CheatProcessMetadata *metadata) { + CheatVmOpcode cur_opcode; + u64 kHeld = 0; + + /* Get Keys held. */ + hid::GetKeysHeld(&kHeld); + + this->OpenDebugLogFile(); + ON_SCOPE_EXIT { this->CloseDebugLogFile(); }; + + this->LogToDebugFile("Started VM execution.\n"); + this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); + this->LogToDebugFile("Keys Held: %08x\n", (u32)(kHeld & 0x0FFFFFFF)); + + /* Clear VM state. */ + this->ResetState(); + + /* Loop until program finishes. */ + while (this->DecodeNextOpcode(&cur_opcode)) { + this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr); + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]); + } + + for (size_t i = 0; i < NumRegisters; i++) { + this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]); + } + this->LogOpcode(&cur_opcode); + + /* Increment conditional depth, if relevant. */ + if (cur_opcode.begin_conditional_block) { + this->condition_depth++; + } + + switch (cur_opcode.opcode) { + case CheatVmOpcodeType_StoreStatic: + { + /* Calculate address, write value to memory. */ + u64 dst_address = GetCheatProcessAddress(metadata, cur_opcode.store_static.mem_type, cur_opcode.store_static.rel_address + this->registers[cur_opcode.store_static.offset_register]); + u64 dst_value = GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width); + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, cur_opcode.store_static.bit_width); + break; + } + } + break; + case CheatVmOpcodeType_BeginConditionalBlock: + { + /* Read value from memory. */ + u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, cur_opcode.begin_cond.rel_address); + u64 src_value = 0; + switch (cur_opcode.store_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(src_address, &src_value, cur_opcode.begin_cond.bit_width); + break; + } + /* Check against condition. */ + u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width); + bool cond_met = false; + switch (cur_opcode.begin_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(); + } + } + break; + case CheatVmOpcodeType_EndConditionalBlock: + /* Decrement the condition depth. */ + /* We will assume, graciously, that mismatched conditional block ends are a nop. */ + if (this->condition_depth > 0) { + this->condition_depth--; + } + break; + case CheatVmOpcodeType_ControlLoop: + if (cur_opcode.ctrl_loop.start_loop) { + /* Start a loop. */ + this->registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters; + this->loop_tops[cur_opcode.ctrl_loop.reg_index] = this->instruction_ptr; + } else { + /* End a loop. */ + this->registers[cur_opcode.ctrl_loop.reg_index]--; + if (this->registers[cur_opcode.ctrl_loop.reg_index] != 0) { + this->instruction_ptr = this->loop_tops[cur_opcode.ctrl_loop.reg_index]; + } + } + break; + case CheatVmOpcodeType_LoadRegisterStatic: + /* Set a register to a static value. */ + this->registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value; + break; + case CheatVmOpcodeType_LoadRegisterMemory: + { + /* Choose source address. */ + u64 src_address; + if (cur_opcode.ldr_memory.load_from_reg) { + src_address = this->registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address; + } else { + src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address); + } + /* Read into register. Gateway only reads on valid bitwidth. */ + switch (cur_opcode.ldr_memory.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(src_address, &this->registers[cur_opcode.ldr_memory.reg_index], cur_opcode.ldr_memory.bit_width); + break; + } + } + break; + case CheatVmOpcodeType_StoreStaticToAddress: + { + /* Calculate address. */ + u64 dst_address = this->registers[cur_opcode.str_static.reg_index]; + u64 dst_value = cur_opcode.str_static.value; + if (cur_opcode.str_static.add_offset_reg) { + dst_address += this->registers[cur_opcode.str_static.offset_reg_index]; + } + /* Write value to memory. Gateway only writes on valid bitwidth. */ + switch (cur_opcode.str_static.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, cur_opcode.str_static.bit_width); + break; + } + /* Increment register if relevant. */ + if (cur_opcode.str_static.increment_reg) { + this->registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width; + } + } + break; + case CheatVmOpcodeType_PerformArithmeticStatic: + { + /* Do requested math. */ + switch (cur_opcode.perform_math_static.math_type) { + case RegisterArithmeticType_Addition: + this->registers[cur_opcode.perform_math_static.reg_index] += (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Subtraction: + this->registers[cur_opcode.perform_math_static.reg_index] -= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_Multiplication: + this->registers[cur_opcode.perform_math_static.reg_index] *= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_LeftShift: + this->registers[cur_opcode.perform_math_static.reg_index] <<= (u64)cur_opcode.perform_math_static.value; + break; + case RegisterArithmeticType_RightShift: + this->registers[cur_opcode.perform_math_static.reg_index] >>= (u64)cur_opcode.perform_math_static.value; + break; + default: + /* Do not handle extensions here. */ + break; + } + /* Apply bit width. */ + switch (cur_opcode.perform_math_static.bit_width) { + case 1: + this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 2: + this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 4: + this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + case 8: + this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); + break; + } + } + break; + case CheatVmOpcodeType_BeginKeypressConditionalBlock: + /* Check for keypress. */ + if ((cur_opcode.begin_keypress_cond.key_mask & kHeld) != cur_opcode.begin_keypress_cond.key_mask) { + /* Keys not pressed. Skip conditional block. */ + this->SkipConditionalBlock(); + } + break; + case CheatVmOpcodeType_PerformArithmeticRegister: + { + const u64 operand_1_value = this->registers[cur_opcode.perform_math_reg.src_reg_1_index]; + const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ? + GetVmInt(cur_opcode.perform_math_reg.value, cur_opcode.perform_math_reg.bit_width) : + this->registers[cur_opcode.perform_math_reg.src_reg_2_index]; + + u64 res_val = 0; + /* Do requested math. */ + switch (cur_opcode.perform_math_reg.math_type) { + case RegisterArithmeticType_Addition: + res_val = operand_1_value + operand_2_value; + break; + case RegisterArithmeticType_Subtraction: + res_val = operand_1_value - operand_2_value; + break; + case RegisterArithmeticType_Multiplication: + res_val = operand_1_value * operand_2_value; + break; + case RegisterArithmeticType_LeftShift: + res_val = operand_1_value << operand_2_value; + break; + case RegisterArithmeticType_RightShift: + res_val = operand_1_value >> operand_2_value; + break; + case RegisterArithmeticType_LogicalAnd: + res_val = operand_1_value & operand_2_value; + break; + case RegisterArithmeticType_LogicalOr: + res_val = operand_1_value | operand_2_value; + break; + case RegisterArithmeticType_LogicalNot: + res_val = ~operand_1_value; + break; + case RegisterArithmeticType_LogicalXor: + res_val = operand_1_value ^ operand_2_value; + break; + case RegisterArithmeticType_None: + res_val = operand_1_value; + break; + } + + + /* Apply bit width. */ + switch (cur_opcode.perform_math_reg.bit_width) { + case 1: + res_val = static_cast(res_val); + break; + case 2: + res_val = static_cast(res_val); + break; + case 4: + res_val = static_cast(res_val); + break; + case 8: + res_val = static_cast(res_val); + break; + } + + /* Save to register. */ + this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val; + } + break; + case CheatVmOpcodeType_StoreRegisterToAddress: + { + /* Calculate address. */ + u64 dst_value = this->registers[cur_opcode.str_register.str_reg_index]; + u64 dst_address = this->registers[cur_opcode.str_register.addr_reg_index]; + switch (cur_opcode.str_register.ofs_type) { + case StoreRegisterOffsetType_None: + /* Nothing more to do */ + break; + case StoreRegisterOffsetType_Reg: + dst_address += this->registers[cur_opcode.str_register.ofs_reg_index]; + break; + case StoreRegisterOffsetType_Imm: + dst_address += cur_opcode.str_register.rel_address; + break; + case StoreRegisterOffsetType_MemReg: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index]); + break; + case StoreRegisterOffsetType_MemImm: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, cur_opcode.str_register.rel_address); + break; + case StoreRegisterOffsetType_MemImmReg: + dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index] + cur_opcode.str_register.rel_address); + break; + } + + /* Write value to memory. Write only on valid bitwidth. */ + switch (cur_opcode.str_register.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::WriteCheatProcessMemoryUnsafe(dst_address, &dst_value, cur_opcode.str_register.bit_width); + break; + } + + /* Increment register if relevant. */ + if (cur_opcode.str_register.increment_reg) { + this->registers[cur_opcode.str_register.addr_reg_index] += cur_opcode.str_register.bit_width; + } + } + break; + case CheatVmOpcodeType_BeginRegisterConditionalBlock: + { + /* Get value from register. */ + u64 src_value = 0; + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul); + break; + case 2: + src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul); + break; + case 4: + src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + + /* Read value from memory. */ + u64 cond_value = 0; + if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) { + cond_value = GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width); + } else if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_OtherRegister) { + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul); + break; + case 2: + cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul); + break; + case 4: + cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul); + break; + case 8: + cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 cond_address = 0; + switch (cur_opcode.begin_reg_cond.comp_type) { + case CompareRegisterValueType_MemoryRelAddr: + cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, cur_opcode.begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]); + break; + case CompareRegisterValueType_RegisterRelAddr: + cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + cur_opcode.begin_reg_cond.rel_address; + break; + case CompareRegisterValueType_RegisterOfsReg: + cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.begin_reg_cond.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(cond_address, &cond_value, cur_opcode.begin_reg_cond.bit_width); + break; + } + } + + /* Check against condition. */ + bool cond_met = false; + switch (cur_opcode.begin_reg_cond.cond_type) { + case ConditionalComparisonType_GT: + cond_met = src_value > cond_value; + break; + case ConditionalComparisonType_GE: + cond_met = src_value >= cond_value; + break; + case ConditionalComparisonType_LT: + cond_met = src_value < cond_value; + break; + case ConditionalComparisonType_LE: + cond_met = src_value <= cond_value; + break; + case ConditionalComparisonType_EQ: + cond_met = src_value == cond_value; + break; + case ConditionalComparisonType_NE: + cond_met = src_value != cond_value; + break; + } + + /* Skip conditional block if condition not met. */ + if (!cond_met) { + this->SkipConditionalBlock(); + } + } + break; + case CheatVmOpcodeType_SaveRestoreRegister: + /* Save or restore a register. */ + switch (cur_opcode.save_restore_reg.op_type) { + case SaveRestoreRegisterOpType_ClearRegs: + this->registers[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_ClearSaved: + this->saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + this->saved_values[cur_opcode.save_restore_reg.dst_index] = this->registers[cur_opcode.save_restore_reg.src_index]; + break; + case SaveRestoreRegisterOpType_Restore: + default: + this->registers[cur_opcode.save_restore_reg.dst_index] = this->saved_values[cur_opcode.save_restore_reg.src_index]; + break; + } + break; + case CheatVmOpcodeType_SaveRestoreRegisterMask: + /* Save or restore register mask. */ + u64 *src; + u64 *dst; + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_Save: + src = this->registers; + dst = this->saved_values; + break; + case SaveRestoreRegisterOpType_ClearRegs: + case SaveRestoreRegisterOpType_Restore: + default: + src = this->registers; + dst = this->saved_values; + break; + } + for (size_t i = 0; i < NumRegisters; i++) { + if (cur_opcode.save_restore_regmask.should_operate[i]) { + switch (cur_opcode.save_restore_regmask.op_type) { + case SaveRestoreRegisterOpType_ClearSaved: + case SaveRestoreRegisterOpType_ClearRegs: + dst[i] = 0ul; + break; + case SaveRestoreRegisterOpType_Save: + case SaveRestoreRegisterOpType_Restore: + default: + dst[i] = src[i]; + break; + } + } + } + break; + case CheatVmOpcodeType_DebugLog: + { + /* Read value from memory. */ + u64 log_value = 0; + if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { + switch (cur_opcode.debug_log.bit_width) { + case 1: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (cur_opcode.debug_log.val_type) { + case DebugLogValueType_MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, cur_opcode.debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, this->registers[cur_opcode.debug_log.ofs_reg_index]); + break; + case DebugLogValueType_RegisterRelAddr: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + cur_opcode.debug_log.rel_address; + break; + case DebugLogValueType_RegisterOfsReg: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + this->registers[cur_opcode.debug_log.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.debug_log.bit_width) { + case 1: + case 2: + case 4: + case 8: + dmnt::cheat::impl::ReadCheatProcessMemoryUnsafe(val_address, &log_value, cur_opcode.debug_log.bit_width); + break; + } + } + + /* Log value. */ + this->DebugLog(cur_opcode.debug_log.log_id, log_value); + } + break; + default: + /* By default, we do a no-op. */ + break; + } + } + } + +} \ No newline at end of file diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp new file mode 100644 index 000000000..0a07f7220 --- /dev/null +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include + +namespace sts::dmnt::cheat::impl { + + enum CheatVmOpcodeType : u32 { + CheatVmOpcodeType_StoreStatic = 0, + CheatVmOpcodeType_BeginConditionalBlock = 1, + CheatVmOpcodeType_EndConditionalBlock = 2, + CheatVmOpcodeType_ControlLoop = 3, + CheatVmOpcodeType_LoadRegisterStatic = 4, + CheatVmOpcodeType_LoadRegisterMemory = 5, + CheatVmOpcodeType_StoreStaticToAddress = 6, + CheatVmOpcodeType_PerformArithmeticStatic = 7, + CheatVmOpcodeType_BeginKeypressConditionalBlock = 8, + + /* These are not implemented by Gateway's VM. */ + CheatVmOpcodeType_PerformArithmeticRegister = 9, + CheatVmOpcodeType_StoreRegisterToAddress = 10, + CheatVmOpcodeType_Reserved11 = 11, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_ExtendedWidth = 12, + + /* Extended width opcodes. */ + CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, + CheatVmOpcodeType_SaveRestoreRegister = 0xC1, + CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, + + /* This is a meta entry, and not a real opcode. */ + /* This is to facilitate multi-nybble instruction decoding. */ + CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, + + /* Double-extended width opcodes. */ + CheatVmOpcodeType_DebugLog = 0xFFF, + }; + + enum MemoryAccessType : u32 { + MemoryAccessType_MainNso = 0, + MemoryAccessType_Heap = 1, + }; + + enum ConditionalComparisonType : u32 { + ConditionalComparisonType_GT = 1, + ConditionalComparisonType_GE = 2, + ConditionalComparisonType_LT = 3, + ConditionalComparisonType_LE = 4, + ConditionalComparisonType_EQ = 5, + ConditionalComparisonType_NE = 6, + }; + + enum RegisterArithmeticType : u32 { + RegisterArithmeticType_Addition = 0, + RegisterArithmeticType_Subtraction = 1, + RegisterArithmeticType_Multiplication = 2, + RegisterArithmeticType_LeftShift = 3, + RegisterArithmeticType_RightShift = 4, + + /* These are not supported by Gateway's VM. */ + RegisterArithmeticType_LogicalAnd = 5, + RegisterArithmeticType_LogicalOr = 6, + RegisterArithmeticType_LogicalNot = 7, + RegisterArithmeticType_LogicalXor = 8, + + RegisterArithmeticType_None = 9, + }; + + enum StoreRegisterOffsetType : u32 { + StoreRegisterOffsetType_None = 0, + StoreRegisterOffsetType_Reg = 1, + StoreRegisterOffsetType_Imm = 2, + StoreRegisterOffsetType_MemReg = 3, + StoreRegisterOffsetType_MemImm = 4, + StoreRegisterOffsetType_MemImmReg = 5, + }; + + enum CompareRegisterValueType : u32 { + CompareRegisterValueType_MemoryRelAddr = 0, + CompareRegisterValueType_MemoryOfsReg = 1, + CompareRegisterValueType_RegisterRelAddr = 2, + CompareRegisterValueType_RegisterOfsReg = 3, + CompareRegisterValueType_StaticValue = 4, + CompareRegisterValueType_OtherRegister = 5, + }; + + enum SaveRestoreRegisterOpType : u32 { + SaveRestoreRegisterOpType_Restore = 0, + SaveRestoreRegisterOpType_Save = 1, + SaveRestoreRegisterOpType_ClearSaved = 2, + SaveRestoreRegisterOpType_ClearRegs = 3, + }; + + enum DebugLogValueType : u32 { + DebugLogValueType_MemoryRelAddr = 0, + DebugLogValueType_MemoryOfsReg = 1, + DebugLogValueType_RegisterRelAddr = 2, + DebugLogValueType_RegisterOfsReg = 3, + DebugLogValueType_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 {}; + + 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; + bool should_operate[0x10]; + }; + + 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 CheatVmOpcode { + CheatVmOpcodeType opcode; + bool begin_conditional_block; + union { + StoreStaticOpcode store_static; + BeginConditionalOpcode begin_cond; + EndConditionalOpcode end_cond; + ControlLoopOpcode ctrl_loop; + LoadRegisterStaticOpcode ldr_static; + LoadRegisterMemoryOpcode ldr_memory; + StoreStaticToAddressOpcode str_static; + PerformArithmeticStaticOpcode perform_math_static; + BeginKeypressConditionalOpcode begin_keypress_cond; + PerformArithmeticRegisterOpcode perform_math_reg; + StoreRegisterToAddressOpcode str_register; + BeginRegisterConditionalOpcode begin_reg_cond; + SaveRestoreRegisterOpcode save_restore_reg; + SaveRestoreRegisterMaskOpcode save_restore_regmask; + DebugLogOpcode debug_log; + }; + }; + + class CheatVirtualMachine { + public: + constexpr static size_t MaximumProgramOpcodeCount = 0x400; + constexpr static size_t NumRegisters = 0x10; + private: + size_t num_opcodes = 0; + size_t instruction_ptr = 0; + size_t condition_depth = 0; + bool decode_success = false; + u32 program[MaximumProgramOpcodeCount] = {0}; + u64 registers[NumRegisters] = {0}; + u64 saved_values[NumRegisters] = {0}; + size_t loop_tops[NumRegisters] = {0}; + private: + bool DecodeNextOpcode(CheatVmOpcode *out); + void SkipConditionalBlock(); + void ResetState(); + + /* For implementing the DebugLog opcode. */ + void DebugLog(u32 log_id, u64 value); + + /* For debugging. These will be IFDEF'd out normally. */ + void OpenDebugLogFile(); + void CloseDebugLogFile(); + void LogToDebugFile(const char *format, ...); + 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); + public: + CheatVirtualMachine() { } + + size_t GetProgramSize() { + return this->num_opcodes; + } + + bool LoadProgram(const CheatEntry *cheats, size_t num_cheats); + void Execute(const CheatProcessMetadata *metadata); + #ifdef DMNT_CHEAT_VM_DEBUG_LOG + private: + FILE *debug_log_file = NULL; + #endif + }; + +} diff --git a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp deleted file mode 100644 index ab9c36a39..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include "dmnt_config.hpp" -#include "dmnt_cheat_debug_events_manager.hpp" - - -/* WORKAROUND: This design prevents a kernel deadlock from occurring on 6.0.0+ */ - -static HosThread g_per_core_threads[DmntCheatDebugEventsManager::NumCores]; -static HosMessageQueue *g_per_core_queues[DmntCheatDebugEventsManager::NumCores]; -static HosSignal g_continued_signal; - -void DmntCheatDebugEventsManager::PerCoreThreadFunc(void *arg) { - /* This thread will simply wait on the appropriate message queue. */ - size_t current_core = reinterpret_cast(arg); - while (true) { - Handle debug_handle = 0; - /* Get the debug handle. */ - { - uintptr_t x = 0; - g_per_core_queues[current_core]->Receive(&x); - debug_handle = static_cast(x); - } - - /* Continue the process, if needed. */ - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_300)) { - svcContinueDebugEvent(debug_handle, 5, nullptr, 0); - } else { - svcLegacyContinueDebugEvent(debug_handle, 5, 0); - } - - g_continued_signal.Signal(); - } -} - -void DmntCheatDebugEventsManager::ContinueCheatProcess(Handle cheat_dbg_hnd) { - /* Loop getting debug events. */ - DebugEventInfo dbg_event; - while (R_SUCCEEDED(svcGetDebugEvent((u8 *)&dbg_event, cheat_dbg_hnd))) { - /* ... */ - } - - size_t target_core = DmntCheatDebugEventsManager::NumCores - 1; - /* Retrieve correct core for new thread event. */ - if (dbg_event.type == DebugEventType::AttachThread) { - u64 out64; - u32 out32; - R_ASSERT(svcGetDebugThreadParam(&out64, &out32, cheat_dbg_hnd, dbg_event.info.attach_thread.thread_id, DebugThreadParam_CurrentCore)); - target_core = out32; - } - - /* Make appropriate thread continue. */ - g_per_core_queues[target_core]->Send(static_cast(cheat_dbg_hnd)); - - /* Wait. */ - g_continued_signal.Wait(); - g_continued_signal.Reset(); -} - -void DmntCheatDebugEventsManager::Initialize() { - /* Spawn per core resources. */ - for (size_t i = 0; i < DmntCheatDebugEventsManager::NumCores; i++) { - /* Create queue. */ - g_per_core_queues[i] = new HosMessageQueue(1); - - /* Create thread. */ - if (R_FAILED(g_per_core_threads[i].Initialize(&DmntCheatDebugEventsManager::PerCoreThreadFunc, reinterpret_cast(i), 0x1000, 24, i))) { - std::abort(); - } - - /* Set core mask. */ - if (R_FAILED(svcSetThreadCoreMask(g_per_core_threads[i].GetHandle(), i, (1u << i)))) { - std::abort(); - } - - /* Start thread. */ - if (R_FAILED(g_per_core_threads[i].Start())) { - std::abort(); - } - } -} \ No newline at end of file diff --git a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp b/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp deleted file mode 100644 index c31ea37f7..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_debug_events_manager.hpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - -#include "dmnt_cheat_types.hpp" - -struct StackFrame { - u64 fp; - u64 lr; -}; - -struct AttachProcessInfo { - u64 title_id; - u64 process_id; - char name[0xC]; - u32 flags; - u64 user_exception_context_address; /* 5.0.0+ */ -}; - -struct AttachThreadInfo { - u64 thread_id; - u64 tls_address; - u64 entrypoint; -}; - -/* TODO: ExitProcessInfo */ -/* TODO: ExitThreadInfo */ - -enum class DebugExceptionType : u32 { - UndefinedInstruction = 0, - InstructionAbort = 1, - DataAbort = 2, - AlignmentFault = 3, - DebuggerAttached = 4, - BreakPoint = 5, - UserBreak = 6, - DebuggerBreak = 7, - BadSvc = 8, - UnknownNine = 9, -}; - -struct UndefinedInstructionInfo { - u32 insn; -}; - -struct DataAbortInfo { - u64 address; -}; - -struct AlignmentFaultInfo { - u64 address; -}; - -struct UserBreakInfo { - u64 break_reason; - u64 address; - u64 size; -}; - -struct BadSvcInfo { - u32 id; -}; - -union SpecificExceptionInfo { - UndefinedInstructionInfo undefined_instruction; - DataAbortInfo data_abort; - AlignmentFaultInfo alignment_fault; - UserBreakInfo user_break; - BadSvcInfo bad_svc; - u64 raw; -}; - -struct ExceptionInfo { - DebugExceptionType type; - u64 address; - SpecificExceptionInfo specific; -}; - - -enum class DebugEventType : u32 { - AttachProcess = 0, - AttachThread = 1, - ExitProcess = 2, - ExitThread = 3, - Exception = 4 -}; - -union DebugInfo { - AttachProcessInfo attach_process; - AttachThreadInfo attach_thread; - ExceptionInfo exception; -}; - -struct DebugEventInfo { - DebugEventType type; - u32 flags; - u64 thread_id; - union { - DebugInfo info; - u64 _[0x40/sizeof(u64)]; - }; -}; - -static_assert(sizeof(DebugEventInfo) >= 0x50, "Incorrect DebugEventInfo definition!"); - -class DmntCheatDebugEventsManager { - public: - static constexpr size_t NumCores = 4; - private: - static void PerCoreThreadFunc(void *arg); - public: - static void ContinueCheatProcess(Handle cheat_dbg_hnd); - - static void Initialize(); -}; diff --git a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp b/stratosphere/dmnt/source/dmnt_cheat_manager.cpp deleted file mode 100644 index ce4315a8d..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_manager.cpp +++ /dev/null @@ -1,1100 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include "dmnt_cheat_manager.hpp" -#include "dmnt_cheat_vm.hpp" -#include "dmnt_config.hpp" -#include "dmnt_cheat_debug_events_manager.hpp" -#include "pm_shim.h" - -static HosMutex g_cheat_lock; -static HosThread g_detect_thread, g_vm_thread, g_debug_events_thread; - -static IEvent *g_cheat_process_event; -static DmntCheatVm *g_cheat_vm; - -static CheatProcessMetadata g_cheat_process_metadata = {0}; -static Handle g_cheat_process_debug_hnd = 0; - -/* Should we enable cheats by default? */ -static bool g_enable_cheats_by_default = true; -static bool g_always_save_cheat_toggles = false; -static bool g_should_save_cheat_toggles = false; - -/* For debug event thread management. */ -static HosMutex g_debug_event_thread_lock, g_attach_lock; -static bool g_has_debug_events_thread = false; - -/* To save some copying. */ -static bool g_needs_reload_vm_program = false; - -/* Global cheat entry storage. */ -static CheatEntry g_cheat_entries[DmntCheatManager::MaxCheatCount]; - -/* Global frozen address storage. */ -static std::map g_frozen_addresses_map; - -void DmntCheatManager::StartDebugEventsThread() { - std::scoped_lock lk(g_debug_event_thread_lock); - - /* Spawn the debug events thread. */ - if (!g_has_debug_events_thread) { - R_ASSERT(g_debug_events_thread.Initialize(&DmntCheatManager::DebugEventsThread, nullptr, 0x4000, 48)); - R_ASSERT(g_debug_events_thread.Start()); - - g_has_debug_events_thread = true; - } -} - -void DmntCheatManager::WaitDebugEventsThread() { - std::scoped_lock lk(g_debug_event_thread_lock); - - /* Wait for the thread to exit. */ - if (g_has_debug_events_thread) { - g_debug_events_thread.CancelSynchronization(); - g_debug_events_thread.Join(); - - g_has_debug_events_thread = false; - } -} - -void DmntCheatManager::CloseActiveCheatProcess() { - if (g_cheat_process_debug_hnd != 0) { - /* Close process resources. */ - svcCloseHandle(g_cheat_process_debug_hnd); - g_cheat_process_debug_hnd = 0; - - /* Save cheat toggles. */ - if (g_always_save_cheat_toggles || g_should_save_cheat_toggles) { - SaveCheatToggles(g_cheat_process_metadata.title_id); - g_should_save_cheat_toggles = false; - } - - /* Clear metadata. */ - g_cheat_process_metadata = (CheatProcessMetadata){0}; - - /* Clear cheat list. */ - ResetAllCheatEntries(); - - /* Clear frozen addresses. */ - ResetFrozenAddresses(); - - /* Signal to our fans. */ - g_cheat_process_event->Signal(); - } -} - -bool DmntCheatManager::HasActiveCheatProcess() { - u64 tmp; - bool has_cheat_process = g_cheat_process_debug_hnd != 0; - - if (has_cheat_process) { - has_cheat_process &= R_SUCCEEDED(svcGetProcessId(&tmp, g_cheat_process_debug_hnd)); - } - - if (has_cheat_process) { - has_cheat_process &= R_SUCCEEDED(pmdmntGetApplicationPid(&tmp)); - } - - if (has_cheat_process) { - has_cheat_process &= tmp == g_cheat_process_metadata.process_id; - } - - if (!has_cheat_process) { - CloseActiveCheatProcess(); - } - - return has_cheat_process; -} - -Result DmntCheatManager::ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size) { - if (HasActiveCheatProcess()) { - return svcReadDebugProcessMemory(out_data, g_cheat_process_debug_hnd, proc_addr, size); - } - - return ResultDmntCheatNotAttached; -} - -Result DmntCheatManager::WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size) { - if (HasActiveCheatProcess()) { - R_TRY(svcWriteDebugProcessMemory(g_cheat_process_debug_hnd, data, proc_addr, size)); - - /* We might have a frozen address. Update it if we do! */ - for (auto & [address, value] : g_frozen_addresses_map) { - /* Map is in order, so break here. */ - if (address >= proc_addr + size) { - break; - } - - /* Check if we need to write. */ - if (proc_addr <= address) { - const size_t offset = (address - proc_addr); - const size_t size_to_copy = size - offset; - memcpy(&value.value, (void *)((uintptr_t)data + offset), size_to_copy < sizeof(value.value) ? size_to_copy : sizeof(value.value)); - } - } - - return ResultSuccess; - } - - return ResultDmntCheatNotAttached; -} - - -Result DmntCheatManager::GetCheatProcessMappingCount(u64 *out_count) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - MemoryInfo mem_info; - - u64 address = 0; - *out_count = 0; - do { - mem_info.perm = 0; - u32 tmp; - if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, g_cheat_process_debug_hnd, address))) { - break; - } - - if (mem_info.perm != 0) { - *out_count += 1; - } - - address = mem_info.addr + mem_info.size; - } while (address != 0); - - return ResultSuccess; -} - -Result DmntCheatManager::GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - MemoryInfo mem_info; - u64 address = 0; - u64 count = 0; - *out_count = 0; - do { - mem_info.perm = 0; - u32 tmp; - if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &tmp, g_cheat_process_debug_hnd, address))) { - break; - } - - if (mem_info.perm != 0) { - count++; - if (count > offset) { - mappings[(*out_count)++] = mem_info; - } - } - - address = mem_info.addr + mem_info.size; - } while (address != 0 && *out_count < max_count); - - return ResultSuccess; -} - -Result DmntCheatManager::ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size) { - std::scoped_lock lk(g_cheat_lock); - - return ReadCheatProcessMemoryForVm(proc_addr, out_data, size); -} - -Result DmntCheatManager::WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size) { - std::scoped_lock lk(g_cheat_lock); - - return WriteCheatProcessMemoryForVm(proc_addr, data, size); -} - -Result DmntCheatManager::QueryCheatProcessMemory(MemoryInfo *mapping, u64 address) { - std::scoped_lock lk(g_cheat_lock); - - if (HasActiveCheatProcess()) { - u32 tmp; - return svcQueryDebugProcessMemory(mapping, &tmp, g_cheat_process_debug_hnd, address); - } - - return ResultDmntCheatNotAttached; -} - -void DmntCheatManager::ResetFrozenAddresses() { - /* Just clear the map. */ - g_frozen_addresses_map.clear(); -} - -void DmntCheatManager::ResetCheatEntry(size_t i) { - if (i < DmntCheatManager::MaxCheatCount) { - g_cheat_entries[i].enabled = false; - g_cheat_entries[i].cheat_id = i; - g_cheat_entries[i].definition = {0}; - - /* Trigger a VM reload. */ - g_needs_reload_vm_program = true; - } -} - -void DmntCheatManager::ResetAllCheatEntries() { - for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) { - ResetCheatEntry(i); - } -} - -CheatEntry *DmntCheatManager::GetFreeCheatEntry() { - /* Check all non-master cheats for availability. */ - for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) { - if (g_cheat_entries[i].definition.num_opcodes == 0) { - return &g_cheat_entries[i]; - } - } - - return nullptr; -} - -CheatEntry *DmntCheatManager::GetCheatEntryById(size_t i) { - if (i < DmntCheatManager::MaxCheatCount) { - return &g_cheat_entries[i]; - } - - return nullptr; -} - -CheatEntry *DmntCheatManager::GetCheatEntryByReadableName(const char *readable_name) { - /* Check all non-master cheats for match. */ - for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) { - if (strcmp(g_cheat_entries[i].definition.readable_name, readable_name) == 0) { - return &g_cheat_entries[i]; - } - } - - return nullptr; -} - -bool DmntCheatManager::ParseCheats(const char *s, size_t len) { - size_t i = 0; - CheatEntry *cur_entry = NULL; - - /* Trigger a VM reload. */ - g_needs_reload_vm_program = true; - - while (i < len) { - if (isspace((unsigned char)s[i])) { - /* Just ignore space. */ - i++; - } else if (s[i] == '[') { - /* Parse a readable cheat name. */ - cur_entry = GetFreeCheatEntry(); - if (cur_entry == NULL) { - return false; - } - - /* Extract name bounds. */ - size_t j = i + 1; - while (s[j] != ']') { - j++; - if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) { - return false; - } - } - - /* s[i+1:j] is cheat name. */ - const size_t cheat_name_len = (j - i - 1); - memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len); - cur_entry->definition.readable_name[cheat_name_len] = 0; - - /* Skip onwards. */ - i = j + 1; - } else if (s[i] == '{') { - /* We're parsing a master cheat. */ - cur_entry = &g_cheat_entries[0]; - - /* There can only be one master cheat. */ - if (cur_entry->definition.num_opcodes > 0) { - return false; - } - - /* Extract name bounds */ - size_t j = i + 1; - while (s[j] != '}') { - j++; - if (j >= len || (j - i - 1) >= sizeof(cur_entry->definition.readable_name)) { - return false; - } - } - - /* s[i+1:j] is cheat name. */ - const size_t cheat_name_len = (j - i - 1); - memcpy(cur_entry->definition.readable_name, &s[i+1], cheat_name_len); - cur_entry->definition.readable_name[cheat_name_len] = 0; - - /* Skip onwards. */ - i = j + 1; - } else if (isxdigit((unsigned char)s[i])) { - /* Make sure that we have a cheat open. */ - if (cur_entry == NULL) { - return false; - } - - /* Bounds check the opcode count. */ - if (cur_entry->definition.num_opcodes >= sts::util::size(cur_entry->definition.opcodes)) { - return false; - } - - /* We're parsing an instruction, so validate it's 8 hex digits. */ - for (size_t j = 1; j < 8; j++) { - /* Validate 8 hex chars. */ - if (i + j >= len || !isxdigit((unsigned char)s[i+j])) { - return false; - } - } - - /* Parse the new opcode. */ - char hex_str[9] = {0}; - memcpy(hex_str, &s[i], 8); - cur_entry->definition.opcodes[cur_entry->definition.num_opcodes++] = strtoul(hex_str, NULL, 16); - - - /* Skip onwards. */ - i += 8; - } else { - /* Unexpected character encountered. */ - return false; - } - } - - /* Master cheat can't be disabled. */ - if (g_cheat_entries[0].definition.num_opcodes > 0) { - g_cheat_entries[0].enabled = true; - } - - /* Enable all entries we parsed. */ - for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) { - if (g_cheat_entries[i].definition.num_opcodes > 0) { - g_cheat_entries[i].enabled = g_enable_cheats_by_default; - } - } - - return true; -} - -bool DmntCheatManager::LoadCheats(u64 title_id, const u8 *build_id) { - /* Reset existing entries. */ - ResetAllCheatEntries(); - - FILE *f_cht = NULL; - /* Open the file for title/build_id. */ - { - char path[FS_MAX_PATH+1] = {0}; - snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/%02x%02x%02x%02x%02x%02x%02x%02x.txt", title_id, - build_id[0], build_id[1], build_id[2], build_id[3], build_id[4], build_id[5], build_id[6], build_id[7]); - - f_cht = fopen(path, "rb"); - } - - /* Check for NULL */ - if (f_cht == NULL) { - return false; - } - ON_SCOPE_EXIT { fclose(f_cht); }; - - /* Get file size. */ - fseek(f_cht, 0L, SEEK_END); - const size_t cht_sz = ftell(f_cht); - fseek(f_cht, 0L, SEEK_SET); - - /* Allocate cheat txt buffer. */ - char *cht_txt = (char *)malloc(cht_sz + 1); - if (cht_txt == NULL) { - return false; - } - ON_SCOPE_EXIT { free(cht_txt); }; - - /* Read cheats into buffer. */ - if (fread(cht_txt, 1, cht_sz, f_cht) != cht_sz) { - return false; - } - cht_txt[cht_sz] = 0; - - /* Parse cheat buffer. */ - return ParseCheats(cht_txt, strlen(cht_txt)); -} - -bool DmntCheatManager::ParseCheatToggles(const char *s, size_t len) { - size_t i = 0; - char cur_cheat_name[sizeof(CheatEntry::definition.readable_name)]; - char toggle[8]; - - while (i < len) { - if (isspace((unsigned char)s[i])) { - /* Just ignore space. */ - i++; - } else if (s[i] == '[') { - - /* Extract name bounds. */ - size_t j = i + 1; - while (s[j] != ']') { - j++; - if (j >= len || (j - i - 1) >= sizeof(cur_cheat_name)) { - return false; - } - } - - /* s[i+1:j] is cheat name. */ - const size_t cheat_name_len = (j - i - 1); - memcpy(cur_cheat_name, &s[i+1], cheat_name_len); - cur_cheat_name[cheat_name_len] = 0; - - /* Skip onwards. */ - i = j + 1; - - /* Skip whitespace. */ - while (isspace((unsigned char)s[i])) { - i++; - } - - /* Parse whether to toggle. */ - j = i + 1; - while (!isspace((unsigned char)s[j])) { - j++; - if (j >= len || (j - i) >= sizeof(toggle)) { - return false; - } - } - - /* s[i:j] is toggle. */ - const size_t toggle_len = (j - i); - memcpy(toggle, &s[i], toggle_len); - toggle[toggle_len] = 0; - - /* Allow specifying toggle for not present cheat. */ - CheatEntry *entry = GetCheatEntryByReadableName(cur_cheat_name); - if (entry != nullptr) { - if (strcasecmp(toggle, "1") == 0 || strcasecmp(toggle, "true") == 0 || strcasecmp(toggle, "on") == 0) { - entry->enabled = true; - } else if (strcasecmp(toggle, "0") == 0 || strcasecmp(toggle, "false") == 0 || strcasecmp(toggle, "off") == 0) { - entry->enabled = false; - } - } - - /* Skip onwards. */ - i = j + 1; - } else { - /* Unexpected character encountered. */ - return false; - } - } - - return true; -} - -bool DmntCheatManager::LoadCheatToggles(u64 title_id) { - FILE *f_tg = NULL; - /* Open the file for title id. */ - { - char path[FS_MAX_PATH+1] = {0}; - snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", title_id); - - f_tg = fopen(path, "rb"); - } - - /* Unless we successfully parse, don't save toggles on close. */ - g_should_save_cheat_toggles = false; - - /* Check for NULL, which is allowed. */ - if (f_tg == NULL) { - return true; - } - ON_SCOPE_EXIT { fclose(f_tg); }; - - /* Get file size. */ - fseek(f_tg, 0L, SEEK_END); - const size_t tg_sz = ftell(f_tg); - fseek(f_tg, 0L, SEEK_SET); - - /* Allocate toggle txt buffer. */ - char *tg_txt = (char *)malloc(tg_sz + 1); - if (tg_txt == NULL) { - return false; - } - ON_SCOPE_EXIT { free(tg_txt); }; - - /* Read toggles into buffer. */ - if (fread(tg_txt, 1, tg_sz, f_tg) != tg_sz) { - return false; - } - tg_txt[tg_sz] = 0; - - /* Parse toggle buffer. */ - g_should_save_cheat_toggles = ParseCheatToggles(tg_txt, strlen(tg_txt)); - return g_should_save_cheat_toggles; -} - -void DmntCheatManager::SaveCheatToggles(u64 title_id) { - FILE *f_tg = NULL; - /* Open the file for title id. */ - { - char path[FS_MAX_PATH+1] = {0}; - snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/cheats/toggles.txt", title_id); - - f_tg = fopen(path, "wb"); - } - - if (f_tg == NULL) { - return; - } - - ON_SCOPE_EXIT { fclose(f_tg); }; - - /* Save all non-master cheats. */ - for (size_t i = 1; i < DmntCheatManager::MaxCheatCount; i++) { - if (g_cheat_entries[i].definition.num_opcodes != 0) { - fprintf(f_tg, "[%s]\n", g_cheat_entries[i].definition.readable_name); - if (g_cheat_entries[i].enabled) { - fprintf(f_tg, "true\n"); - } else { - fprintf(f_tg, "false\n"); - } - } - } -} - -Result DmntCheatManager::GetCheatCount(u64 *out_count) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - *out_count = 0; - for (size_t i = 0; i < DmntCheatManager::MaxCheatCount; i++) { - if (g_cheat_entries[i].definition.num_opcodes > 0) { - *out_count += 1; - } - } - - return ResultSuccess; -} - -Result DmntCheatManager::GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - u64 count = 0; - *out_count = 0; - for (size_t i = 0; i < DmntCheatManager::MaxCheatCount && (*out_count) < max_count; i++) { - if (g_cheat_entries[i].definition.num_opcodes > 0) { - count++; - if (count > offset) { - cheats[(*out_count)++] = g_cheat_entries[i]; - } - } - } - - return ResultSuccess; -} - -Result DmntCheatManager::GetCheatById(CheatEntry *out_cheat, u32 cheat_id) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - const CheatEntry *entry = GetCheatEntryById(cheat_id); - if (entry == nullptr || entry->definition.num_opcodes == 0) { - return ResultDmntCheatUnknownChtId; - } - - *out_cheat = *entry; - return ResultSuccess; -} - -Result DmntCheatManager::ToggleCheat(u32 cheat_id) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - CheatEntry *entry = GetCheatEntryById(cheat_id); - if (entry == nullptr || entry->definition.num_opcodes == 0) { - return ResultDmntCheatUnknownChtId; - } - - if (cheat_id == 0) { - return ResultDmntCheatCannotDisableMasterCheat; - } - - entry->enabled = !entry->enabled; - - /* Trigger a VM reload. */ - g_needs_reload_vm_program = true; - return ResultSuccess; -} - -Result DmntCheatManager::AddCheat(u32 *out_id, CheatDefinition *def, bool enabled) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - if (def->num_opcodes == 0 || def->num_opcodes > sts::util::size(def->opcodes)) { - return ResultDmntCheatInvalidCheat; - } - - CheatEntry *new_entry = GetFreeCheatEntry(); - if (new_entry == nullptr) { - return ResultDmntCheatOutOfCheats; - } - - new_entry->enabled = enabled; - new_entry->definition = *def; - - /* Trigger a VM reload. */ - g_needs_reload_vm_program = true; - return ResultSuccess; -} - -Result DmntCheatManager::RemoveCheat(u32 cheat_id) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - if (cheat_id >= DmntCheatManager::MaxCheatCount) { - return ResultDmntCheatUnknownChtId; - } - - ResetCheatEntry(cheat_id); - - /* Trigger a VM reload. */ - g_needs_reload_vm_program = true; - return ResultSuccess; -} - -Result DmntCheatManager::GetFrozenAddressCount(u64 *out_count) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - *out_count = g_frozen_addresses_map.size(); - return ResultSuccess; -} - -Result DmntCheatManager::GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - u64 count = 0; - *out_count = 0; - for (auto const& [address, value] : g_frozen_addresses_map) { - if ((*out_count) >= max_count) { - break; - } - - count++; - if (count > offset) { - const u64 cur_ind = (*out_count)++; - frz_addrs[cur_ind].address = address; - frz_addrs[cur_ind].value = value; - } - } - - return ResultSuccess; -} - -Result DmntCheatManager::GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - const auto it = g_frozen_addresses_map.find(address); - if (it == g_frozen_addresses_map.end()) { - return ResultDmntCheatAddressNotFrozen; - } - - frz_addr->address = it->first; - frz_addr->value = it->second; - return ResultSuccess; -} - -Result DmntCheatManager::EnableFrozenAddress(u64 *out_value, u64 address, u64 width) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - if (g_frozen_addresses_map.size() >= DmntCheatManager::MaxFrozenAddressCount) { - return ResultDmntCheatTooManyFrozenAddresses; - } - - const auto it = g_frozen_addresses_map.find(address); - if (it != g_frozen_addresses_map.end()) { - return ResultDmntCheatAddressAlreadyFrozen; - } - - FrozenAddressValue value = {0}; - value.width = width; - R_TRY(ReadCheatProcessMemoryForVm(address, &value.value, width)); - - g_frozen_addresses_map[address] = value; - *out_value = value.value; - return ResultSuccess; -} - -Result DmntCheatManager::DisableFrozenAddress(u64 address) { - std::scoped_lock lk(g_cheat_lock); - - if (!HasActiveCheatProcess()) { - return ResultDmntCheatNotAttached; - } - - const auto it = g_frozen_addresses_map.find(address); - if (it == g_frozen_addresses_map.end()) { - return ResultDmntCheatAddressNotFrozen; - } - - g_frozen_addresses_map.erase(address); - return ResultSuccess; -} - -Handle DmntCheatManager::PrepareDebugNextApplication() { - Handle event_h; - R_ASSERT(pmdmntEnableDebugForApplication(&event_h)); - - return event_h; -} - -static void PopulateMemoryExtents(MemoryRegionExtents *extents, Handle p_h, u64 id_base, u64 id_size) { - /* Get base extent. */ - R_ASSERT(svcGetInfo(&extents->base, id_base, p_h, 0)); - - /* Get size extent. */ - R_ASSERT(svcGetInfo(&extents->size, id_size, p_h, 0)); -} - -static void StartDebugProcess(u64 pid) { - R_ASSERT(pmdmntStartProcess(pid)); -} - -Result DmntCheatManager::ForceOpenCheatProcess() { - std::scoped_lock attach_lk(g_attach_lock); - - /* Acquire the cheat lock for long enough to close the process if needed. */ - { - std::scoped_lock lk(g_cheat_lock); - - if (HasActiveCheatProcess()) { - return ResultSuccess; - } - - /* Close the current application, if it's open. */ - CloseActiveCheatProcess(); - } - - /* Intentionally yield the cheat lock to the debug events thread. */ - /* Wait to not have debug events thread. */ - WaitDebugEventsThread(); - - /* At this point, we can re-acquire the lock for the rest of the function. */ - std::scoped_lock lk(g_cheat_lock); - - /* Get the current application process ID. */ - R_TRY(pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)); - auto proc_guard = SCOPE_GUARD { - g_cheat_process_metadata.process_id = 0; - }; - - /* Get process handle, use it to learn memory extents. */ - { - Handle proc_h = 0; - ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } }; - - R_TRY(pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)); - - /* Get memory extents. */ - PopulateMemoryExtents(&g_cheat_process_metadata.heap_extents, proc_h, 4, 5); - PopulateMemoryExtents(&g_cheat_process_metadata.alias_extents, proc_h, 2, 3); - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200)) { - PopulateMemoryExtents(&g_cheat_process_metadata.address_space_extents, proc_h, 12, 13); - } else { - g_cheat_process_metadata.address_space_extents.base = 0x08000000UL; - g_cheat_process_metadata.address_space_extents.size = 0x78000000UL; - } - } - - /* Get module information from Loader. */ - { - LoaderModuleInfo proc_modules[2]; - u32 num_modules; - R_TRY(ldrDmntGetModuleInfos(g_cheat_process_metadata.process_id, proc_modules, sts::util::size(proc_modules), &num_modules)); - - /* All applications must have two modules. */ - /* However, this is a force-open, so we will accept one module. */ - /* Poor HBL, I guess... */ - LoaderModuleInfo *proc_module; - if (num_modules == 2) { - proc_module = &proc_modules[1]; - } else if (num_modules == 1) { - proc_module = &proc_modules[0]; - } else { - return ResultDmntCheatNotAttached; - } - - g_cheat_process_metadata.main_nso_extents.base = proc_module->base_address; - g_cheat_process_metadata.main_nso_extents.size = proc_module->size; - memcpy(g_cheat_process_metadata.main_nso_build_id, proc_module->build_id, sizeof(g_cheat_process_metadata.main_nso_build_id)); - } - - /* Read cheats off the SD. */ - /* This is allowed to fail. We may not have any cheats. */ - LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id); - - /* Load saved toggles, if present. */ - LoadCheatToggles(g_cheat_process_metadata.title_id); - - /* Open a debug handle. */ - R_TRY(svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)); - - /* Cancel process guard. */ - proc_guard.Cancel(); - - /* Start debug events thread. */ - StartDebugEventsThread(); - - /* Signal to our fans. */ - g_cheat_process_event->Signal(); - - return ResultSuccess; -} - -void DmntCheatManager::OnNewApplicationLaunch() { - std::scoped_lock attach_lk(g_attach_lock); - - { - std::scoped_lock lk(g_cheat_lock); - /* Close the current application, if it's open. */ - CloseActiveCheatProcess(); - } - - /* Intentionally yield the cheat lock to the debug events thread. */ - /* Wait to not have debug events thread. */ - WaitDebugEventsThread(); - - /* At this point, we can re-acquire the lock for the rest of the function. */ - std::scoped_lock lk(g_cheat_lock); - - /* Get the new application's process ID. */ - R_ASSERT(pmdmntGetApplicationPid(&g_cheat_process_metadata.process_id)); - - /* Get process handle, use it to learn memory extents. */ - { - Handle proc_h = 0; - ON_SCOPE_EXIT { if (proc_h != 0) { svcCloseHandle(proc_h); } }; - - R_ASSERT(pmdmntAtmosphereGetProcessInfo(&proc_h, &g_cheat_process_metadata.title_id, nullptr, g_cheat_process_metadata.process_id)); - - /* Get memory extents. */ - PopulateMemoryExtents(&g_cheat_process_metadata.heap_extents, proc_h, 4, 5); - PopulateMemoryExtents(&g_cheat_process_metadata.alias_extents, proc_h, 2, 3); - if ((GetRuntimeFirmwareVersion() >= FirmwareVersion_200)) { - PopulateMemoryExtents(&g_cheat_process_metadata.address_space_extents, proc_h, 12, 13); - } else { - g_cheat_process_metadata.address_space_extents.base = 0x08000000UL; - g_cheat_process_metadata.address_space_extents.size = 0x78000000UL; - } - } - - /* Check if we should skip based on keys down. */ - if (!DmntConfigManager::HasCheatEnableButton(g_cheat_process_metadata.title_id)) { - StartDebugProcess(g_cheat_process_metadata.process_id); - g_cheat_process_metadata.process_id = 0; - return; - } - - /* Get module information from Loader. */ - { - LoaderModuleInfo proc_modules[2]; - u32 num_modules; - R_ASSERT(ldrDmntGetModuleInfos(g_cheat_process_metadata.process_id, proc_modules, sts::util::size(proc_modules), &num_modules)); - - /* All applications must have two modules. */ - /* If we only have one, we must be e.g. mitming HBL. */ - /* We don't want to fuck with HBL. */ - if (num_modules != 2) { - StartDebugProcess(g_cheat_process_metadata.process_id); - g_cheat_process_metadata.process_id = 0; - return; - } - - g_cheat_process_metadata.main_nso_extents.base = proc_modules[1].base_address; - g_cheat_process_metadata.main_nso_extents.size = proc_modules[1].size; - memcpy(g_cheat_process_metadata.main_nso_build_id, proc_modules[1].build_id, sizeof(g_cheat_process_metadata.main_nso_build_id)); - } - - /* Read cheats off the SD. */ - if (!LoadCheats(g_cheat_process_metadata.title_id, g_cheat_process_metadata.main_nso_build_id) || !LoadCheatToggles(g_cheat_process_metadata.title_id)) { - /* If we don't have cheats, or cheats are malformed, don't attach. */ - StartDebugProcess(g_cheat_process_metadata.process_id); - g_cheat_process_metadata.process_id = 0; - return; - } - - /* Open a debug handle. */ - R_ASSERT(svcDebugActiveProcess(&g_cheat_process_debug_hnd, g_cheat_process_metadata.process_id)); - - /* Start the process. */ - StartDebugProcess(g_cheat_process_metadata.process_id); - - /* Start debug events thread. */ - StartDebugEventsThread(); - - /* Signal to our fans. */ - g_cheat_process_event->Signal(); -} - -void DmntCheatManager::DetectThread(void *arg) { - static auto s_waiter = WaitableManager(1); - s_waiter.AddWaitable(LoadReadOnlySystemEvent(PrepareDebugNextApplication(), [](u64 timeout) { - /* Process stuff for new application. */ - DmntCheatManager::OnNewApplicationLaunch(); - - /* Setup detection for the next application, and close the duplicate handle. */ - svcCloseHandle(PrepareDebugNextApplication()); - - return ResultSuccess; - }, true)); - - s_waiter.Process(); -} - -void DmntCheatManager::VmThread(void *arg) { - while (true) { - /* Execute Cheat VM. */ - { - /* Acquire lock. */ - std::scoped_lock lk(g_cheat_lock); - - if (HasActiveCheatProcess()) { - /* Execute VM. */ - if (!g_needs_reload_vm_program || (g_cheat_vm->LoadProgram(g_cheat_entries, DmntCheatManager::MaxCheatCount))) { - /* Program: reloaded. */ - g_needs_reload_vm_program = false; - - /* Execute program if it's present. */ - if (g_cheat_vm->GetProgramSize() != 0) { - g_cheat_vm->Execute(&g_cheat_process_metadata); - } - } - - /* Apply frozen addresses. */ - for (auto const& [address, value] : g_frozen_addresses_map) { - WriteCheatProcessMemoryForVm(address, &value.value, value.width); - } - } - } - - constexpr u64 ONE_SECOND = 1000000000ul; - constexpr u64 NUM_TIMES = 12; - constexpr u64 DELAY = ONE_SECOND / NUM_TIMES; - svcSleepThread(DELAY); - } -} - -void DmntCheatManager::DebugEventsThread(void *arg) { - - while (R_SUCCEEDED(svcWaitSynchronizationSingle(g_cheat_process_debug_hnd, U64_MAX))) { - std::scoped_lock lk(g_cheat_lock); - - /* Handle any pending debug events. */ - if (HasActiveCheatProcess()) { - DmntCheatDebugEventsManager::ContinueCheatProcess(g_cheat_process_debug_hnd); - } - - } -} - -bool DmntCheatManager::GetHasActiveCheatProcess() { - std::scoped_lock lk(g_cheat_lock); - - return HasActiveCheatProcess(); -} - -Handle DmntCheatManager::GetCheatProcessEventHandle() { - return g_cheat_process_event->GetHandle(); -} - -Result DmntCheatManager::GetCheatProcessMetadata(CheatProcessMetadata *out) { - std::scoped_lock lk(g_cheat_lock); - - if (HasActiveCheatProcess()) { - *out = g_cheat_process_metadata; - return ResultSuccess; - } - - return ResultDmntCheatNotAttached; -} - -void DmntCheatManager::InitializeCheatManager() { - /* Create cheat process detection event. */ - g_cheat_process_event = CreateWriteOnlySystemEvent(); - - /* Create cheat vm. */ - g_cheat_vm = new DmntCheatVm(); - - /* Learn whether we should enable cheats by default. */ - { - u8 en; - if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_cheats_enabled_by_default", &en, sizeof(en)))) { - g_enable_cheats_by_default = (en != 0); - } - - if (R_SUCCEEDED(setsysGetSettingsItemValue("atmosphere", "dmnt_always_save_cheat_toggles", &en, sizeof(en)))) { - g_always_save_cheat_toggles = (en != 0); - } - } - - /* Initialize debug events manager. */ - DmntCheatDebugEventsManager::Initialize(); - - /* Spawn application detection thread, spawn cheat vm thread. */ - R_ASSERT(g_detect_thread.Initialize(&DmntCheatManager::DetectThread, nullptr, 0x4000, 39)); - R_ASSERT(g_vm_thread.Initialize(&DmntCheatManager::VmThread, nullptr, 0x4000, 48)); - - /* Start threads. */ - R_ASSERT(g_detect_thread.Start()); - R_ASSERT(g_vm_thread.Start()); -} diff --git a/stratosphere/dmnt/source/dmnt_cheat_manager.hpp b/stratosphere/dmnt/source/dmnt_cheat_manager.hpp deleted file mode 100644 index 3178d469a..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_manager.hpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - -#include "dmnt_cheat_types.hpp" - -class DmntCheatManager { - public: - static constexpr size_t MaxCheatCount = 0x80; - static constexpr size_t MaxFrozenAddressCount = 0x80; - private: - static Handle PrepareDebugNextApplication(); - static void OnNewApplicationLaunch(); - static void DetectThread(void *arg); - static void VmThread(void *arg); - static void DebugEventsThread(void *arg); - - static void StartDebugEventsThread(); - static void WaitDebugEventsThread(); - - static bool HasActiveCheatProcess(); - static void CloseActiveCheatProcess(); - static void ContinueCheatProcess(); - - static void ResetCheatEntry(size_t i); - static void ResetAllCheatEntries(); - static CheatEntry *GetFreeCheatEntry(); - static CheatEntry *GetCheatEntryById(size_t i); - static CheatEntry *GetCheatEntryByReadableName(const char *readable_name); - static bool ParseCheats(const char *cht_txt, size_t len); - static bool LoadCheats(u64 title_id, const u8 *build_id); - - static bool ParseCheatToggles(const char *s, size_t len); - static bool LoadCheatToggles(u64 title_id); - static void SaveCheatToggles(u64 title_id); - - static void ResetFrozenAddresses(); - public: - static bool GetHasActiveCheatProcess(); - static Handle GetCheatProcessEventHandle(); - static Result GetCheatProcessMetadata(CheatProcessMetadata *out); - static Result ForceOpenCheatProcess(); - - static Result ReadCheatProcessMemoryForVm(u64 proc_addr, void *out_data, size_t size); - static Result WriteCheatProcessMemoryForVm(u64 proc_addr, const void *data, size_t size); - - static Result GetCheatProcessMappingCount(u64 *out_count); - static Result GetCheatProcessMappings(MemoryInfo *mappings, size_t max_count, u64 *out_count, u64 offset); - static Result ReadCheatProcessMemory(u64 proc_addr, void *out_data, size_t size); - static Result WriteCheatProcessMemory(u64 proc_addr, const void *data, size_t size); - static Result QueryCheatProcessMemory(MemoryInfo *mapping, u64 address); - - static Result GetCheatCount(u64 *out_count); - static Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset); - static Result GetCheatById(CheatEntry *out_cheat, u32 cheat_id); - static Result ToggleCheat(u32 cheat_id); - static Result AddCheat(u32 *out_id, CheatDefinition *def, bool enabled); - static Result RemoveCheat(u32 cheat_id); - - static Result GetFrozenAddressCount(u64 *out_count); - static Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset); - static Result GetFrozenAddress(FrozenAddressEntry *frz_addr, u64 address); - static Result EnableFrozenAddress(u64 *out_value, u64 address, u64 width); - static Result DisableFrozenAddress(u64 address); - - static void InitializeCheatManager(); -}; diff --git a/stratosphere/dmnt/source/dmnt_cheat_service.cpp b/stratosphere/dmnt/source/dmnt_cheat_service.cpp deleted file mode 100644 index 54f339bda..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_service.cpp +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include "dmnt_cheat_service.hpp" -#include "dmnt_cheat_manager.hpp" - -/* ========================================================================================= */ -/* ==================================== Meta Commands ==================================== */ -/* ========================================================================================= */ - -void DmntCheatService::HasCheatProcess(Out out) { - out.SetValue(DmntCheatManager::GetHasActiveCheatProcess()); -} - -void DmntCheatService::GetCheatProcessEvent(Out out_event) { - out_event.SetValue(DmntCheatManager::GetCheatProcessEventHandle()); -} - -Result DmntCheatService::GetCheatProcessMetadata(Out out_metadata) { - return DmntCheatManager::GetCheatProcessMetadata(out_metadata.GetPointer()); -} - -Result DmntCheatService::ForceOpenCheatProcess() { - if (R_FAILED(DmntCheatManager::ForceOpenCheatProcess())) { - return ResultDmntCheatNotAttached; - } - - return ResultSuccess; -} - -/* ========================================================================================= */ -/* =================================== Memory Commands =================================== */ -/* ========================================================================================= */ - -Result DmntCheatService::GetCheatProcessMappingCount(Out out_count) { - return DmntCheatManager::GetCheatProcessMappingCount(out_count.GetPointer()); -} - -Result DmntCheatService::GetCheatProcessMappings(OutBuffer mappings, Out out_count, u64 offset) { - if (mappings.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - return DmntCheatManager::GetCheatProcessMappings(mappings.buffer, mappings.num_elements, out_count.GetPointer(), offset); -} - -Result DmntCheatService::ReadCheatProcessMemory(OutBuffer buffer, u64 address, u64 out_size) { - if (buffer.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - u64 sz = out_size; - if (buffer.num_elements < sz) { - sz = buffer.num_elements; - } - - return DmntCheatManager::ReadCheatProcessMemory(address, buffer.buffer, sz); -} - -Result DmntCheatService::WriteCheatProcessMemory(InBuffer buffer, u64 address, u64 in_size) { - if (buffer.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - u64 sz = in_size; - if (buffer.num_elements < sz) { - sz = buffer.num_elements; - } - - return DmntCheatManager::WriteCheatProcessMemory(address, buffer.buffer, sz); -} - -Result DmntCheatService::QueryCheatProcessMemory(Out mapping, u64 address) { - return DmntCheatManager::QueryCheatProcessMemory(mapping.GetPointer(), address); -} - -/* ========================================================================================= */ -/* =================================== Cheat Commands ==================================== */ -/* ========================================================================================= */ - -Result DmntCheatService::GetCheatCount(Out out_count) { - return DmntCheatManager::GetCheatCount(out_count.GetPointer()); -} - -Result DmntCheatService::GetCheats(OutBuffer cheats, Out out_count, u64 offset) { - if (cheats.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - return DmntCheatManager::GetCheats(cheats.buffer, cheats.num_elements, out_count.GetPointer(), offset); -} - -Result DmntCheatService::GetCheatById(OutBuffer cheat, u32 cheat_id) { - if (cheat.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - if (cheat.num_elements < 1) { - return ResultDmntCheatInvalidBuffer; - } - - return DmntCheatManager::GetCheatById(cheat.buffer, cheat_id); -} - -Result DmntCheatService::ToggleCheat(u32 cheat_id) { - return DmntCheatManager::ToggleCheat(cheat_id); -} - -Result DmntCheatService::AddCheat(InBuffer cheat, Out out_cheat_id, bool enabled) { - if (cheat.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - if (cheat.num_elements < 1) { - return ResultDmntCheatInvalidBuffer; - } - - return DmntCheatManager::AddCheat(out_cheat_id.GetPointer(), cheat.buffer, enabled); -} - -Result DmntCheatService::RemoveCheat(u32 cheat_id) { - return DmntCheatManager::RemoveCheat(cheat_id); -} - -/* ========================================================================================= */ -/* =================================== Address Commands ================================== */ -/* ========================================================================================= */ - -Result DmntCheatService::GetFrozenAddressCount(Out out_count) { - return DmntCheatManager::GetFrozenAddressCount(out_count.GetPointer()); -} - -Result DmntCheatService::GetFrozenAddresses(OutBuffer frz_addrs, Out out_count, u64 offset) { - if (frz_addrs.buffer == nullptr) { - return ResultDmntCheatNullBuffer; - } - - return DmntCheatManager::GetFrozenAddresses(frz_addrs.buffer, frz_addrs.num_elements, out_count.GetPointer(), offset); -} - -Result DmntCheatService::GetFrozenAddress(Out entry, u64 address) { - return DmntCheatManager::GetFrozenAddress(entry.GetPointer(), address); -} - -Result DmntCheatService::EnableFrozenAddress(Out out_value, u64 address, u64 width) { - switch (width) { - case 1: - case 2: - case 4: - case 8: - break; - default: - return ResultDmntCheatInvalidFreezeWidth; - } - - return DmntCheatManager::EnableFrozenAddress(out_value.GetPointer(), address, width); -} - -Result DmntCheatService::DisableFrozenAddress(u64 address) { - return DmntCheatManager::DisableFrozenAddress(address); -} diff --git a/stratosphere/dmnt/source/dmnt_cheat_service.hpp b/stratosphere/dmnt/source/dmnt_cheat_service.hpp deleted file mode 100644 index b473d788e..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_service.hpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - -#include "dmnt_cheat_types.hpp" - -class DmntCheatService final : public IServiceObject { - private: - enum class CommandId { - /* Meta */ - HasCheatProcess = 65000, - GetCheatProcessEvent = 65001, - GetCheatProcessMetadata = 65002, - ForceOpenCheatProcess = 65003, - - /* Interact with Memory */ - GetCheatProcessMappingCount = 65100, - GetCheatProcessMappings = 65101, - ReadCheatProcessMemory = 65102, - WriteCheatProcessMemory = 65103, - QueryCheatProcessMemory = 65104, - - /* Interact with Cheats */ - GetCheatCount = 65200, - GetCheats = 65201, - GetCheatById = 65202, - ToggleCheat = 65203, - AddCheat = 65204, - RemoveCheat = 65205, - - /* Interact with Frozen Addresses */ - GetFrozenAddressCount = 65300, - GetFrozenAddresses = 65301, - GetFrozenAddress = 65302, - EnableFrozenAddress = 65303, - DisableFrozenAddress = 65304, - }; - private: - void HasCheatProcess(Out out); - void GetCheatProcessEvent(Out out_event); - Result GetCheatProcessMetadata(Out out_metadata); - Result ForceOpenCheatProcess(); - - Result GetCheatProcessMappingCount(Out out_count); - Result GetCheatProcessMappings(OutBuffer mappings, Out out_count, u64 offset); - Result ReadCheatProcessMemory(OutBuffer buffer, u64 address, u64 out_size); - Result WriteCheatProcessMemory(InBuffer buffer, u64 address, u64 in_size); - Result QueryCheatProcessMemory(Out mapping, u64 address); - - Result GetCheatCount(Out out_count); - Result GetCheats(OutBuffer cheats, Out out_count, u64 offset); - Result GetCheatById(OutBuffer cheat, u32 cheat_id); - Result ToggleCheat(u32 cheat_id); - Result AddCheat(InBuffer cheat, Out out_cheat_id, bool enabled); - Result RemoveCheat(u32 cheat_id); - - Result GetFrozenAddressCount(Out out_count); - Result GetFrozenAddresses(OutBuffer addresses, Out out_count, u64 offset); - Result GetFrozenAddress(Out entry, u64 address); - Result EnableFrozenAddress(Out out_value, u64 address, u64 width); - Result DisableFrozenAddress(u64 address); - - public: - DEFINE_SERVICE_DISPATCH_TABLE { - MAKE_SERVICE_COMMAND_META(DmntCheatService, HasCheatProcess), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessEvent), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMetadata), - MAKE_SERVICE_COMMAND_META(DmntCheatService, ForceOpenCheatProcess), - - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMappingCount), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatProcessMappings), - MAKE_SERVICE_COMMAND_META(DmntCheatService, ReadCheatProcessMemory), - MAKE_SERVICE_COMMAND_META(DmntCheatService, WriteCheatProcessMemory), - MAKE_SERVICE_COMMAND_META(DmntCheatService, QueryCheatProcessMemory), - - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatCount), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheats), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetCheatById), - MAKE_SERVICE_COMMAND_META(DmntCheatService, ToggleCheat), - MAKE_SERVICE_COMMAND_META(DmntCheatService, AddCheat), - MAKE_SERVICE_COMMAND_META(DmntCheatService, RemoveCheat), - - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddressCount), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddresses), - MAKE_SERVICE_COMMAND_META(DmntCheatService, GetFrozenAddress), - MAKE_SERVICE_COMMAND_META(DmntCheatService, EnableFrozenAddress), - MAKE_SERVICE_COMMAND_META(DmntCheatService, DisableFrozenAddress), - }; -}; diff --git a/stratosphere/dmnt/source/dmnt_cheat_types.hpp b/stratosphere/dmnt/source/dmnt_cheat_types.hpp deleted file mode 100644 index 8c5305471..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_types.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include - -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 address_space_extents; - u8 main_nso_build_id[0x20]; -}; - -struct CheatDefinition { - char readable_name[0x40]; - uint32_t num_opcodes; - uint32_t opcodes[0x100]; -}; - -struct CheatEntry { - bool enabled; - uint32_t cheat_id; - CheatDefinition definition; -}; - -struct FrozenAddressValue { - u64 value; - u8 width; -}; - -struct FrozenAddressEntry { - u64 address; - FrozenAddressValue value; -}; \ No newline at end of file diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp deleted file mode 100644 index 26c71194c..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp +++ /dev/null @@ -1,1212 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include "dmnt_cheat_types.hpp" -#include "dmnt_cheat_vm.hpp" -#include "dmnt_cheat_manager.hpp" -#include "dmnt_hid.hpp" - -void DmntCheatVm::DebugLog(u32 log_id, u64 value) { - /* Just unconditionally try to create the log folder. */ - mkdir("/atmosphere/cheat_vm_logs", 0777); - FILE *f_log = NULL; - { - char log_path[FS_MAX_PATH]; - snprintf(log_path, sizeof(log_path), "/atmosphere/cheat_vm_logs/%x.log", log_id); - f_log = fopen(log_path, "ab"); - } - if (f_log != NULL) { - ON_SCOPE_EXIT { fclose(f_log); }; - fprintf(f_log, "%016lx\n", value); - } -} - -void DmntCheatVm::OpenDebugLogFile() { - #ifdef DMNT_CHEAT_VM_DEBUG_LOG - CloseDebugLogFile(); - this->debug_log_file = fopen("cheat_vm_log.txt", "ab"); - #endif -} - -void DmntCheatVm::CloseDebugLogFile() { - #ifdef DMNT_CHEAT_VM_DEBUG_LOG - if (this->debug_log_file != NULL) { - fclose(this->debug_log_file); - this->debug_log_file = NULL; - } - #endif -} - -void DmntCheatVm::LogToDebugFile(const char *format, ...) { - #ifdef DMNT_CHEAT_VM_DEBUG_LOG - if (this->debug_log_file != NULL) { - va_list arglist; - va_start(arglist, format); - vfprintf(this->debug_log_file, format, arglist); - va_end(arglist); - } - #endif -} - -void DmntCheatVm::LogOpcode(const CheatVmOpcode *opcode) { - #ifndef DMNT_CHEAT_VM_DEBUG_LOG - return; - #endif - switch (opcode->opcode) { - case CheatVmOpcodeType_StoreStatic: - this->LogToDebugFile("Opcode: Store Static\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->store_static.bit_width); - this->LogToDebugFile("Mem Type: %x\n", opcode->store_static.mem_type); - this->LogToDebugFile("Reg Idx: %x\n", opcode->store_static.offset_register); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->store_static.rel_address); - this->LogToDebugFile("Value: %lx\n", opcode->store_static.value.bit64); - break; - case CheatVmOpcodeType_BeginConditionalBlock: - this->LogToDebugFile("Opcode: Begin Conditional\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->begin_cond.bit_width); - this->LogToDebugFile("Mem Type: %x\n", opcode->begin_cond.mem_type); - this->LogToDebugFile("Cond Type: %x\n", opcode->begin_cond.cond_type); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_cond.rel_address); - this->LogToDebugFile("Value: %lx\n", opcode->begin_cond.value.bit64); - break; - case CheatVmOpcodeType_EndConditionalBlock: - this->LogToDebugFile("Opcode: End Conditional\n"); - break; - case CheatVmOpcodeType_ControlLoop: - if (opcode->ctrl_loop.start_loop) { - this->LogToDebugFile("Opcode: Start Loop\n"); - this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); - this->LogToDebugFile("Num Iters: %x\n", opcode->ctrl_loop.num_iters); - } else { - this->LogToDebugFile("Opcode: End Loop\n"); - this->LogToDebugFile("Reg Idx: %x\n", opcode->ctrl_loop.reg_index); - } - break; - case CheatVmOpcodeType_LoadRegisterStatic: - this->LogToDebugFile("Opcode: Load Register Static\n"); - this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_static.reg_index); - this->LogToDebugFile("Value: %lx\n", opcode->ldr_static.value); - break; - case CheatVmOpcodeType_LoadRegisterMemory: - this->LogToDebugFile("Opcode: Load Register Memory\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->ldr_memory.bit_width); - this->LogToDebugFile("Reg Idx: %x\n", opcode->ldr_memory.reg_index); - this->LogToDebugFile("Mem Type: %x\n", opcode->ldr_memory.mem_type); - this->LogToDebugFile("From Reg: %d\n", opcode->ldr_memory.load_from_reg); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->ldr_memory.rel_address); - break; - case CheatVmOpcodeType_StoreStaticToAddress: - this->LogToDebugFile("Opcode: Store Static to Address\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->str_static.bit_width); - this->LogToDebugFile("Reg Idx: %x\n", opcode->str_static.reg_index); - if (opcode->str_static.add_offset_reg) { - this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_static.offset_reg_index); - } - this->LogToDebugFile("Incr Reg: %d\n", opcode->str_static.increment_reg); - this->LogToDebugFile("Value: %lx\n", opcode->str_static.value); - break; - case CheatVmOpcodeType_PerformArithmeticStatic: - this->LogToDebugFile("Opcode: Perform Static Arithmetic\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_static.bit_width); - this->LogToDebugFile("Reg Idx: %x\n", opcode->perform_math_static.reg_index); - this->LogToDebugFile("Math Type: %x\n", opcode->perform_math_static.math_type); - this->LogToDebugFile("Value: %lx\n", opcode->perform_math_static.value); - break; - case CheatVmOpcodeType_BeginKeypressConditionalBlock: - this->LogToDebugFile("Opcode: Begin Keypress Conditional\n"); - this->LogToDebugFile("Key Mask: %x\n", opcode->begin_keypress_cond.key_mask); - break; - case CheatVmOpcodeType_PerformArithmeticRegister: - this->LogToDebugFile("Opcode: Perform Register Arithmetic\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->perform_math_reg.bit_width); - this->LogToDebugFile("Dst Idx: %x\n", opcode->perform_math_reg.dst_reg_index); - this->LogToDebugFile("Src1 Idx: %x\n", opcode->perform_math_reg.src_reg_1_index); - if (opcode->perform_math_reg.has_immediate) { - this->LogToDebugFile("Value: %lx\n", opcode->perform_math_reg.value.bit64); - } else { - this->LogToDebugFile("Src2 Idx: %x\n", opcode->perform_math_reg.src_reg_2_index); - } - break; - case CheatVmOpcodeType_StoreRegisterToAddress: - this->LogToDebugFile("Opcode: Store Register to Address\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->str_register.bit_width); - this->LogToDebugFile("S Reg Idx: %x\n", opcode->str_register.str_reg_index); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->str_register.addr_reg_index); - this->LogToDebugFile("Incr Reg: %d\n", opcode->str_register.increment_reg); - switch (opcode->str_register.ofs_type) { - case StoreRegisterOffsetType_None: - break; - case StoreRegisterOffsetType_Reg: - this->LogToDebugFile("O Reg Idx: %x\n", opcode->str_register.ofs_reg_index); - break; - case StoreRegisterOffsetType_Imm: - this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); - break; - case StoreRegisterOffsetType_MemReg: - this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); - break; - case StoreRegisterOffsetType_MemImm: - case StoreRegisterOffsetType_MemImmReg: - this->LogToDebugFile("Mem Type: %x\n", opcode->str_register.mem_type); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->str_register.rel_address); - break; - } - break; - case CheatVmOpcodeType_BeginRegisterConditionalBlock: - this->LogToDebugFile("Opcode: Begin Register Conditional\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->begin_reg_cond.bit_width); - this->LogToDebugFile("Cond Type: %x\n", opcode->begin_reg_cond.cond_type); - this->LogToDebugFile("V Reg Idx: %x\n", opcode->begin_reg_cond.val_reg_index); - switch (opcode->begin_reg_cond.comp_type) { - case CompareRegisterValueType_StaticValue: - this->LogToDebugFile("Comp Type: Static Value\n"); - this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64); - break; - case CompareRegisterValueType_OtherRegister: - this->LogToDebugFile("Comp Type: Other Register\n"); - this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index); - break; - case CompareRegisterValueType_MemoryRelAddr: - this->LogToDebugFile("Comp Type: Memory Relative Address\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); - break; - case CompareRegisterValueType_MemoryOfsReg: - this->LogToDebugFile("Comp Type: Memory Offset Register\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); - break; - case CompareRegisterValueType_RegisterRelAddr: - this->LogToDebugFile("Comp Type: Register Relative Address\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); - break; - case CompareRegisterValueType_RegisterOfsReg: - this->LogToDebugFile("Comp Type: Register Offset Register\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); - break; - } - break; - case CheatVmOpcodeType_SaveRestoreRegister: - this->LogToDebugFile("Opcode: Save or Restore Register\n"); - this->LogToDebugFile("Dst Idx: %x\n", opcode->save_restore_reg.dst_index); - this->LogToDebugFile("Src Idx: %x\n", opcode->save_restore_reg.src_index); - this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_reg.op_type); - break; - case CheatVmOpcodeType_SaveRestoreRegisterMask: - this->LogToDebugFile("Opcode: Save or Restore Register Mask\n"); - this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_regmask.op_type); - for (size_t i = 0; i < NumRegisters; i++) { - this->LogToDebugFile("Act[%02x]: %d\n", i, opcode->save_restore_regmask.should_operate[i]); - } - break; - case CheatVmOpcodeType_DebugLog: - this->LogToDebugFile("Opcode: Debug Log\n"); - this->LogToDebugFile("Bit Width: %x\n", opcode->debug_log.bit_width); - this->LogToDebugFile("Log ID: %x\n", opcode->debug_log.log_id); - this->LogToDebugFile("Val Type: %x\n", opcode->debug_log.val_type); - switch (opcode->debug_log.val_type) { - case DebugLogValueType_RegisterValue: - this->LogToDebugFile("Val Type: Register Value\n"); - this->LogToDebugFile("X Reg Idx: %x\n", opcode->debug_log.val_reg_index); - break; - case DebugLogValueType_MemoryRelAddr: - this->LogToDebugFile("Val Type: Memory Relative Address\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); - break; - case DebugLogValueType_MemoryOfsReg: - this->LogToDebugFile("Val Type: Memory Offset Register\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); - break; - case DebugLogValueType_RegisterRelAddr: - this->LogToDebugFile("Val Type: Register Relative Address\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); - break; - case DebugLogValueType_RegisterOfsReg: - this->LogToDebugFile("Val Type: Register Offset Register\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); - break; - } - default: - this->LogToDebugFile("Unknown opcode: %x\n", opcode->opcode); - break; - } -} - -bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { - /* If we've ever seen a decode failure, return false. */ - bool valid = this->decode_success; - CheatVmOpcode opcode = {}; - ON_SCOPE_EXIT { - this->decode_success &= valid; - if (valid) { - *out = opcode; - } - }; - - /* Helper function for getting instruction dwords. */ - auto GetNextDword = [&]() { - if (this->instruction_ptr >= this->num_opcodes) { - valid = false; - return static_cast(0); - } - return this->program[this->instruction_ptr++]; - }; - - /* Helper function for parsing a VmInt. */ - auto GetNextVmInt = [&](const u32 bit_width) { - VmInt val = {0}; - - const u32 first_dword = GetNextDword(); - switch (bit_width) { - case 1: - val.bit8 = (u8)first_dword; - break; - case 2: - val.bit16 = (u16)first_dword; - break; - case 4: - val.bit32 = first_dword; - break; - case 8: - val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); - break; - } - - return val; - }; - - /* Read opcode. */ - const u32 first_dword = GetNextDword(); - if (!valid) { - return valid; - } - - opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF)); - if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) { - opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); - } - if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { - opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); - } - - /* detect condition start. */ - switch (opcode.opcode) { - case CheatVmOpcodeType_BeginConditionalBlock: - case CheatVmOpcodeType_BeginKeypressConditionalBlock: - case CheatVmOpcodeType_BeginRegisterConditionalBlock: - opcode.begin_conditional_block = true; - break; - default: - opcode.begin_conditional_block = false; - break; - } - - switch (opcode.opcode) { - case CheatVmOpcodeType_StoreStatic: - { - /* 0TMR00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ - /* Read additional words. */ - const u32 second_dword = GetNextDword(); - opcode.store_static.bit_width = (first_dword >> 24) & 0xF; - opcode.store_static.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); - opcode.store_static.offset_register = ((first_dword >> 16) & 0xF); - opcode.store_static.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); - opcode.store_static.value = GetNextVmInt(opcode.store_static.bit_width); - } - break; - case CheatVmOpcodeType_BeginConditionalBlock: - { - /* 1TMC00AA AAAAAAAA YYYYYYYY (YYYYYYYY) */ - /* Read additional words. */ - const u32 second_dword = GetNextDword(); - opcode.begin_cond.bit_width = (first_dword >> 24) & 0xF; - opcode.begin_cond.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); - opcode.begin_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); - opcode.begin_cond.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); - opcode.begin_cond.value = GetNextVmInt(opcode.store_static.bit_width); - } - break; - case CheatVmOpcodeType_EndConditionalBlock: - { - /* 20000000 */ - /* There's actually nothing left to process here! */ - } - break; - case CheatVmOpcodeType_ControlLoop: - { - /* 300R0000 VVVVVVVV */ - /* 310R0000 */ - /* Parse register, whether loop start or loop end. */ - opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; - opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); - - /* Read number of iters if loop start. */ - if (opcode.ctrl_loop.start_loop) { - opcode.ctrl_loop.num_iters = GetNextDword(); - } - } - break; - case CheatVmOpcodeType_LoadRegisterStatic: - { - /* 400R0000 VVVVVVVV VVVVVVVV */ - /* Read additional words. */ - opcode.ldr_static.reg_index = ((first_dword >> 16) & 0xF); - opcode.ldr_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); - } - break; - case CheatVmOpcodeType_LoadRegisterMemory: - { - /* 5TMRI0AA AAAAAAAA */ - /* Read additional words. */ - const u32 second_dword = GetNextDword(); - opcode.ldr_memory.bit_width = (first_dword >> 24) & 0xF; - opcode.ldr_memory.mem_type = (MemoryAccessType)((first_dword >> 20) & 0xF); - opcode.ldr_memory.reg_index = ((first_dword >> 16) & 0xF); - opcode.ldr_memory.load_from_reg = ((first_dword >> 12) & 0xF) != 0; - opcode.ldr_memory.rel_address = ((u64)(first_dword & 0xFF) << 32ul) | ((u64)second_dword); - } - break; - case CheatVmOpcodeType_StoreStaticToAddress: - { - /* 6T0RIor0 VVVVVVVV VVVVVVVV */ - /* Read additional words. */ - opcode.str_static.bit_width = (first_dword >> 24) & 0xF; - opcode.str_static.reg_index = ((first_dword >> 16) & 0xF); - opcode.str_static.increment_reg = ((first_dword >> 12) & 0xF) != 0; - opcode.str_static.add_offset_reg = ((first_dword >> 8) & 0xF) != 0; - opcode.str_static.offset_reg_index = ((first_dword >> 4) & 0xF); - opcode.str_static.value = (((u64)GetNextDword()) << 32ul) | ((u64)GetNextDword()); - } - break; - case CheatVmOpcodeType_PerformArithmeticStatic: - { - /* 7T0RC000 VVVVVVVV */ - /* Read additional words. */ - opcode.perform_math_static.bit_width = (first_dword >> 24) & 0xF; - opcode.perform_math_static.reg_index = ((first_dword >> 16) & 0xF); - opcode.perform_math_static.math_type = (RegisterArithmeticType)((first_dword >> 12) & 0xF); - opcode.perform_math_static.value = GetNextDword(); - } - break; - case CheatVmOpcodeType_BeginKeypressConditionalBlock: - { - /* 8kkkkkkk */ - /* Just parse the mask. */ - opcode.begin_keypress_cond.key_mask = first_dword & 0x0FFFFFFF; - } - break; - case CheatVmOpcodeType_PerformArithmeticRegister: - { - /* 9TCRSIs0 (VVVVVVVV (VVVVVVVV)) */ - opcode.perform_math_reg.bit_width = (first_dword >> 24) & 0xF; - opcode.perform_math_reg.math_type = (RegisterArithmeticType)((first_dword >> 20) & 0xF); - opcode.perform_math_reg.dst_reg_index = ((first_dword >> 16) & 0xF); - opcode.perform_math_reg.src_reg_1_index = ((first_dword >> 12) & 0xF); - opcode.perform_math_reg.has_immediate = ((first_dword >> 8) & 0xF) != 0; - if (opcode.perform_math_reg.has_immediate) { - opcode.perform_math_reg.src_reg_2_index = 0; - opcode.perform_math_reg.value = GetNextVmInt(opcode.perform_math_reg.bit_width); - } else { - opcode.perform_math_reg.src_reg_2_index = ((first_dword >> 4) & 0xF); - } - } - break; - case CheatVmOpcodeType_StoreRegisterToAddress: - { - /* ATSRIOxa (aaaaaaaa) */ - /* A = opcode 10 */ - /* T = bit width */ - /* S = src register index */ - /* R = address register index */ - /* I = 1 if increment address register, 0 if not increment address register */ - /* O = offset type, 0 = None, 1 = Register, 2 = Immediate, 3 = Memory Region, - 4 = Memory Region + Relative Address (ignore address register), 5 = Memory Region + Relative Address */ - /* x = offset register (for offset type 1), memory type (for offset type 3) */ - /* a = relative address (for offset type 2+3) */ - opcode.str_register.bit_width = (first_dword >> 24) & 0xF; - opcode.str_register.str_reg_index = ((first_dword >> 20) & 0xF); - opcode.str_register.addr_reg_index = ((first_dword >> 16) & 0xF); - opcode.str_register.increment_reg = ((first_dword >> 12) & 0xF) != 0; - opcode.str_register.ofs_type = (StoreRegisterOffsetType)(((first_dword >> 8) & 0xF)); - opcode.str_register.ofs_reg_index = ((first_dword >> 4) & 0xF); - switch (opcode.str_register.ofs_type) { - case StoreRegisterOffsetType_None: - case StoreRegisterOffsetType_Reg: - /* Nothing more to do */ - break; - case StoreRegisterOffsetType_Imm: - opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - case StoreRegisterOffsetType_MemReg: - opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - break; - case StoreRegisterOffsetType_MemImm: - case StoreRegisterOffsetType_MemImmReg: - opcode.str_register.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.str_register.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - default: - opcode.str_register.ofs_type = StoreRegisterOffsetType_None; - break; - } - } - break; - case CheatVmOpcodeType_BeginRegisterConditionalBlock: - { - /* C0TcSX## */ - /* C0TcS0Ma aaaaaaaa */ - /* C0TcS1Mr */ - /* C0TcS2Ra aaaaaaaa */ - /* C0TcS3Rr */ - /* C0TcS400 VVVVVVVV (VVVVVVVV) */ - /* C0TcS5X0 */ - /* C0 = opcode 0xC0 */ - /* T = bit width */ - /* c = condition type. */ - /* S = source register. */ - /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ - /* 2 = register with relative offset, 3 = register with offset register, 4 = static value, 5 = other register. */ - /* M = memory type. */ - /* R = address register. */ - /* a = relative address. */ - /* r = offset register. */ - /* X = other register. */ - /* V = value. */ - opcode.begin_reg_cond.bit_width = (first_dword >> 20) & 0xF; - opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); - opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); - opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF); - - switch (opcode.begin_reg_cond.comp_type) { - case CompareRegisterValueType_StaticValue: - opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width); - break; - case CompareRegisterValueType_OtherRegister: - opcode.begin_reg_cond.other_reg_index = ((first_dword >> 4) & 0xF); - break; - case CompareRegisterValueType_MemoryRelAddr: - opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - case CompareRegisterValueType_MemoryOfsReg: - opcode.begin_reg_cond.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); - break; - case CompareRegisterValueType_RegisterRelAddr: - opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); - opcode.begin_reg_cond.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - case CompareRegisterValueType_RegisterOfsReg: - opcode.begin_reg_cond.addr_reg_index = ((first_dword >> 4) & 0xF); - opcode.begin_reg_cond.ofs_reg_index = (first_dword & 0xF); - break; - } - } - break; - case CheatVmOpcodeType_SaveRestoreRegister: - { - /* C10D0Sx0 */ - /* C1 = opcode 0xC1 */ - /* D = destination index. */ - /* S = source index. */ - /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving a register, 0 if restoring a register. */ - /* NOTE: If we add more save slots later, current encoding is backwards compatible. */ - opcode.save_restore_reg.dst_index = (first_dword >> 16) & 0xF; - opcode.save_restore_reg.src_index = (first_dword >> 8) & 0xF; - opcode.save_restore_reg.op_type = (SaveRestoreRegisterOpType)((first_dword >> 4) & 0xF); - } - break; - case CheatVmOpcodeType_SaveRestoreRegisterMask: - { - /* C2x0XXXX */ - /* C2 = opcode 0xC2 */ - /* x = 3 if clearing reg, 2 if clearing saved value, 1 if saving, 0 if restoring. */ - /* X = 16-bit bitmask, bit i --> save or restore register i. */ - opcode.save_restore_regmask.op_type = (SaveRestoreRegisterOpType)((first_dword >> 20) & 0xF); - for (size_t i = 0; i < NumRegisters; i++) { - opcode.save_restore_regmask.should_operate[i] = (first_dword & (1u << i)) != 0; - } - } - break; - case CheatVmOpcodeType_DebugLog: - { - /* FFFTIX## */ - /* FFFTI0Ma aaaaaaaa */ - /* FFFTI1Mr */ - /* FFFTI2Ra aaaaaaaa */ - /* FFFTI3Rr */ - /* FFFTI4X0 */ - /* FFF = opcode 0xFFF */ - /* T = bit width. */ - /* I = log id. */ - /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ - /* 2 = register with relative offset, 3 = register with offset register, 4 = register value. */ - /* M = memory type. */ - /* R = address register. */ - /* a = relative address. */ - /* r = offset register. */ - /* X = value register. */ - opcode.debug_log.bit_width = (first_dword >> 16) & 0xF; - opcode.debug_log.log_id = ((first_dword >> 12) & 0xF); - opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF); - - switch (opcode.debug_log.val_type) { - case DebugLogValueType_RegisterValue: - opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); - break; - case DebugLogValueType_MemoryRelAddr: - opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - case DebugLogValueType_MemoryOfsReg: - opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); - opcode.debug_log.ofs_reg_index = (first_dword & 0xF); - break; - case DebugLogValueType_RegisterRelAddr: - opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); - opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); - break; - case DebugLogValueType_RegisterOfsReg: - opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); - opcode.debug_log.ofs_reg_index = (first_dword & 0xF); - break; - } - } - break; - case CheatVmOpcodeType_ExtendedWidth: - case CheatVmOpcodeType_DoubleExtendedWidth: - default: - /* Unrecognized instruction cannot be decoded. */ - valid = false; - break; - } - - /* End decoding. */ - return valid; -} - -void DmntCheatVm::SkipConditionalBlock() { - if (this->condition_depth > 0) { - /* We want to continue until we're out of the current block. */ - const size_t desired_depth = this->condition_depth - 1; - - CheatVmOpcode skip_opcode; - while (this->condition_depth > desired_depth && this->DecodeNextOpcode(&skip_opcode)) { - /* Decode instructions until we see end of the current conditional block. */ - /* NOTE: This is broken in gateway's implementation. */ - /* Gateway currently checks for "0x2" instead of "0x20000000" */ - /* In addition, they do a linear scan instead of correctly decoding opcodes. */ - /* This causes issues if "0x2" appears as an immediate in the conditional block... */ - - /* We also support nesting of conditional blocks, and Gateway does not. */ - if (skip_opcode.begin_conditional_block) { - this->condition_depth++; - } else if (skip_opcode.opcode == CheatVmOpcodeType_EndConditionalBlock) { - this->condition_depth--; - } - } - } else { - /* Skipping, but this->condition_depth = 0. */ - /* This is an error condition. */ - /* However, I don't actually believe it is possible for this to happen. */ - /* I guess we'll throw a fatal error here, so as to encourage me to fix the VM */ - /* in the event that someone triggers it? I don't know how you'd do that. */ - fatalSimple(ResultDmntCheatVmInvalidCondDepth); - } -} - -u64 DmntCheatVm::GetVmInt(VmInt value, u32 bit_width) { - switch (bit_width) { - case 1: - return value.bit8; - case 2: - return value.bit16; - case 4: - return value.bit32; - case 8: - return value.bit64; - default: - /* Invalid bit width -> return 0. */ - return 0; - } -} - -u64 DmntCheatVm::GetCheatProcessAddress(const CheatProcessMetadata* metadata, MemoryAccessType mem_type, u64 rel_address) { - switch (mem_type) { - case MemoryAccessType_MainNso: - default: - return metadata->main_nso_extents.base + rel_address; - case MemoryAccessType_Heap: - return metadata->heap_extents.base + rel_address; - } -} - -void DmntCheatVm::ResetState() { - for (size_t i = 0; i < DmntCheatVm::NumRegisters; i++) { - this->registers[i] = 0; - this->saved_values[i] = 0; - this->loop_tops[i] = 0; - } - this->instruction_ptr = 0; - this->condition_depth = 0; - this->decode_success = true; -} - -bool DmntCheatVm::LoadProgram(const CheatEntry *cheats, size_t num_cheats) { - /* Reset opcode count. */ - this->num_opcodes = 0; - - for (size_t i = 0; i < num_cheats; i++) { - if (cheats[i].enabled) { - /* Bounds check. */ - if (cheats[i].definition.num_opcodes + this->num_opcodes > MaximumProgramOpcodeCount) { - this->num_opcodes = 0; - return false; - } - - for (size_t n = 0; n < cheats[i].definition.num_opcodes; n++) { - this->program[this->num_opcodes++] = cheats[i].definition.opcodes[n]; - } - } - } - - return true; -} - -void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { - CheatVmOpcode cur_opcode; - u64 kDown = 0; - - /* Get Keys down. */ - HidManagement::GetKeysDown(&kDown); - - this->OpenDebugLogFile(); - ON_SCOPE_EXIT { this->CloseDebugLogFile(); }; - - this->LogToDebugFile("Started VM execution.\n"); - this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); - this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); - this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF)); - - /* Clear VM state. */ - this->ResetState(); - - /* Loop until program finishes. */ - while (this->DecodeNextOpcode(&cur_opcode)) { - this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr); - - for (size_t i = 0; i < NumRegisters; i++) { - this->LogToDebugFile("Registers[%02x]: %016lx\n", i, this->registers[i]); - } - - for (size_t i = 0; i < NumRegisters; i++) { - this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]); - } - this->LogOpcode(&cur_opcode); - - /* Increment conditional depth, if relevant. */ - if (cur_opcode.begin_conditional_block) { - this->condition_depth++; - } - - switch (cur_opcode.opcode) { - case CheatVmOpcodeType_StoreStatic: - { - /* Calculate address, write value to memory. */ - u64 dst_address = GetCheatProcessAddress(metadata, cur_opcode.store_static.mem_type, cur_opcode.store_static.rel_address + this->registers[cur_opcode.store_static.offset_register]); - u64 dst_value = GetVmInt(cur_opcode.store_static.value, cur_opcode.store_static.bit_width); - switch (cur_opcode.store_static.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.store_static.bit_width); - break; - } - } - break; - case CheatVmOpcodeType_BeginConditionalBlock: - { - /* Read value from memory. */ - u64 src_address = GetCheatProcessAddress(metadata, cur_opcode.begin_cond.mem_type, cur_opcode.begin_cond.rel_address); - u64 src_value = 0; - switch (cur_opcode.store_static.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::ReadCheatProcessMemoryForVm(src_address, &src_value, cur_opcode.begin_cond.bit_width); - break; - } - /* Check against condition. */ - u64 cond_value = GetVmInt(cur_opcode.begin_cond.value, cur_opcode.begin_cond.bit_width); - bool cond_met = false; - switch (cur_opcode.begin_cond.cond_type) { - case ConditionalComparisonType_GT: - cond_met = src_value > cond_value; - break; - case ConditionalComparisonType_GE: - cond_met = src_value >= cond_value; - break; - case ConditionalComparisonType_LT: - cond_met = src_value < cond_value; - break; - case ConditionalComparisonType_LE: - cond_met = src_value <= cond_value; - break; - case ConditionalComparisonType_EQ: - cond_met = src_value == cond_value; - break; - case ConditionalComparisonType_NE: - cond_met = src_value != cond_value; - break; - } - /* Skip conditional block if condition not met. */ - if (!cond_met) { - this->SkipConditionalBlock(); - } - } - break; - case CheatVmOpcodeType_EndConditionalBlock: - /* Decrement the condition depth. */ - /* We will assume, graciously, that mismatched conditional block ends are a nop. */ - if (this->condition_depth > 0) { - this->condition_depth--; - } - break; - case CheatVmOpcodeType_ControlLoop: - if (cur_opcode.ctrl_loop.start_loop) { - /* Start a loop. */ - this->registers[cur_opcode.ctrl_loop.reg_index] = cur_opcode.ctrl_loop.num_iters; - this->loop_tops[cur_opcode.ctrl_loop.reg_index] = this->instruction_ptr; - } else { - /* End a loop. */ - this->registers[cur_opcode.ctrl_loop.reg_index]--; - if (this->registers[cur_opcode.ctrl_loop.reg_index] != 0) { - this->instruction_ptr = this->loop_tops[cur_opcode.ctrl_loop.reg_index]; - } - } - break; - case CheatVmOpcodeType_LoadRegisterStatic: - /* Set a register to a static value. */ - this->registers[cur_opcode.ldr_static.reg_index] = cur_opcode.ldr_static.value; - break; - case CheatVmOpcodeType_LoadRegisterMemory: - { - /* Choose source address. */ - u64 src_address; - if (cur_opcode.ldr_memory.load_from_reg) { - src_address = this->registers[cur_opcode.ldr_memory.reg_index] + cur_opcode.ldr_memory.rel_address; - } else { - src_address = GetCheatProcessAddress(metadata, cur_opcode.ldr_memory.mem_type, cur_opcode.ldr_memory.rel_address); - } - /* Read into register. Gateway only reads on valid bitwidth. */ - switch (cur_opcode.ldr_memory.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::ReadCheatProcessMemoryForVm(src_address, &this->registers[cur_opcode.ldr_memory.reg_index], cur_opcode.ldr_memory.bit_width); - break; - } - } - break; - case CheatVmOpcodeType_StoreStaticToAddress: - { - /* Calculate address. */ - u64 dst_address = this->registers[cur_opcode.str_static.reg_index]; - u64 dst_value = cur_opcode.str_static.value; - if (cur_opcode.str_static.add_offset_reg) { - dst_address += this->registers[cur_opcode.str_static.offset_reg_index]; - } - /* Write value to memory. Gateway only writes on valid bitwidth. */ - switch (cur_opcode.str_static.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.str_static.bit_width); - break; - } - /* Increment register if relevant. */ - if (cur_opcode.str_static.increment_reg) { - this->registers[cur_opcode.str_static.reg_index] += cur_opcode.str_static.bit_width; - } - } - break; - case CheatVmOpcodeType_PerformArithmeticStatic: - { - /* Do requested math. */ - switch (cur_opcode.perform_math_static.math_type) { - case RegisterArithmeticType_Addition: - this->registers[cur_opcode.perform_math_static.reg_index] += (u64)cur_opcode.perform_math_static.value; - break; - case RegisterArithmeticType_Subtraction: - this->registers[cur_opcode.perform_math_static.reg_index] -= (u64)cur_opcode.perform_math_static.value; - break; - case RegisterArithmeticType_Multiplication: - this->registers[cur_opcode.perform_math_static.reg_index] *= (u64)cur_opcode.perform_math_static.value; - break; - case RegisterArithmeticType_LeftShift: - this->registers[cur_opcode.perform_math_static.reg_index] <<= (u64)cur_opcode.perform_math_static.value; - break; - case RegisterArithmeticType_RightShift: - this->registers[cur_opcode.perform_math_static.reg_index] >>= (u64)cur_opcode.perform_math_static.value; - break; - default: - /* Do not handle extensions here. */ - break; - } - /* Apply bit width. */ - switch (cur_opcode.perform_math_static.bit_width) { - case 1: - this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); - break; - case 2: - this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); - break; - case 4: - this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); - break; - case 8: - this->registers[cur_opcode.perform_math_static.reg_index] = static_cast(this->registers[cur_opcode.perform_math_static.reg_index]); - break; - } - } - break; - case CheatVmOpcodeType_BeginKeypressConditionalBlock: - /* Check for keypress. */ - if ((cur_opcode.begin_keypress_cond.key_mask & kDown) != cur_opcode.begin_keypress_cond.key_mask) { - /* Keys not pressed. Skip conditional block. */ - this->SkipConditionalBlock(); - } - break; - case CheatVmOpcodeType_PerformArithmeticRegister: - { - const u64 operand_1_value = this->registers[cur_opcode.perform_math_reg.src_reg_1_index]; - const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ? - GetVmInt(cur_opcode.perform_math_reg.value, cur_opcode.perform_math_reg.bit_width) : - this->registers[cur_opcode.perform_math_reg.src_reg_2_index]; - - u64 res_val = 0; - /* Do requested math. */ - switch (cur_opcode.perform_math_reg.math_type) { - case RegisterArithmeticType_Addition: - res_val = operand_1_value + operand_2_value; - break; - case RegisterArithmeticType_Subtraction: - res_val = operand_1_value - operand_2_value; - break; - case RegisterArithmeticType_Multiplication: - res_val = operand_1_value * operand_2_value; - break; - case RegisterArithmeticType_LeftShift: - res_val = operand_1_value << operand_2_value; - break; - case RegisterArithmeticType_RightShift: - res_val = operand_1_value >> operand_2_value; - break; - case RegisterArithmeticType_LogicalAnd: - res_val = operand_1_value & operand_2_value; - break; - case RegisterArithmeticType_LogicalOr: - res_val = operand_1_value | operand_2_value; - break; - case RegisterArithmeticType_LogicalNot: - res_val = ~operand_1_value; - break; - case RegisterArithmeticType_LogicalXor: - res_val = operand_1_value ^ operand_2_value; - break; - case RegisterArithmeticType_None: - res_val = operand_1_value; - break; - } - - - /* Apply bit width. */ - switch (cur_opcode.perform_math_reg.bit_width) { - case 1: - res_val = static_cast(res_val); - break; - case 2: - res_val = static_cast(res_val); - break; - case 4: - res_val = static_cast(res_val); - break; - case 8: - res_val = static_cast(res_val); - break; - } - - /* Save to register. */ - this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val; - } - break; - case CheatVmOpcodeType_StoreRegisterToAddress: - { - /* Calculate address. */ - u64 dst_value = this->registers[cur_opcode.str_register.str_reg_index]; - u64 dst_address = this->registers[cur_opcode.str_register.addr_reg_index]; - switch (cur_opcode.str_register.ofs_type) { - case StoreRegisterOffsetType_None: - /* Nothing more to do */ - break; - case StoreRegisterOffsetType_Reg: - dst_address += this->registers[cur_opcode.str_register.ofs_reg_index]; - break; - case StoreRegisterOffsetType_Imm: - dst_address += cur_opcode.str_register.rel_address; - break; - case StoreRegisterOffsetType_MemReg: - dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index]); - break; - case StoreRegisterOffsetType_MemImm: - dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, cur_opcode.str_register.rel_address); - break; - case StoreRegisterOffsetType_MemImmReg: - dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index] + cur_opcode.str_register.rel_address); - break; - } - - /* Write value to memory. Write only on valid bitwidth. */ - switch (cur_opcode.str_register.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.str_register.bit_width); - break; - } - - /* Increment register if relevant. */ - if (cur_opcode.str_register.increment_reg) { - this->registers[cur_opcode.str_register.addr_reg_index] += cur_opcode.str_register.bit_width; - } - } - break; - case CheatVmOpcodeType_BeginRegisterConditionalBlock: - { - /* Get value from register. */ - u64 src_value = 0; - switch (cur_opcode.begin_reg_cond.bit_width) { - case 1: - src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFul); - break; - case 2: - src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFul); - break; - case 4: - src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFul); - break; - case 8: - src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); - break; - } - - /* Read value from memory. */ - u64 cond_value = 0; - if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) { - cond_value = GetVmInt(cur_opcode.begin_reg_cond.value, cur_opcode.begin_reg_cond.bit_width); - } else if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_OtherRegister) { - switch (cur_opcode.begin_reg_cond.bit_width) { - case 1: - cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFul); - break; - case 2: - cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFul); - break; - case 4: - cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFul); - break; - case 8: - cond_value = static_cast(this->registers[cur_opcode.begin_reg_cond.other_reg_index] & 0xFFFFFFFFFFFFFFFFul); - break; - } - } else { - u64 cond_address = 0; - switch (cur_opcode.begin_reg_cond.comp_type) { - case CompareRegisterValueType_MemoryRelAddr: - cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, cur_opcode.begin_reg_cond.rel_address); - break; - case CompareRegisterValueType_MemoryOfsReg: - cond_address = GetCheatProcessAddress(metadata, cur_opcode.begin_reg_cond.mem_type, this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]); - break; - case CompareRegisterValueType_RegisterRelAddr: - cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + cur_opcode.begin_reg_cond.rel_address; - break; - case CompareRegisterValueType_RegisterOfsReg: - cond_address = this->registers[cur_opcode.begin_reg_cond.addr_reg_index] + this->registers[cur_opcode.begin_reg_cond.ofs_reg_index]; - break; - default: - break; - } - switch (cur_opcode.begin_reg_cond.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::ReadCheatProcessMemoryForVm(cond_address, &cond_value, cur_opcode.begin_reg_cond.bit_width); - break; - } - } - - /* Check against condition. */ - bool cond_met = false; - switch (cur_opcode.begin_reg_cond.cond_type) { - case ConditionalComparisonType_GT: - cond_met = src_value > cond_value; - break; - case ConditionalComparisonType_GE: - cond_met = src_value >= cond_value; - break; - case ConditionalComparisonType_LT: - cond_met = src_value < cond_value; - break; - case ConditionalComparisonType_LE: - cond_met = src_value <= cond_value; - break; - case ConditionalComparisonType_EQ: - cond_met = src_value == cond_value; - break; - case ConditionalComparisonType_NE: - cond_met = src_value != cond_value; - break; - } - - /* Skip conditional block if condition not met. */ - if (!cond_met) { - this->SkipConditionalBlock(); - } - } - break; - case CheatVmOpcodeType_SaveRestoreRegister: - /* Save or restore a register. */ - switch (cur_opcode.save_restore_reg.op_type) { - case SaveRestoreRegisterOpType_ClearRegs: - this->registers[cur_opcode.save_restore_reg.dst_index] = 0ul; - break; - case SaveRestoreRegisterOpType_ClearSaved: - this->saved_values[cur_opcode.save_restore_reg.dst_index] = 0ul; - break; - case SaveRestoreRegisterOpType_Save: - this->saved_values[cur_opcode.save_restore_reg.dst_index] = this->registers[cur_opcode.save_restore_reg.src_index]; - break; - case SaveRestoreRegisterOpType_Restore: - default: - this->registers[cur_opcode.save_restore_reg.dst_index] = this->saved_values[cur_opcode.save_restore_reg.src_index]; - break; - } - break; - case CheatVmOpcodeType_SaveRestoreRegisterMask: - /* Save or restore register mask. */ - u64 *src; - u64 *dst; - switch (cur_opcode.save_restore_regmask.op_type) { - case SaveRestoreRegisterOpType_ClearSaved: - case SaveRestoreRegisterOpType_Save: - src = this->registers; - dst = this->saved_values; - break; - case SaveRestoreRegisterOpType_ClearRegs: - case SaveRestoreRegisterOpType_Restore: - default: - src = this->registers; - dst = this->saved_values; - break; - } - for (size_t i = 0; i < NumRegisters; i++) { - if (cur_opcode.save_restore_regmask.should_operate[i]) { - switch (cur_opcode.save_restore_regmask.op_type) { - case SaveRestoreRegisterOpType_ClearSaved: - case SaveRestoreRegisterOpType_ClearRegs: - dst[i] = 0ul; - break; - case SaveRestoreRegisterOpType_Save: - case SaveRestoreRegisterOpType_Restore: - default: - dst[i] = src[i]; - break; - } - } - } - break; - case CheatVmOpcodeType_DebugLog: - { - /* Read value from memory. */ - u64 log_value = 0; - if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { - switch (cur_opcode.debug_log.bit_width) { - case 1: - log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); - break; - case 2: - log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); - break; - case 4: - log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); - break; - case 8: - log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); - break; - } - } else { - u64 val_address = 0; - switch (cur_opcode.debug_log.val_type) { - case DebugLogValueType_MemoryRelAddr: - val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, cur_opcode.debug_log.rel_address); - break; - case DebugLogValueType_MemoryOfsReg: - val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, this->registers[cur_opcode.debug_log.ofs_reg_index]); - break; - case DebugLogValueType_RegisterRelAddr: - val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + cur_opcode.debug_log.rel_address; - break; - case DebugLogValueType_RegisterOfsReg: - val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + this->registers[cur_opcode.debug_log.ofs_reg_index]; - break; - default: - break; - } - switch (cur_opcode.debug_log.bit_width) { - case 1: - case 2: - case 4: - case 8: - DmntCheatManager::ReadCheatProcessMemoryForVm(val_address, &log_value, cur_opcode.debug_log.bit_width); - break; - } - } - - /* Log value. */ - this->DebugLog(cur_opcode.debug_log.log_id, log_value); - } - break; - default: - /* By default, we do a no-op. */ - break; - } - } -} \ No newline at end of file diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp b/stratosphere/dmnt/source/dmnt_cheat_vm.hpp deleted file mode 100644 index 2ad1fcbbe..000000000 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include -#include -#include - -#include "dmnt_cheat_types.hpp" - -enum CheatVmOpcodeType : u32 { - CheatVmOpcodeType_StoreStatic = 0, - CheatVmOpcodeType_BeginConditionalBlock = 1, - CheatVmOpcodeType_EndConditionalBlock = 2, - CheatVmOpcodeType_ControlLoop = 3, - CheatVmOpcodeType_LoadRegisterStatic = 4, - CheatVmOpcodeType_LoadRegisterMemory = 5, - CheatVmOpcodeType_StoreStaticToAddress = 6, - CheatVmOpcodeType_PerformArithmeticStatic = 7, - CheatVmOpcodeType_BeginKeypressConditionalBlock = 8, - - /* These are not implemented by Gateway's VM. */ - CheatVmOpcodeType_PerformArithmeticRegister = 9, - CheatVmOpcodeType_StoreRegisterToAddress = 10, - CheatVmOpcodeType_Reserved11 = 11, - - /* This is a meta entry, and not a real opcode. */ - /* This is to facilitate multi-nybble instruction decoding. */ - CheatVmOpcodeType_ExtendedWidth = 12, - - /* Extended width opcodes. */ - CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, - CheatVmOpcodeType_SaveRestoreRegister = 0xC1, - CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, - - /* This is a meta entry, and not a real opcode. */ - /* This is to facilitate multi-nybble instruction decoding. */ - CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, - - /* Double-extended width opcodes. */ - CheatVmOpcodeType_DebugLog = 0xFFF, -}; - -enum MemoryAccessType : u32 { - MemoryAccessType_MainNso = 0, - MemoryAccessType_Heap = 1, -}; - -enum ConditionalComparisonType : u32 { - ConditionalComparisonType_GT = 1, - ConditionalComparisonType_GE = 2, - ConditionalComparisonType_LT = 3, - ConditionalComparisonType_LE = 4, - ConditionalComparisonType_EQ = 5, - ConditionalComparisonType_NE = 6, -}; - -enum RegisterArithmeticType : u32 { - RegisterArithmeticType_Addition = 0, - RegisterArithmeticType_Subtraction = 1, - RegisterArithmeticType_Multiplication = 2, - RegisterArithmeticType_LeftShift = 3, - RegisterArithmeticType_RightShift = 4, - - /* These are not supported by Gateway's VM. */ - RegisterArithmeticType_LogicalAnd = 5, - RegisterArithmeticType_LogicalOr = 6, - RegisterArithmeticType_LogicalNot = 7, - RegisterArithmeticType_LogicalXor = 8, - - RegisterArithmeticType_None = 9, -}; - -enum StoreRegisterOffsetType : u32 { - StoreRegisterOffsetType_None = 0, - StoreRegisterOffsetType_Reg = 1, - StoreRegisterOffsetType_Imm = 2, - StoreRegisterOffsetType_MemReg = 3, - StoreRegisterOffsetType_MemImm = 4, - StoreRegisterOffsetType_MemImmReg = 5, -}; - -enum CompareRegisterValueType : u32 { - CompareRegisterValueType_MemoryRelAddr = 0, - CompareRegisterValueType_MemoryOfsReg = 1, - CompareRegisterValueType_RegisterRelAddr = 2, - CompareRegisterValueType_RegisterOfsReg = 3, - CompareRegisterValueType_StaticValue = 4, - CompareRegisterValueType_OtherRegister = 5, -}; - -enum SaveRestoreRegisterOpType : u32 { - SaveRestoreRegisterOpType_Restore = 0, - SaveRestoreRegisterOpType_Save = 1, - SaveRestoreRegisterOpType_ClearSaved = 2, - SaveRestoreRegisterOpType_ClearRegs = 3, -}; - -enum DebugLogValueType : u32 { - DebugLogValueType_MemoryRelAddr = 0, - DebugLogValueType_MemoryOfsReg = 1, - DebugLogValueType_RegisterRelAddr = 2, - DebugLogValueType_RegisterOfsReg = 3, - DebugLogValueType_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 {}; - -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; - bool should_operate[0x10]; -}; - -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 CheatVmOpcode { - CheatVmOpcodeType opcode; - bool begin_conditional_block; - union { - StoreStaticOpcode store_static; - BeginConditionalOpcode begin_cond; - EndConditionalOpcode end_cond; - ControlLoopOpcode ctrl_loop; - LoadRegisterStaticOpcode ldr_static; - LoadRegisterMemoryOpcode ldr_memory; - StoreStaticToAddressOpcode str_static; - PerformArithmeticStaticOpcode perform_math_static; - BeginKeypressConditionalOpcode begin_keypress_cond; - PerformArithmeticRegisterOpcode perform_math_reg; - StoreRegisterToAddressOpcode str_register; - BeginRegisterConditionalOpcode begin_reg_cond; - SaveRestoreRegisterOpcode save_restore_reg; - SaveRestoreRegisterMaskOpcode save_restore_regmask; - DebugLogOpcode debug_log; - }; -}; - -class DmntCheatVm { - public: - constexpr static size_t MaximumProgramOpcodeCount = 0x400; - constexpr static size_t NumRegisters = 0x10; - private: - size_t num_opcodes = 0; - size_t instruction_ptr = 0; - size_t condition_depth = 0; - bool decode_success = false; - u32 program[MaximumProgramOpcodeCount] = {0}; - u64 registers[NumRegisters] = {0}; - u64 saved_values[NumRegisters] = {0}; - size_t loop_tops[NumRegisters] = {0}; - private: - bool DecodeNextOpcode(CheatVmOpcode *out); - void SkipConditionalBlock(); - void ResetState(); - - /* For implementing the DebugLog opcode. */ - void DebugLog(u32 log_id, u64 value); - - /* For debugging. These will be IFDEF'd out normally. */ - void OpenDebugLogFile(); - void CloseDebugLogFile(); - void LogToDebugFile(const char *format, ...); - 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); - public: - DmntCheatVm() { } - - size_t GetProgramSize() { - return this->num_opcodes; - } - - bool LoadProgram(const CheatEntry *cheats, size_t num_cheats); - void Execute(const CheatProcessMetadata *metadata); -#ifdef DMNT_CHEAT_VM_DEBUG_LOG - private: - FILE *debug_log_file = NULL; -#endif -}; diff --git a/stratosphere/dmnt/source/dmnt_config.cpp b/stratosphere/dmnt/source/dmnt_config.cpp deleted file mode 100644 index e61af7461..000000000 --- a/stratosphere/dmnt/source/dmnt_config.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include "dmnt_hid.hpp" -#include "dmnt_config.hpp" -#include "ini.h" - -/* Support variables. */ -static OverrideKey g_default_cheat_enable_key = { - .key_combination = KEY_L, - .override_by_default = true -}; - -/* Static buffer for loader.ini contents at runtime. */ -static char g_config_ini_data[0x800]; - -static OverrideKey ParseOverrideKey(const char *value) { - OverrideKey cfg; - - /* Parse on by default. */ - if (value[0] == '!') { - cfg.override_by_default = true; - value++; - } else { - cfg.override_by_default = false; - } - - /* Parse key combination. */ - if (strcasecmp(value, "A") == 0) { - cfg.key_combination = KEY_A; - } else if (strcasecmp(value, "B") == 0) { - cfg.key_combination = KEY_B; - } else if (strcasecmp(value, "X") == 0) { - cfg.key_combination = KEY_X; - } else if (strcasecmp(value, "Y") == 0) { - cfg.key_combination = KEY_Y; - } else if (strcasecmp(value, "LS") == 0) { - cfg.key_combination = KEY_LSTICK; - } else if (strcasecmp(value, "RS") == 0) { - cfg.key_combination = KEY_RSTICK; - } else if (strcasecmp(value, "L") == 0) { - cfg.key_combination = KEY_L; - } else if (strcasecmp(value, "R") == 0) { - cfg.key_combination = KEY_R; - } else if (strcasecmp(value, "ZL") == 0) { - cfg.key_combination = KEY_ZL; - } else if (strcasecmp(value, "ZR") == 0) { - cfg.key_combination = KEY_ZR; - } else if (strcasecmp(value, "PLUS") == 0) { - cfg.key_combination = KEY_PLUS; - } else if (strcasecmp(value, "MINUS") == 0) { - cfg.key_combination = KEY_MINUS; - } else if (strcasecmp(value, "DLEFT") == 0) { - cfg.key_combination = KEY_DLEFT; - } else if (strcasecmp(value, "DUP") == 0) { - cfg.key_combination = KEY_DUP; - } else if (strcasecmp(value, "DRIGHT") == 0) { - cfg.key_combination = KEY_DRIGHT; - } else if (strcasecmp(value, "DDOWN") == 0) { - cfg.key_combination = KEY_DDOWN; - } else if (strcasecmp(value, "SL") == 0) { - cfg.key_combination = KEY_SL; - } else if (strcasecmp(value, "SR") == 0) { - cfg.key_combination = KEY_SR; - } else { - cfg.key_combination = 0; - } - - return cfg; -} - -static int DmntIniHandler(void *user, const char *section, const char *name, const char *value) { - /* Taken and modified, with love, from Rajkosto's implementation. */ - if (strcasecmp(section, "default_config") == 0) { - if (strcasecmp(name, "cheat_enable_key") == 0) { - g_default_cheat_enable_key = ParseOverrideKey(value); - } - } else { - return 0; - } - return 1; -} - -static int DmntTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) { - /* We'll output an override key when relevant. */ - OverrideKey *user_cfg = reinterpret_cast(user); - - if (strcasecmp(section, "override_config") == 0) { - if (strcasecmp(name, "cheat_enable_key") == 0) { - *user_cfg = ParseOverrideKey(value); - } - } else { - return 0; - } - return 1; -} - -void DmntConfigManager::RefreshConfiguration() { - FILE *config = fopen("sdmc:/atmosphere/loader.ini", "r"); - if (config == NULL) { - return; - } - - memset(g_config_ini_data, 0, sizeof(g_config_ini_data)); - fread(g_config_ini_data, 1, sizeof(g_config_ini_data) - 1, config); - fclose(config); - - ini_parse_string(g_config_ini_data, DmntIniHandler, NULL); -} - -OverrideKey DmntConfigManager::GetTitleCheatEnableKey(u64 tid) { - OverrideKey cfg = g_default_cheat_enable_key; - char path[FS_MAX_PATH+1] = {0}; - snprintf(path, FS_MAX_PATH, "sdmc:/atmosphere/titles/%016lx/config.ini", tid); - - - FILE *config = fopen(path, "r"); - if (config != NULL) { - ON_SCOPE_EXIT { fclose(config); }; - - /* Parse current title ini. */ - ini_parse_file(config, DmntTitleSpecificIniHandler, &cfg); - } - - return cfg; -} - -static bool HasOverrideKey(OverrideKey *cfg) { - u64 kDown = 0; - bool keys_triggered = (R_SUCCEEDED(HidManagement::GetKeysDown(&kDown)) && ((kDown & cfg->key_combination) != 0)); - return (cfg->override_by_default ^ keys_triggered); -} - -bool DmntConfigManager::HasCheatEnableButton(u64 tid) { - /* Unconditionally refresh loader.ini contents. */ - RefreshConfiguration(); - - OverrideKey title_cfg = GetTitleCheatEnableKey(tid); - return HasOverrideKey(&title_cfg); -} diff --git a/stratosphere/dmnt/source/dmnt_main.cpp b/stratosphere/dmnt/source/dmnt_main.cpp index 156917a1a..ad1cfff66 100644 --- a/stratosphere/dmnt/source/dmnt_main.cpp +++ b/stratosphere/dmnt/source/dmnt_main.cpp @@ -24,9 +24,7 @@ #include #include "dmnt_service.hpp" -#include "dmnt_cheat_service.hpp" -#include "dmnt_cheat_manager.hpp" -#include "dmnt_config.hpp" +#include "cheat/dmnt_cheat_service.hpp" extern "C" { extern u32 __start__; @@ -96,14 +94,6 @@ void __appExit(void) { int main(int argc, char **argv) { - consoleDebugInit(debugDevice_SVC); - - /* Initialize configuration manager. */ - DmntConfigManager::RefreshConfiguration(); - - /* Start cheat manager. */ - DmntCheatManager::InitializeCheatManager(); - /* Nintendo uses four threads. Add a fifth for our cheat service. */ static auto s_server_manager = WaitableManager(5); @@ -111,9 +101,7 @@ int main(int argc, char **argv) /* TODO: Implement rest of dmnt:- in ams.tma development branch. */ /* server_manager->AddWaitable(new ServiceServer("dmnt:-", 4)); */ - - - s_server_manager.AddWaitable(new ServiceServer("dmnt:cht", 1)); + s_server_manager.AddWaitable(new ServiceServer("dmnt:cht", 1)); /* Loop forever, servicing our services. */ s_server_manager.Process(); diff --git a/stratosphere/dmnt/source/dmnt_service.hpp b/stratosphere/dmnt/source/dmnt_service.hpp index 9974f23fd..eff755ec2 100644 --- a/stratosphere/dmnt/source/dmnt_service.hpp +++ b/stratosphere/dmnt/source/dmnt_service.hpp @@ -18,133 +18,137 @@ #include #include -class DebugMonitorService final : public IServiceObject { - private: - enum class CommandId { - BreakDebugProcess = 0, - TerminateDebugProcess = 1, - CloseHandle = 2, - LoadImage = 3, - GetProcessId = 4, - GetProcessHandle = 5, - WaitSynchronization = 6, - GetDebugEvent = 7, - GetProcessModuleInfo = 8, - GetProcessList = 9, - GetThreadList = 10, - GetDebugThreadContext = 11, - ContinueDebugEvent = 12, - ReadDebugProcessMemory = 13, - WriteDebugProcessMemory = 14, - SetDebugThreadContext = 15, - GetDebugThreadParam = 16, - InitializeThreadInfo = 17, - SetHardwareBreakPoint = 18, - QueryDebugProcessMemory = 19, - GetProcessMemoryDetails = 20, - AttachByProgramId = 21, - AttachOnLaunch = 22, - GetDebugMonitorProcessId = 23, - GetJitDebugProcessList = 25, - CreateCoreDump = 26, - GetAllDebugThreadInfo = 27, - TargetIO_FileOpen = 29, - TargetIO_FileClose = 30, - TargetIO_FileRead = 31, - TargetIO_FileWrite = 32, - TargetIO_FileSetAttributes = 33, - TargetIO_FileGetInformation = 34, - TargetIO_FileSetTime = 35, - TargetIO_FileSetSize = 36, - TargetIO_FileDelete = 37, - TargetIO_FileMove = 38, - TargetIO_DirectoryCreate = 39, - TargetIO_DirectoryDelete = 40, - TargetIO_DirectoryRename = 41, - TargetIO_DirectoryGetCount = 42, - TargetIO_DirectoryOpen = 43, - TargetIO_DirectoryGetNext = 44, - TargetIO_DirectoryClose = 45, - TargetIO_GetFreeSpace = 46, - TargetIO_GetVolumeInformation = 47, - InitiateCoreDump = 48, - ContinueCoreDump = 49, - AddTTYToCoreDump = 50, - AddImageToCoreDump = 51, - CloseCoreDump = 52, - CancelAttach = 53, - }; - private: - Result BreakDebugProcess(Handle debug_hnd); - Result TerminateDebugProcess(Handle debug_hnd); - Result CloseHandle(Handle debug_hnd); - Result GetProcessId(Out out_pid, Handle hnd); - Result GetProcessHandle(Out out_hnd, u64 pid); - Result WaitSynchronization(Handle hnd, u64 ns); +namespace sts::dmnt { - Result TargetIO_FileOpen(OutBuffer out_hnd, InBuffer path, int open_mode, u32 create_mode); - Result TargetIO_FileClose(InBuffer hnd); - Result TargetIO_FileRead(InBuffer hnd, OutBuffer out_data, Out out_read, u64 offset); - Result TargetIO_FileWrite(InBuffer hnd, InBuffer data, Out out_written, u64 offset); - Result TargetIO_FileSetAttributes(InBuffer path, InBuffer attributes); - Result TargetIO_FileGetInformation(InBuffer path, OutBuffer out_info, Out is_directory); - Result TargetIO_FileSetTime(InBuffer path, u64 create, u64 access, u64 modify); - Result TargetIO_FileSetSize(InBuffer path, u64 size); - Result TargetIO_FileDelete(InBuffer path); - Result TargetIO_FileMove(InBuffer path0, InBuffer path1); - public: - DEFINE_SERVICE_DISPATCH_TABLE { - MAKE_SERVICE_COMMAND_META(DebugMonitorService, BreakDebugProcess), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TerminateDebugProcess), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseHandle), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, LoadImage), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessId), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessHandle), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, WaitSynchronization), - //MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugEvent), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessModuleInfo), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessList), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetThreadList), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadContext), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueDebugEvent), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ReadDebugProcessMemory), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, WriteDebugProcessMemory), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetDebugThreadContext), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadParam), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitializeThreadInfo), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetHardwareBreakPoint), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, QueryDebugProcessMemory), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessMemoryDetails), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachByProgramId), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachOnLaunch), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugMonitorProcessId), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetJitDebugProcessList), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CreateCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetAllDebugThreadInfo), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileOpen), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileClose), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileRead), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileWrite), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetAttributes), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileGetInformation), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetTime), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetSize), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileDelete), - MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileMove), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryCreate), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryDelete), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryRename), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetCount), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryOpen), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetNext), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryClose), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetFreeSpace), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetVolumeInformation), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitiateCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddTTYToCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddImageToCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseCoreDump), - // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CancelAttach), - }; -}; + class DebugMonitorService final : public IServiceObject { + private: + enum class CommandId { + BreakDebugProcess = 0, + TerminateDebugProcess = 1, + CloseHandle = 2, + LoadImage = 3, + GetProcessId = 4, + GetProcessHandle = 5, + WaitSynchronization = 6, + GetDebugEvent = 7, + GetProcessModuleInfo = 8, + GetProcessList = 9, + GetThreadList = 10, + GetDebugThreadContext = 11, + ContinueDebugEvent = 12, + ReadDebugProcessMemory = 13, + WriteDebugProcessMemory = 14, + SetDebugThreadContext = 15, + GetDebugThreadParam = 16, + InitializeThreadInfo = 17, + SetHardwareBreakPoint = 18, + QueryDebugProcessMemory = 19, + GetProcessMemoryDetails = 20, + AttachByProgramId = 21, + AttachOnLaunch = 22, + GetDebugMonitorProcessId = 23, + GetJitDebugProcessList = 25, + CreateCoreDump = 26, + GetAllDebugThreadInfo = 27, + TargetIO_FileOpen = 29, + TargetIO_FileClose = 30, + TargetIO_FileRead = 31, + TargetIO_FileWrite = 32, + TargetIO_FileSetAttributes = 33, + TargetIO_FileGetInformation = 34, + TargetIO_FileSetTime = 35, + TargetIO_FileSetSize = 36, + TargetIO_FileDelete = 37, + TargetIO_FileMove = 38, + TargetIO_DirectoryCreate = 39, + TargetIO_DirectoryDelete = 40, + TargetIO_DirectoryRename = 41, + TargetIO_DirectoryGetCount = 42, + TargetIO_DirectoryOpen = 43, + TargetIO_DirectoryGetNext = 44, + TargetIO_DirectoryClose = 45, + TargetIO_GetFreeSpace = 46, + TargetIO_GetVolumeInformation = 47, + InitiateCoreDump = 48, + ContinueCoreDump = 49, + AddTTYToCoreDump = 50, + AddImageToCoreDump = 51, + CloseCoreDump = 52, + CancelAttach = 53, + }; + private: + Result BreakDebugProcess(Handle debug_hnd); + Result TerminateDebugProcess(Handle debug_hnd); + Result CloseHandle(Handle debug_hnd); + Result GetProcessId(Out out_pid, Handle hnd); + Result GetProcessHandle(Out out_hnd, u64 pid); + Result WaitSynchronization(Handle hnd, u64 ns); + + Result TargetIO_FileOpen(OutBuffer out_hnd, InBuffer path, int open_mode, u32 create_mode); + Result TargetIO_FileClose(InBuffer hnd); + Result TargetIO_FileRead(InBuffer hnd, OutBuffer out_data, Out out_read, u64 offset); + Result TargetIO_FileWrite(InBuffer hnd, InBuffer data, Out out_written, u64 offset); + Result TargetIO_FileSetAttributes(InBuffer path, InBuffer attributes); + Result TargetIO_FileGetInformation(InBuffer path, OutBuffer out_info, Out is_directory); + Result TargetIO_FileSetTime(InBuffer path, u64 create, u64 access, u64 modify); + Result TargetIO_FileSetSize(InBuffer path, u64 size); + Result TargetIO_FileDelete(InBuffer path); + Result TargetIO_FileMove(InBuffer path0, InBuffer path1); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(DebugMonitorService, BreakDebugProcess), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TerminateDebugProcess), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseHandle), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, LoadImage), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessId), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessHandle), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, WaitSynchronization), + //MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugEvent), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessModuleInfo), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessList), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetThreadList), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadContext), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueDebugEvent), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ReadDebugProcessMemory), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, WriteDebugProcessMemory), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetDebugThreadContext), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugThreadParam), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitializeThreadInfo), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, SetHardwareBreakPoint), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, QueryDebugProcessMemory), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetProcessMemoryDetails), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachByProgramId), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AttachOnLaunch), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetDebugMonitorProcessId), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetJitDebugProcessList), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CreateCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, GetAllDebugThreadInfo), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileOpen), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileClose), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileRead), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileWrite), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetAttributes), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileGetInformation), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetTime), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileSetSize), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileDelete), + MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_FileMove), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryCreate), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryDelete), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryRename), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetCount), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryOpen), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryGetNext), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_DirectoryClose), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetFreeSpace), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, TargetIO_GetVolumeInformation), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, InitiateCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, ContinueCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddTTYToCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, AddImageToCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CloseCoreDump), + // MAKE_SERVICE_COMMAND_META(DebugMonitorService, CancelAttach), + }; + }; + +} diff --git a/stratosphere/dmnt/source/dmnt_service_debug.cpp b/stratosphere/dmnt/source/dmnt_service_debug.cpp index 91ba8ca6c..72b6dbbbc 100644 --- a/stratosphere/dmnt/source/dmnt_service_debug.cpp +++ b/stratosphere/dmnt/source/dmnt_service_debug.cpp @@ -14,41 +14,44 @@ * along with this program. If not, see . */ -#include #include "dmnt_service.hpp" -Result DebugMonitorService::BreakDebugProcess(Handle debug_hnd) { - /* Nintendo discards the output of this command, but we will return it. */ - return svcBreakDebugProcess(debug_hnd); -} +namespace sts::dmnt { -Result DebugMonitorService::TerminateDebugProcess(Handle debug_hnd) { - /* Nintendo discards the output of this command, but we will return it. */ - return svcTerminateDebugProcess(debug_hnd); -} + Result DebugMonitorService::BreakDebugProcess(Handle debug_hnd) { + /* Nintendo discards the output of this command, but we will return it. */ + return svcBreakDebugProcess(debug_hnd); + } -Result DebugMonitorService::CloseHandle(Handle debug_hnd) { - /* Nintendo discards the output of this command, but we will return it. */ - /* This command is, entertainingly, also pretty unsafe in general... */ - return svcCloseHandle(debug_hnd); -} + Result DebugMonitorService::TerminateDebugProcess(Handle debug_hnd) { + /* Nintendo discards the output of this command, but we will return it. */ + return svcTerminateDebugProcess(debug_hnd); + } -Result DebugMonitorService::GetProcessId(Out out_pid, Handle hnd) { - /* Nintendo discards the output of this command, but we will return it. */ - return svcGetProcessId(out_pid.GetPointer(), hnd); -} + Result DebugMonitorService::CloseHandle(Handle debug_hnd) { + /* Nintendo discards the output of this command, but we will return it. */ + /* This command is, entertainingly, also pretty unsafe in general... */ + return svcCloseHandle(debug_hnd); + } -Result DebugMonitorService::GetProcessHandle(Out out_hnd, u64 pid) { - R_TRY_CATCH(svcDebugActiveProcess(out_hnd.GetPointer(), pid)) { - R_CATCH(ResultKernelAlreadyExists) { - return ResultDebugAlreadyAttached; - } - } R_END_TRY_CATCH; + Result DebugMonitorService::GetProcessId(Out out_pid, Handle hnd) { + /* Nintendo discards the output of this command, but we will return it. */ + return svcGetProcessId(out_pid.GetPointer(), hnd); + } - return ResultSuccess; -} + Result DebugMonitorService::GetProcessHandle(Out out_hnd, u64 pid) { + R_TRY_CATCH(svcDebugActiveProcess(out_hnd.GetPointer(), pid)) { + R_CATCH(ResultKernelAlreadyExists) { + return ResultDebugAlreadyAttached; + } + } R_END_TRY_CATCH; + + return ResultSuccess; + } + + Result DebugMonitorService::WaitSynchronization(Handle hnd, u64 ns) { + /* Nintendo discards the output of this command, but we will return it. */ + return svcWaitSynchronizationSingle(hnd, ns); + } -Result DebugMonitorService::WaitSynchronization(Handle hnd, u64 ns) { - /* Nintendo discards the output of this command, but we will return it. */ - return svcWaitSynchronizationSingle(hnd, ns); } diff --git a/stratosphere/dmnt/source/dmnt_service_target_io.cpp b/stratosphere/dmnt/source/dmnt_service_target_io.cpp index ad7d915fe..7ecca79b5 100644 --- a/stratosphere/dmnt/source/dmnt_service_target_io.cpp +++ b/stratosphere/dmnt/source/dmnt_service_target_io.cpp @@ -15,259 +15,266 @@ */ #include -#include #include "dmnt_service.hpp" -enum TIOCreateOption : u32 { - TIOCreateOption_CreateNew = 1, - TIOCreateOption_CreateAlways = 2, - TIOCreateOption_OpenExisting = 3, - TIOCreateOption_OpenAlways = 4, - TIOCreateOption_ResetSize = 5, -}; +namespace sts::dmnt { -/* Nintendo uses actual pointers as file handles. We'll add a layer of indirection... */ -static bool g_sd_initialized = false; -static HosMutex g_sd_lock; -static FsFileSystem g_sd_fs; + namespace { -static HosMutex g_file_handle_lock; -static u64 g_cur_fd = 0; -static std::unordered_map g_file_handles; + enum TIOCreateOption : u32 { + TIOCreateOption_CreateNew = 1, + TIOCreateOption_CreateAlways = 2, + TIOCreateOption_OpenExisting = 3, + TIOCreateOption_OpenAlways = 4, + TIOCreateOption_ResetSize = 5, + }; -static Result EnsureSdInitialized() { - std::scoped_lock lk(g_sd_lock); - if (g_sd_initialized) { - return ResultSuccess; - } + /* Nintendo uses actual pointers as file handles. We'll add a layer of indirection... */ + bool g_sd_initialized = false; + HosMutex g_sd_lock; + FsFileSystem g_sd_fs; - R_TRY(fsMountSdcard(&g_sd_fs)); - g_sd_initialized = true; - return ResultSuccess; -} + HosMutex g_file_handle_lock; + u64 g_cur_fd; + std::unordered_map g_file_handles; -static u64 GetNewFileHandle(FsFile f) { - std::scoped_lock lk(g_file_handle_lock); + Result EnsureSdInitialized() { + std::scoped_lock lk(g_sd_lock); + if (g_sd_initialized) { + return ResultSuccess; + } - u64 fd = g_cur_fd++; - g_file_handles[fd] = f; - return fd; -} - -static Result GetFileByHandle(FsFile *out, u64 handle) { - std::scoped_lock lk(g_file_handle_lock); - - if (g_file_handles.find(handle) != g_file_handles.end()) { - *out = g_file_handles[handle]; - return ResultSuccess; - } - - return ResultFsInvalidArgument; -} - -static Result CloseFileByHandle(u64 handle) { - std::scoped_lock lk(g_file_handle_lock); - - if (g_file_handles.find(handle) != g_file_handles.end()) { - fsFileClose(&g_file_handles[handle]); - g_file_handles.erase(handle); - return ResultSuccess; - } - - return ResultFsInvalidArgument; -} - -static void FixPath(char *dst, size_t dst_size, InBuffer &path) { - dst[dst_size - 1] = 0; - strncpy(dst, "/", dst_size - 1); - - const char *src = path.buffer; - size_t src_idx = 0; - size_t dst_idx = 1; - while (src_idx < path.num_elements && (src[src_idx] == '/' || src[src_idx] == '\\')) { - src_idx++; - } - - while (src_idx < path.num_elements && dst_idx < dst_size - 1 && src[src_idx] != 0) { - if (src[src_idx] == '\\') { - dst[dst_idx] = '/'; - } else { - dst[dst_idx] = src[src_idx]; + R_TRY(fsMountSdcard(&g_sd_fs)); + g_sd_initialized = true; + return ResultSuccess; + } + + u64 GetNewFileHandle(FsFile f) { + std::scoped_lock lk(g_file_handle_lock); + + u64 fd = g_cur_fd++; + g_file_handles[fd] = f; + return fd; + } + + Result GetFileByHandle(FsFile *out, u64 handle) { + std::scoped_lock lk(g_file_handle_lock); + + if (g_file_handles.find(handle) != g_file_handles.end()) { + *out = g_file_handles[handle]; + return ResultSuccess; + } + + return ResultFsInvalidArgument; + } + + Result CloseFileByHandle(u64 handle) { + std::scoped_lock lk(g_file_handle_lock); + + if (g_file_handles.find(handle) != g_file_handles.end()) { + fsFileClose(&g_file_handles[handle]); + g_file_handles.erase(handle); + return ResultSuccess; + } + + return ResultFsInvalidArgument; + } + + void FixPath(char *dst, size_t dst_size, InBuffer &path) { + dst[dst_size - 1] = 0; + strncpy(dst, "/", dst_size - 1); + + const char *src = path.buffer; + size_t src_idx = 0; + size_t dst_idx = 1; + while (src_idx < path.num_elements && (src[src_idx] == '/' || src[src_idx] == '\\')) { + src_idx++; + } + + while (src_idx < path.num_elements && dst_idx < dst_size - 1 && src[src_idx] != 0) { + if (src[src_idx] == '\\') { + dst[dst_idx] = '/'; + } else { + dst[dst_idx] = src[src_idx]; + } + + src_idx++; + dst_idx++; + } + + if (dst_idx < dst_size) { + dst[dst_idx] = 0; + } } - src_idx++; - dst_idx++; } - if (dst_idx < dst_size) { - dst[dst_idx] = 0; - } -} + Result DebugMonitorService::TargetIO_FileOpen(OutBuffer out_hnd, InBuffer path, int open_mode, u32 create_mode) { + if (out_hnd.num_elements != 1) { + /* Serialization error. */ + return ResultKernelConnectionClosed; + } -Result DebugMonitorService::TargetIO_FileOpen(OutBuffer out_hnd, InBuffer path, int open_mode, u32 create_mode) { - if (out_hnd.num_elements != 1) { - /* Serialization error. */ - return ResultKernelConnectionClosed; + R_TRY(EnsureSdInitialized()); + + char fs_path[FS_MAX_PATH]; + FixPath(fs_path, sizeof(fs_path), path); + + /* Create file as required by mode. */ + if (create_mode == TIOCreateOption_CreateAlways) { + fsFsDeleteFile(&g_sd_fs, fs_path); + R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0)); + } else if (create_mode == TIOCreateOption_CreateNew) { + R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0)); + } else if (create_mode == TIOCreateOption_OpenAlways) { + fsFsCreateFile(&g_sd_fs, fs_path, 0, 0); + } + + /* Open the file, guard to prevent failure to close. */ + FsFile f; + R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f)); + auto file_guard = SCOPE_GUARD { + fsFileClose(&f); + }; + + /* Set size if needed. */ + if (create_mode == TIOCreateOption_ResetSize) { + R_TRY(fsFileSetSize(&f, 0)); + } + + /* Cancel guard, output file handle. */ + file_guard.Cancel(); + out_hnd[0] = GetNewFileHandle(f); + + return ResultSuccess; } - R_TRY(EnsureSdInitialized()); + Result DebugMonitorService::TargetIO_FileClose(InBuffer hnd) { + if (hnd.num_elements != 1) { + /* Serialization error. */ + return ResultKernelConnectionClosed; + } - char fs_path[FS_MAX_PATH]; - FixPath(fs_path, sizeof(fs_path), path); - - /* Create file as required by mode. */ - if (create_mode == TIOCreateOption_CreateAlways) { - fsFsDeleteFile(&g_sd_fs, fs_path); - R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0)); - } else if (create_mode == TIOCreateOption_CreateNew) { - R_TRY(fsFsCreateFile(&g_sd_fs, fs_path, 0, 0)); - } else if (create_mode == TIOCreateOption_OpenAlways) { - fsFsCreateFile(&g_sd_fs, fs_path, 0, 0); + return CloseFileByHandle(hnd[0]); } - /* Open the file, guard to prevent failure to close. */ - FsFile f; - R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, open_mode, &f)); - auto file_guard = SCOPE_GUARD { - fsFileClose(&f); - }; + Result DebugMonitorService::TargetIO_FileRead(InBuffer hnd, OutBuffer out_data, Out out_read, u64 offset) { + if (hnd.num_elements != 1) { + /* Serialization error. */ + return ResultKernelConnectionClosed; + } - /* Set size if needed. */ - if (create_mode == TIOCreateOption_ResetSize) { - R_TRY(fsFileSetSize(&f, 0)); + FsFile f; + size_t read = 0; + + R_TRY(GetFileByHandle(&f, hnd[0])); + R_TRY(fsFileRead(&f, offset, out_data.buffer, out_data.num_elements, FS_READOPTION_NONE, &read)); + + out_read.SetValue(static_cast(read)); + return ResultSuccess; } - /* Cancel guard, output file handle. */ - file_guard.Cancel(); - out_hnd[0] = GetNewFileHandle(f); + Result DebugMonitorService::TargetIO_FileWrite(InBuffer hnd, InBuffer data, Out out_written, u64 offset) { + if (hnd.num_elements != 1) { + /* Serialization error. */ + return ResultKernelConnectionClosed; + } - return ResultSuccess; -} + FsFile f; -Result DebugMonitorService::TargetIO_FileClose(InBuffer hnd) { - if (hnd.num_elements != 1) { - /* Serialization error. */ - return ResultKernelConnectionClosed; + R_TRY(GetFileByHandle(&f, hnd[0])); + R_TRY(fsFileWrite(&f, offset, data.buffer, data.num_elements, FS_WRITEOPTION_NONE)); + + out_written.SetValue(data.num_elements); + return ResultSuccess; } - return CloseFileByHandle(hnd[0]); -} - -Result DebugMonitorService::TargetIO_FileRead(InBuffer hnd, OutBuffer out_data, Out out_read, u64 offset) { - if (hnd.num_elements != 1) { - /* Serialization error. */ - return ResultKernelConnectionClosed; + Result DebugMonitorService::TargetIO_FileSetAttributes(InBuffer path, InBuffer attributes) { + /* I don't really know why this command exists, Horizon doesn't allow you to set any attributes. */ + /* N just returns ResultSuccess unconditionally here. */ + return ResultSuccess; } - FsFile f; - size_t read = 0; + Result DebugMonitorService::TargetIO_FileGetInformation(InBuffer path, OutBuffer out_info, Out is_directory) { + if (out_info.num_elements != 4) { + /* Serialization error. */ + return ResultKernelConnectionClosed; + } - R_TRY(GetFileByHandle(&f, hnd[0])); - R_TRY(fsFileRead(&f, offset, out_data.buffer, out_data.num_elements, FS_READOPTION_NONE, &read)); + R_TRY(EnsureSdInitialized()); - out_read.SetValue(static_cast(read)); - return ResultSuccess; -} + char fs_path[FS_MAX_PATH]; + FixPath(fs_path, sizeof(fs_path), path); -Result DebugMonitorService::TargetIO_FileWrite(InBuffer hnd, InBuffer data, Out out_written, u64 offset) { - if (hnd.num_elements != 1) { - /* Serialization error. */ - return ResultKernelConnectionClosed; + for (size_t i = 0; i < out_info.num_elements; i++) { + out_info[i] = 0; + } + is_directory.SetValue(0); + + FsFile f; + if (R_SUCCEEDED(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_READ, &f))) { + ON_SCOPE_EXIT { fsFileClose(&f); }; + + /* N doesn't check this return code. */ + fsFileGetSize(&f, &out_info[0]); + + /* TODO: N does not call fsFsGetFileTimestampRaw here, but we possibly could. */ + } else { + FsDir dir; + R_TRY(fsFsOpenDirectory(&g_sd_fs, fs_path, FS_DIROPEN_FILE | FS_DIROPEN_DIRECTORY, &dir)); + fsDirClose(&dir); + is_directory.SetValue(1); + } + + return ResultSuccess; } - FsFile f; - - R_TRY(GetFileByHandle(&f, hnd[0])); - R_TRY(fsFileWrite(&f, offset, data.buffer, data.num_elements, FS_WRITEOPTION_NONE)); - - out_written.SetValue(data.num_elements); - return ResultSuccess; -} - -Result DebugMonitorService::TargetIO_FileSetAttributes(InBuffer path, InBuffer attributes) { - /* I don't really know why this command exists, Horizon doesn't allow you to set any attributes. */ - /* N just returns ResultSuccess unconditionally here. */ - return ResultSuccess; -} - -Result DebugMonitorService::TargetIO_FileGetInformation(InBuffer path, OutBuffer out_info, Out is_directory) { - if (out_info.num_elements != 4) { - /* Serialization error. */ - return ResultKernelConnectionClosed; + Result DebugMonitorService::TargetIO_FileSetTime(InBuffer path, u64 create, u64 access, u64 modify) { + /* This is another function that doesn't really need to exist, because Horizon doesn't let you set anything. */ + return ResultSuccess; } - R_TRY(EnsureSdInitialized()); + Result DebugMonitorService::TargetIO_FileSetSize(InBuffer input, u64 size) { + /* Why does this function take in a path and not a file handle? */ - char fs_path[FS_MAX_PATH]; - FixPath(fs_path, sizeof(fs_path), path); + /* We will try to be better than N, here. N only treats input as a path. */ + if (input.num_elements == sizeof(u64)) { + FsFile f; + if (R_SUCCEEDED(GetFileByHandle(&f, reinterpret_cast(input.buffer)[0]))) { + return fsFileSetSize(&f, size); + } + } - for (size_t i = 0; i < out_info.num_elements; i++) { - out_info[i] = 0; - } - is_directory.SetValue(0); + R_TRY(EnsureSdInitialized()); - FsFile f; - if (R_SUCCEEDED(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_READ, &f))) { + char fs_path[FS_MAX_PATH]; + FixPath(fs_path, sizeof(fs_path), input); + + FsFile f; + R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_WRITE, &f)); ON_SCOPE_EXIT { fsFileClose(&f); }; - /* N doesn't check this return code. */ - fsFileGetSize(&f, &out_info[0]); - - /* TODO: N does not call fsFsGetFileTimestampRaw here, but we possibly could. */ - } else { - FsDir dir; - R_TRY(fsFsOpenDirectory(&g_sd_fs, fs_path, FS_DIROPEN_FILE | FS_DIROPEN_DIRECTORY, &dir)); - fsDirClose(&dir); - is_directory.SetValue(1); + return fsFileSetSize(&f, size); } - return ResultSuccess; -} + Result DebugMonitorService::TargetIO_FileDelete(InBuffer path) { + R_TRY(EnsureSdInitialized()); -Result DebugMonitorService::TargetIO_FileSetTime(InBuffer path, u64 create, u64 access, u64 modify) { - /* This is another function that doesn't really need to exist, because Horizon doesn't let you set anything. */ - return ResultSuccess; -} + char fs_path[FS_MAX_PATH]; + FixPath(fs_path, sizeof(fs_path), path); -Result DebugMonitorService::TargetIO_FileSetSize(InBuffer input, u64 size) { - /* Why does this function take in a path and not a file handle? */ - - /* We will try to be better than N, here. N only treats input as a path. */ - if (input.num_elements == sizeof(u64)) { - FsFile f; - if (R_SUCCEEDED(GetFileByHandle(&f, reinterpret_cast(input.buffer)[0]))) { - return fsFileSetSize(&f, size); - } + return fsFsDeleteFile(&g_sd_fs, fs_path); } - R_TRY(EnsureSdInitialized()); + Result DebugMonitorService::TargetIO_FileMove(InBuffer path0, InBuffer path1) { + R_TRY(EnsureSdInitialized()); - char fs_path[FS_MAX_PATH]; - FixPath(fs_path, sizeof(fs_path), input); + char fs_path0[FS_MAX_PATH]; + char fs_path1[FS_MAX_PATH]; + FixPath(fs_path0, sizeof(fs_path0), path0); + FixPath(fs_path1, sizeof(fs_path1), path1); - FsFile f; - R_TRY(fsFsOpenFile(&g_sd_fs, fs_path, FS_OPEN_WRITE, &f)); - ON_SCOPE_EXIT { fsFileClose(&f); }; + return fsFsRenameFile(&g_sd_fs, fs_path0, fs_path1); + } - return fsFileSetSize(&f, size); -} - -Result DebugMonitorService::TargetIO_FileDelete(InBuffer path) { - R_TRY(EnsureSdInitialized()); - - char fs_path[FS_MAX_PATH]; - FixPath(fs_path, sizeof(fs_path), path); - - return fsFsDeleteFile(&g_sd_fs, fs_path); -} - -Result DebugMonitorService::TargetIO_FileMove(InBuffer path0, InBuffer path1) { - R_TRY(EnsureSdInitialized()); - - char fs_path0[FS_MAX_PATH]; - char fs_path1[FS_MAX_PATH]; - FixPath(fs_path0, sizeof(fs_path0), path0); - FixPath(fs_path1, sizeof(fs_path1), path1); - - return fsFsRenameFile(&g_sd_fs, fs_path0, fs_path1); } diff --git a/stratosphere/dmnt/source/ini.c b/stratosphere/dmnt/source/ini.c deleted file mode 100644 index 63626c72d..000000000 --- a/stratosphere/dmnt/source/ini.c +++ /dev/null @@ -1,269 +0,0 @@ -/* inih -- simple .INI file parser - -inih is released under the New BSD license (see LICENSE.txt). Go to the project -home page for more info: - -https://github.com/benhoyt/inih - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include -#include -#include - -#include "ini.h" - -#if !INI_USE_STACK -#include -#endif - -#define MAX_SECTION 50 -#define MAX_NAME 50 - -/* Used by ini_parse_string() to keep track of string parsing state. */ -typedef struct { - const char* ptr; - size_t num_left; -} ini_parse_string_ctx; - -/* Strip whitespace chars off end of given string, in place. Return s. */ -static char* rstrip(char* s) -{ - char* p = s + strlen(s); - while (p > s && isspace((unsigned char)(*--p))) - *p = '\0'; - return s; -} - -/* Return pointer to first non-whitespace char in given string. */ -static char* lskip(const char* s) -{ - while (*s && isspace((unsigned char)(*s))) - s++; - return (char*)s; -} - -/* Return pointer to first char (of chars) or inline comment in given string, - or pointer to null at end of string if neither found. Inline comment must - be prefixed by a whitespace character to register as a comment. */ -static char* find_chars_or_comment(const char* s, const char* chars) -{ -#if INI_ALLOW_INLINE_COMMENTS - int was_space = 0; - while (*s && (!chars || !strchr(chars, *s)) && - !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { - was_space = isspace((unsigned char)(*s)); - s++; - } -#else - while (*s && (!chars || !strchr(chars, *s))) { - s++; - } -#endif - return (char*)s; -} - -/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ -static char* strncpy0(char* dest, const char* src, size_t size) -{ - strncpy(dest, src, size - 1); - dest[size - 1] = '\0'; - return dest; -} - -/* See documentation in header file. */ -int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, - void* user) -{ - /* Uses a fair bit of stack (use heap instead if you need to) */ -#if INI_USE_STACK - char line[INI_MAX_LINE]; - int max_line = INI_MAX_LINE; -#else - char* line; - int max_line = INI_INITIAL_ALLOC; -#endif -#if INI_ALLOW_REALLOC - char* new_line; - int offset; -#endif - char section[MAX_SECTION] = ""; - char prev_name[MAX_NAME] = ""; - - char* start; - char* end; - char* name; - char* value; - int lineno = 0; - int error = 0; - -#if !INI_USE_STACK - line = (char*)malloc(INI_INITIAL_ALLOC); - if (!line) { - return -2; - } -#endif - -#if INI_HANDLER_LINENO -#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) -#else -#define HANDLER(u, s, n, v) handler(u, s, n, v) -#endif - - /* Scan through stream line by line */ - while (reader(line, max_line, stream) != NULL) { -#if INI_ALLOW_REALLOC - offset = strlen(line); - while (offset == max_line - 1 && line[offset - 1] != '\n') { - max_line *= 2; - if (max_line > INI_MAX_LINE) - max_line = INI_MAX_LINE; - new_line = realloc(line, max_line); - if (!new_line) { - free(line); - return -2; - } - line = new_line; - if (reader(line + offset, max_line - offset, stream) == NULL) - break; - if (max_line >= INI_MAX_LINE) - break; - offset += strlen(line + offset); - } -#endif - - lineno++; - - start = line; -#if INI_ALLOW_BOM - if (lineno == 1 && (unsigned char)start[0] == 0xEF && - (unsigned char)start[1] == 0xBB && - (unsigned char)start[2] == 0xBF) { - start += 3; - } -#endif - start = lskip(rstrip(start)); - - if (strchr(INI_START_COMMENT_PREFIXES, *start)) { - /* Start-of-line comment */ - } -#if INI_ALLOW_MULTILINE - else if (*prev_name && *start && start > line) { - /* Non-blank line with leading whitespace, treat as continuation - of previous name's value (as per Python configparser). */ - if (!HANDLER(user, section, prev_name, start) && !error) - error = lineno; - } -#endif - else if (*start == '[') { - /* A "[section]" line */ - end = find_chars_or_comment(start + 1, "]"); - if (*end == ']') { - *end = '\0'; - strncpy0(section, start + 1, sizeof(section)); - *prev_name = '\0'; - } - else if (!error) { - /* No ']' found on section line */ - error = lineno; - } - } - else if (*start) { - /* Not a comment, must be a name[=:]value pair */ - end = find_chars_or_comment(start, "=:"); - if (*end == '=' || *end == ':') { - *end = '\0'; - name = rstrip(start); - value = end + 1; -#if INI_ALLOW_INLINE_COMMENTS - end = find_chars_or_comment(value, NULL); - if (*end) - *end = '\0'; -#endif - value = lskip(value); - rstrip(value); - - /* Valid name[=:]value pair found, call handler */ - strncpy0(prev_name, name, sizeof(prev_name)); - if (!HANDLER(user, section, name, value) && !error) - error = lineno; - } - else if (!error) { - /* No '=' or ':' found on name[=:]value line */ - error = lineno; - } - } - -#if INI_STOP_ON_FIRST_ERROR - if (error) - break; -#endif - } - -#if !INI_USE_STACK - free(line); -#endif - - return error; -} - -/* See documentation in header file. */ -int ini_parse_file(FILE* file, ini_handler handler, void* user) -{ - return ini_parse_stream((ini_reader)fgets, file, handler, user); -} - -/* See documentation in header file. */ -int ini_parse(const char* filename, ini_handler handler, void* user) -{ - FILE* file; - int error; - - file = fopen(filename, "r"); - if (!file) - return -1; - error = ini_parse_file(file, handler, user); - fclose(file); - return error; -} - -/* An ini_reader function to read the next line from a string buffer. This - is the fgets() equivalent used by ini_parse_string(). */ -static char* ini_reader_string(char* str, int num, void* stream) { - ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; - const char* ctx_ptr = ctx->ptr; - size_t ctx_num_left = ctx->num_left; - char* strp = str; - char c; - - if (ctx_num_left == 0 || num < 2) - return NULL; - - while (num > 1 && ctx_num_left != 0) { - c = *ctx_ptr++; - ctx_num_left--; - *strp++ = c; - if (c == '\n') - break; - num--; - } - - *strp = '\0'; - ctx->ptr = ctx_ptr; - ctx->num_left = ctx_num_left; - return str; -} - -/* See documentation in header file. */ -int ini_parse_string(const char* string, ini_handler handler, void* user) { - ini_parse_string_ctx ctx; - - ctx.ptr = string; - ctx.num_left = strlen(string); - return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, - user); -} diff --git a/stratosphere/dmnt/source/ini.h b/stratosphere/dmnt/source/ini.h deleted file mode 100644 index f45ba40ba..000000000 --- a/stratosphere/dmnt/source/ini.h +++ /dev/null @@ -1,130 +0,0 @@ -/* inih -- simple .INI file parser - -inih is released under the New BSD license (see LICENSE.txt). Go to the project -home page for more info: - -https://github.com/benhoyt/inih - -*/ - -#ifndef __INI_H__ -#define __INI_H__ - -/* Make this header file easier to include in C++ code */ -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/* Nonzero if ini_handler callback should accept lineno parameter. */ -#ifndef INI_HANDLER_LINENO -#define INI_HANDLER_LINENO 0 -#endif - -/* Typedef for prototype of handler function. */ -#if INI_HANDLER_LINENO -typedef int (*ini_handler)(void* user, const char* section, - const char* name, const char* value, - int lineno); -#else -typedef int (*ini_handler)(void* user, const char* section, - const char* name, const char* value); -#endif - -/* Typedef for prototype of fgets-style reader function. */ -typedef char* (*ini_reader)(char* str, int num, void* stream); - -/* Parse given INI-style file. May have [section]s, name=value pairs - (whitespace stripped), and comments starting with ';' (semicolon). Section - is "" if name=value pair parsed before any section heading. name:value - pairs are also supported as a concession to Python's configparser. - - For each name=value pair parsed, call handler function with given user - pointer as well as section, name, and value (data only valid for duration - of handler call). Handler should return nonzero on success, zero on error. - - Returns 0 on success, line number of first error on parse error (doesn't - stop on first error), -1 on file open error, or -2 on memory allocation - error (only when INI_USE_STACK is zero). -*/ -int ini_parse(const char* filename, ini_handler handler, void* user); - -/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't - close the file when it's finished -- the caller must do that. */ -int ini_parse_file(FILE* file, ini_handler handler, void* user); - -/* Same as ini_parse(), but takes an ini_reader function pointer instead of - filename. Used for implementing custom or string-based I/O (see also - ini_parse_string). */ -int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, - void* user); - -/* Same as ini_parse(), but takes a zero-terminated string with the INI data -instead of a file. Useful for parsing INI data from a network socket or -already in memory. */ -int ini_parse_string(const char* string, ini_handler handler, void* user); - -/* Nonzero to allow multi-line value parsing, in the style of Python's - configparser. If allowed, ini_parse() will call the handler with the same - name for each subsequent line parsed. */ -#ifndef INI_ALLOW_MULTILINE -#define INI_ALLOW_MULTILINE 1 -#endif - -/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of - the file. See http://code.google.com/p/inih/issues/detail?id=21 */ -#ifndef INI_ALLOW_BOM -#define INI_ALLOW_BOM 1 -#endif - -/* Chars that begin a start-of-line comment. Per Python configparser, allow - both ; and # comments at the start of a line by default. */ -#ifndef INI_START_COMMENT_PREFIXES -#define INI_START_COMMENT_PREFIXES ";#" -#endif - -/* Nonzero to allow inline comments (with valid inline comment characters - specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match - Python 3.2+ configparser behaviour. */ -#ifndef INI_ALLOW_INLINE_COMMENTS -#define INI_ALLOW_INLINE_COMMENTS 1 -#endif -#ifndef INI_INLINE_COMMENT_PREFIXES -#define INI_INLINE_COMMENT_PREFIXES ";" -#endif - -/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ -#ifndef INI_USE_STACK -#define INI_USE_STACK 1 -#endif - -/* Maximum line length for any line in INI file (stack or heap). Note that - this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ -#ifndef INI_MAX_LINE -#define INI_MAX_LINE 200 -#endif - -/* Nonzero to allow heap line buffer to grow via realloc(), zero for a - fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is - zero. */ -#ifndef INI_ALLOW_REALLOC -#define INI_ALLOW_REALLOC 0 -#endif - -/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK - is zero. */ -#ifndef INI_INITIAL_ALLOC -#define INI_INITIAL_ALLOC 200 -#endif - -/* Stop parsing on first error (default is to keep parsing). */ -#ifndef INI_STOP_ON_FIRST_ERROR -#define INI_STOP_ON_FIRST_ERROR 0 -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __INI_H__ */ diff --git a/stratosphere/dmnt/source/pm_shim.c b/stratosphere/dmnt/source/pm_shim.c deleted file mode 100644 index 7c83beca0..000000000 --- a/stratosphere/dmnt/source/pm_shim.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2018-2019 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include "pm_shim.h" - -/* Atmosphere extension commands. */ -Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid) { - IpcCommand c; - ipcInitialize(&c); - Service *s = pmdmntGetServiceSession(); - - struct { - u64 magic; - u64 cmd_id; - u64 pid; - } *raw; - - raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw)); - - raw->magic = SFCI_MAGIC; - raw->cmd_id = 65000; - raw->pid = pid; - - Result rc = serviceIpcDispatch(s); - - if (R_SUCCEEDED(rc)) { - IpcParsedCommand r; - struct { - u64 magic; - u64 result; - u64 title_id; - FsStorageId storage_id; - } *resp; - - serviceIpcParse(s, &r, sizeof(*resp)); - resp = r.Raw; - - rc = resp->result; - - if (R_SUCCEEDED(rc)) { - if (out) { - *out = r.Handles[0]; - } else { - svcCloseHandle(r.Handles[0]); - } - if (tid_out) *tid_out = resp->title_id; - if (sid_out) *sid_out = resp->storage_id; - } - } - - return rc; -} diff --git a/stratosphere/dmnt/source/pm_shim.h b/stratosphere/dmnt/source/pm_shim.h deleted file mode 100644 index 76d4e68b9..000000000 --- a/stratosphere/dmnt/source/pm_shim.h +++ /dev/null @@ -1,19 +0,0 @@ -/** - * @file pm_shim.h - * @brief Process Management (pm) IPC wrapper. - * @author SciresM - * @copyright libnx Authors - */ -#pragma once -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Atmosphere extension commands. */ -Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, FsStorageId *sid_out, u64 pid); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/stratosphere/libstratosphere/Makefile b/stratosphere/libstratosphere/Makefile index ea746be8b..add6a0814 100644 --- a/stratosphere/libstratosphere/Makefile +++ b/stratosphere/libstratosphere/Makefile @@ -32,7 +32,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \ CFLAGS += $(INCLUDE) -D__SWITCH__ -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++17 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -flto -std=gnu++17 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) diff --git a/stratosphere/dmnt/source/dmnt_hid.hpp b/stratosphere/libstratosphere/include/stratosphere/dmnt.hpp similarity index 89% rename from stratosphere/dmnt/source/dmnt_hid.hpp rename to stratosphere/libstratosphere/include/stratosphere/dmnt.hpp index e56b95143..625399596 100644 --- a/stratosphere/dmnt/source/dmnt_hid.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/dmnt.hpp @@ -17,7 +17,4 @@ #pragma once #include -class HidManagement { - public: - static Result GetKeysDown(u64 *keys); -}; +#include "dmnt/dmnt_cheat_types.hpp" diff --git a/stratosphere/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp b/stratosphere/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp new file mode 100644 index 000000000..1edb9192f --- /dev/null +++ b/stratosphere/libstratosphere/include/stratosphere/dmnt/dmnt_cheat_types.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "../ncm/ncm_types.hpp" + +namespace sts::dmnt::cheat { + + struct CheatProcessMetadata { + struct MemoryRegionExtents { + u64 base; + u64 size; + }; + + u64 process_id; + ncm::TitleId title_id; + MemoryRegionExtents main_nso_extents; + MemoryRegionExtents heap_extents; + MemoryRegionExtents alias_extents; + MemoryRegionExtents aslr_extents; + u8 main_nso_build_id[0x20]; + }; + + static_assert(std::is_pod::value && sizeof(CheatProcessMetadata) == 0x70, "CheatProcessMetadata definition!"); + + struct CheatDefinition { + char readable_name[0x40]; + uint32_t num_opcodes; + uint32_t opcodes[0x100]; + }; + + struct CheatEntry { + bool enabled; + uint32_t cheat_id; + CheatDefinition definition; + }; + + struct FrozenAddressValue { + u64 value; + u8 width; + }; + + struct FrozenAddressEntry { + u64 address; + FrozenAddressValue value; + }; + +} diff --git a/stratosphere/libstratosphere/include/stratosphere/hossynch.hpp b/stratosphere/libstratosphere/include/stratosphere/hossynch.hpp index 624dba803..2b654a74e 100644 --- a/stratosphere/libstratosphere/include/stratosphere/hossynch.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/hossynch.hpp @@ -224,24 +224,33 @@ class HosSignal { mutexUnlock(&m); } - void Wait() { + void Wait(bool reset = false) { mutexLock(&m); while (!signaled) { condvarWait(&cv, &m); } + if (reset) { + this->signaled = false; + } + mutexUnlock(&m); } - bool TryWait() { + bool TryWait(bool reset = false) { mutexLock(&m); + bool success = signaled; + if (reset) { + this->signaled = false; + } + mutexUnlock(&m); return success; } - Result TimedWait(u64 ns) { + Result TimedWait(u64 ns, bool reset = false) { mutexLock(&m); TimeoutHelper timeout_helper(ns); @@ -251,6 +260,10 @@ class HosSignal { } } + if (reset) { + this->signaled = false; + } + mutexUnlock(&m); return true; } diff --git a/stratosphere/libstratosphere/include/stratosphere/pm.hpp b/stratosphere/libstratosphere/include/stratosphere/pm.hpp index 92d8eef6c..2a8d79762 100644 --- a/stratosphere/libstratosphere/include/stratosphere/pm.hpp +++ b/stratosphere/libstratosphere/include/stratosphere/pm.hpp @@ -20,4 +20,5 @@ #include "pm/pm_types.hpp" #include "pm/pm_boot_mode_api.hpp" #include "pm/pm_info_api.hpp" -#include "pm/pm_shell_api.hpp" \ No newline at end of file +#include "pm/pm_shell_api.hpp" +#include "pm/pm_dmnt_api.hpp" \ No newline at end of file diff --git a/stratosphere/dmnt/source/dmnt_hid.cpp b/stratosphere/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp similarity index 52% rename from stratosphere/dmnt/source/dmnt_hid.cpp rename to stratosphere/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp index 016052cd2..607b4c43d 100644 --- a/stratosphere/dmnt/source/dmnt_hid.cpp +++ b/stratosphere/libstratosphere/include/stratosphere/pm/pm_dmnt_api.hpp @@ -14,23 +14,19 @@ * along with this program. If not, see . */ -#include -#include -#include +#pragma once -#include "dmnt_hid.hpp" +#include "../ldr.hpp" +#include "pm_types.hpp" -static HosMutex g_hid_keys_down_lock; +namespace sts::pm::dmnt { -Result HidManagement::GetKeysDown(u64 *keys) { - std::scoped_lock lk(g_hid_keys_down_lock); + /* Debug Monitor API. */ + Result StartProcess(u64 process_id); + Result GetProcessId(u64 *out_process_id, const ncm::TitleId title_id); + Result GetApplicationProcessId(u64 *out_process_id); + Result HookToCreateApplicationProcess(Handle *out_handle); + Result AtmosphereGetProcessInfo(Handle *out_handle, ncm::TitleLocation *out_loc, u64 process_id); + Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, LimitableResource resource); - hidScanInput(); - *keys = 0; - - for (int controller = 0; controller < 10; controller++) { - *keys |= hidKeysHeld((HidControllerID) controller); - } - - return ResultSuccess; } diff --git a/stratosphere/libstratosphere/source/pm/pm_ams.c b/stratosphere/libstratosphere/source/pm/pm_ams.c index 46882eb07..2e1c19886 100644 --- a/stratosphere/libstratosphere/source/pm/pm_ams.c +++ b/stratosphere/libstratosphere/source/pm/pm_ams.c @@ -94,3 +94,94 @@ Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid) { return rc; } + +Result pmdmntAtmosphereGetProcessInfo(Handle* out, u64 *tid_out, u8 *sid_out, u64 pid) { + IpcCommand c; + ipcInitialize(&c); + Service *s = pmdmntGetServiceSession(); + + struct { + u64 magic; + u64 cmd_id; + u64 pid; + } *raw; + + raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 65000; + raw->pid = pid; + + Result rc = serviceIpcDispatch(s); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 title_id; + FsStorageId storage_id; + } *resp; + + serviceIpcParse(s, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + if (out) { + *out = r.Handles[0]; + } else { + svcCloseHandle(r.Handles[0]); + } + if (tid_out) *tid_out = resp->title_id; + if (sid_out) *sid_out = resp->storage_id; + } + } + + return rc; +} + +Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource) { + IpcCommand c; + ipcInitialize(&c); + Service *s = pmdmntGetServiceSession(); + + struct { + u64 magic; + u64 cmd_id; + u32 group; + u32 resource; + } *raw; + + raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw)); + + raw->magic = SFCI_MAGIC; + raw->cmd_id = 65001; + raw->group = group; + raw->resource = resource; + + Result rc = serviceIpcDispatch(s); + + if (R_SUCCEEDED(rc)) { + IpcParsedCommand r; + struct { + u64 magic; + u64 result; + u64 cur_value; + u64 lim_value; + } *resp; + + serviceIpcParse(s, &r, sizeof(*resp)); + resp = r.Raw; + + rc = resp->result; + + if (R_SUCCEEDED(rc)) { + if (out_cur) *out_cur = resp->cur_value; + if (out_lim) *out_lim = resp->lim_value; + } + } + + return rc; +} diff --git a/stratosphere/libstratosphere/source/pm/pm_ams.h b/stratosphere/libstratosphere/source/pm/pm_ams.h index 78bc9ce92..118879717 100644 --- a/stratosphere/libstratosphere/source/pm/pm_ams.h +++ b/stratosphere/libstratosphere/source/pm/pm_ams.h @@ -14,6 +14,9 @@ extern "C" { Result pminfoAtmosphereGetProcessId(u64 *out_pid, u64 tid); Result pminfoAtmosphereHasLaunchedTitle(bool *out, u64 tid); +Result pmdmntAtmosphereGetProcessInfo(Handle *out, u64 *tid_out, u8 *sid_out, u64 pid); +Result pmdmntAtmosphereGetCurrentLimitInfo(u64 *out_cur, u64 *out_lim, u32 group, u32 resource); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/stratosphere/libstratosphere/source/pm/pm_dmnt_api.cpp b/stratosphere/libstratosphere/source/pm/pm_dmnt_api.cpp new file mode 100644 index 000000000..2ea35478c --- /dev/null +++ b/stratosphere/libstratosphere/source/pm/pm_dmnt_api.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "pm_ams.h" + +namespace sts::pm::dmnt { + + /* Debug Monitor API. */ + Result StartProcess(u64 process_id) { + return pmdmntStartProcess(process_id); + } + + Result GetProcessId(u64 *out_process_id, const ncm::TitleId title_id) { + return pmdmntGetTitlePid(out_process_id, static_cast(title_id)); + } + + Result GetApplicationProcessId(u64 *out_process_id) { + return pmdmntGetApplicationPid(out_process_id); + } + + Result HookToCreateApplicationProcess(Handle *out_handle) { + return pmdmntEnableDebugForApplication(out_handle); + } + + Result AtmosphereGetProcessInfo(Handle *out_handle, ncm::TitleLocation *out_loc, u64 process_id) { + *out_handle = INVALID_HANDLE; + *out_loc = {}; + return pmdmntAtmosphereGetProcessInfo(out_handle, reinterpret_cast(&out_loc->title_id), &out_loc->storage_id, process_id); + } + + Result AtmosphereGetCurrentLimitInfo(u64 *out_current_value, u64 *out_limit_value, ResourceLimitGroup group, LimitableResource resource) { + *out_current_value = 0; + *out_limit_value = 0; + return pmdmntAtmosphereGetCurrentLimitInfo(out_current_value, out_limit_value, group, resource); + } + +}