diff --git a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp index 19db7ce98..51452f228 100644 --- a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp +++ b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.cpp @@ -66,6 +66,14 @@ namespace ams::dmnt::cheat { return dmnt::cheat::impl::QueryCheatProcessMemory(mapping.GetPointer(), address); } + Result CheatService::BreakCheatProcess() { + return dmnt::cheat::impl::BreakCheatProcess(); + } + + Result CheatService::ContinueCheatProcess() { + return dmnt::cheat::impl::ContinueCheatProcess(); + } + /* ========================================================================================= */ /* =================================== Cheat Commands ==================================== */ /* ========================================================================================= */ @@ -95,6 +103,18 @@ namespace ams::dmnt::cheat { return dmnt::cheat::impl::RemoveCheat(cheat_id); } + Result CheatService::ReadStaticRegister(sf::Out out, u8 which) { + return dmnt::cheat::impl::ReadStaticRegister(out.GetPointer(), which); + } + + Result CheatService::WriteStaticRegister(u8 which, u64 value) { + return dmnt::cheat::impl::WriteStaticRegister(which, value); + } + + Result CheatService::ResetStaticRegisters() { + return dmnt::cheat::impl::ResetStaticRegisters(); + } + /* ========================================================================================= */ /* =================================== Address Commands ================================== */ /* ========================================================================================= */ diff --git a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp index 5fbdc7ba9..416aeca2c 100644 --- a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp +++ b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp @@ -33,14 +33,19 @@ namespace ams::dmnt::cheat { ReadCheatProcessMemory = 65102, WriteCheatProcessMemory = 65103, QueryCheatProcessMemory = 65104, + BreakCheatProcess = 65105, + ContinueCheatProcess = 65106, /* Interact with Cheats */ - GetCheatCount = 65200, - GetCheats = 65201, - GetCheatById = 65202, - ToggleCheat = 65203, - AddCheat = 65204, - RemoveCheat = 65205, + GetCheatCount = 65200, + GetCheats = 65201, + GetCheatById = 65202, + ToggleCheat = 65203, + AddCheat = 65204, + RemoveCheat = 65205, + ReadStaticRegister = 65206, + WriteStaticRegister = 65207, + ResetStaticRegisters = 65208, /* Interact with Frozen Addresses */ GetFrozenAddressCount = 65300, @@ -60,6 +65,8 @@ namespace ams::dmnt::cheat { Result ReadCheatProcessMemory(const sf::OutBuffer &buffer, u64 address, u64 out_size); Result WriteCheatProcessMemory(const sf::InBuffer &buffer, u64 address, u64 in_size); Result QueryCheatProcessMemory(sf::Out mapping, u64 address); + Result BreakCheatProcess(); + Result ContinueCheatProcess(); Result GetCheatCount(sf::Out out_count); Result GetCheats(const sf::OutArray &cheats, sf::Out out_count, u64 offset); @@ -67,6 +74,9 @@ namespace ams::dmnt::cheat { Result ToggleCheat(u32 cheat_id); Result AddCheat(const CheatDefinition &cheat, sf::Out out_cheat_id, bool enabled); Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(sf::Out out, u8 which); + Result WriteStaticRegister(u8 which, u64 value); + Result ResetStaticRegisters(); Result GetFrozenAddressCount(sf::Out out_count); Result GetFrozenAddresses(const sf::OutArray &addresses, sf::Out out_count, u64 offset); @@ -86,6 +96,8 @@ namespace ams::dmnt::cheat { MAKE_SERVICE_COMMAND_META(ReadCheatProcessMemory), MAKE_SERVICE_COMMAND_META(WriteCheatProcessMemory), MAKE_SERVICE_COMMAND_META(QueryCheatProcessMemory), + MAKE_SERVICE_COMMAND_META(BreakCheatProcess), + MAKE_SERVICE_COMMAND_META(ContinueCheatProcess), MAKE_SERVICE_COMMAND_META(GetCheatCount), MAKE_SERVICE_COMMAND_META(GetCheats), @@ -93,6 +105,9 @@ namespace ams::dmnt::cheat { MAKE_SERVICE_COMMAND_META(ToggleCheat), MAKE_SERVICE_COMMAND_META(AddCheat), MAKE_SERVICE_COMMAND_META(RemoveCheat), + MAKE_SERVICE_COMMAND_META(ReadStaticRegister), + MAKE_SERVICE_COMMAND_META(WriteStaticRegister), + MAKE_SERVICE_COMMAND_META(ResetStaticRegisters), MAKE_SERVICE_COMMAND_META(GetFrozenAddressCount), MAKE_SERVICE_COMMAND_META(GetFrozenAddresses), diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp index 02e336604..a2cd6e817 100644 --- a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp @@ -31,6 +31,7 @@ namespace ams::dmnt::cheat::impl { static constexpr size_t ThreadStackSize = 0x4000; private: os::Mutex cheat_lock; + os::Event unsafe_break_event; os::Event debug_events_event; /* Autoclear. */ os::ThreadType detect_thread, debug_events_thread; os::SystemEvent cheat_process_event; @@ -38,6 +39,7 @@ namespace ams::dmnt::cheat::impl { CheatProcessMetadata cheat_process_metadata = {}; os::ThreadType vm_thread; + bool broken_unsafe = false; bool needs_reload_vm = false; CheatVirtualMachine cheat_vm; @@ -85,6 +87,8 @@ namespace ams::dmnt::cheat::impl { for (size_t i = 0; i < MaxCheatCount; i++) { this->ResetCheatEntry(i); } + + this->cheat_vm.ResetStaticRegisters(); } CheatEntry *GetCheatEntryById(size_t i) { @@ -119,6 +123,10 @@ namespace ams::dmnt::cheat::impl { void CloseActiveCheatProcess() { if (this->cheat_process_debug_handle != svc::InvalidHandle) { + /* We don't need to do any unsafe brekaing. */ + this->broken_unsafe = false; + this->unsafe_break_event.Signal(); + /* Knock out the debug events thread. */ os::CancelThreadSynchronization(std::addressof(this->debug_events_thread)); @@ -182,7 +190,7 @@ namespace ams::dmnt::cheat::impl { } public: - CheatProcessManager() : cheat_lock(false), debug_events_event(os::EventClearMode_AutoClear), cheat_process_event(os::EventClearMode_AutoClear, true) { + CheatProcessManager() : cheat_lock(false), unsafe_break_event(os::EventClearMode_ManualClear), debug_events_event(os::EventClearMode_AutoClear), cheat_process_event(os::EventClearMode_AutoClear, true) { /* Learn whether we should enable cheats by default. */ { u8 en = 0; @@ -257,6 +265,19 @@ namespace ams::dmnt::cheat::impl { return ResultSuccess(); } + Result BreakCheatProcessUnsafe() { + this->broken_unsafe = true; + this->unsafe_break_event.Clear(); + return svcBreakDebugProcess(this->GetCheatProcessHandle()); + } + + Result ContinueCheatProcessUnsafe() { + this->broken_unsafe = false; + this->unsafe_break_event.Signal(); + dmnt::cheat::impl::ContinueCheatProcess(this->GetCheatProcessHandle()); + return ResultSuccess(); + } + Result GetCheatProcessMappingCount(u64 *out_count) { std::scoped_lock lk(this->cheat_lock); @@ -335,6 +356,22 @@ namespace ams::dmnt::cheat::impl { return svcQueryDebugProcessMemory(mapping, &tmp, this->GetCheatProcessHandle(), address); } + Result BreakCheatProcess() { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + return this->BreakCheatProcessUnsafe(); + } + + Result ContinueCheatProcess() { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + return this->ContinueCheatProcessUnsafe(); + } + Result GetCheatCount(u64 *out_count) { std::scoped_lock lk(this->cheat_lock); @@ -436,6 +473,35 @@ namespace ams::dmnt::cheat::impl { return ResultSuccess(); } + Result ReadStaticRegister(u64 *out, size_t which) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + R_UNLESS(which < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid()); + + *out = this->cheat_vm.GetStaticRegister(which); + return ResultSuccess(); + } + + Result WriteStaticRegister(size_t which, u64 value) { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + R_UNLESS(which < CheatVirtualMachine::NumStaticRegisters, ResultCheatInvalid()); + + this->cheat_vm.SetStaticRegister(which, value); + return ResultSuccess(); + } + + Result ResetStaticRegisters() { + std::scoped_lock lk(this->cheat_lock); + + R_TRY(this->EnsureCheatProcess()); + + this->cheat_vm.ResetStaticRegisters(); + return ResultSuccess(); + } + Result GetFrozenAddressCount(u64 *out_count) { std::scoped_lock lk(this->cheat_lock); @@ -533,7 +599,20 @@ namespace ams::dmnt::cheat::impl { this_ptr->debug_events_event.Wait(); while (true) { while (R_SUCCEEDED(svcWaitSynchronizationSingle(this_ptr->GetCheatProcessHandle(), std::numeric_limits::max()))) { - std::scoped_lock lk(this_ptr->cheat_lock); + this_ptr->cheat_lock.Lock(); + ON_SCOPE_EXIT { this_ptr->cheat_lock.Unlock(); }; + + /* If we did an unsafe break, wait until we're not broken. */ + if (this_ptr->broken_unsafe) { + this_ptr->cheat_lock.Unlock(); + this_ptr->unsafe_break_event.Wait(); + this_ptr->cheat_lock.Lock(); + if (this_ptr->GetCheatProcessHandle() != svc::InvalidHandle) { + continue; + } else { + break; + } + } /* Handle any pending debug events. */ if (this_ptr->HasActiveCheatProcess()) { @@ -680,6 +759,10 @@ namespace ams::dmnt::cheat::impl { /* Cancel process guard. */ proc_guard.Cancel(); + /* Reset broken state. */ + this->broken_unsafe = false; + this->unsafe_break_event.Signal(); + /* If new process, start the process. */ if (on_process_launch) { this->StartProcess(this->cheat_process_metadata.process_id); @@ -1021,6 +1104,14 @@ namespace ams::dmnt::cheat::impl { return GetReference(g_cheat_process_manager).WriteCheatProcessMemoryUnsafe(process_addr, data, size); } + Result BreakCheatProcessUnsafe() { + return GetReference(g_cheat_process_manager).BreakCheatProcessUnsafe(); + } + + Result ContinueCheatProcessUnsafe() { + return GetReference(g_cheat_process_manager).ContinueCheatProcessUnsafe(); + } + Result GetCheatProcessMappingCount(u64 *out_count) { return GetReference(g_cheat_process_manager).GetCheatProcessMappingCount(out_count); } @@ -1041,6 +1132,14 @@ namespace ams::dmnt::cheat::impl { return GetReference(g_cheat_process_manager).QueryCheatProcessMemory(mapping, address); } + Result BreakCheatProcess() { + return GetReference(g_cheat_process_manager).BreakCheatProcess(); + } + + Result ContinueCheatProcess() { + return GetReference(g_cheat_process_manager).ContinueCheatProcess(); + } + Result GetCheatCount(u64 *out_count) { return GetReference(g_cheat_process_manager).GetCheatCount(out_count); } @@ -1065,6 +1164,18 @@ namespace ams::dmnt::cheat::impl { return GetReference(g_cheat_process_manager).RemoveCheat(cheat_id); } + Result ReadStaticRegister(u64 *out, size_t which) { + return GetReference(g_cheat_process_manager).ReadStaticRegister(out, which); + } + + Result WriteStaticRegister(size_t which, u64 value) { + return GetReference(g_cheat_process_manager).WriteStaticRegister(which, value); + } + + Result ResetStaticRegisters() { + return GetReference(g_cheat_process_manager).ResetStaticRegisters(); + } + Result GetFrozenAddressCount(u64 *out_count) { return GetReference(g_cheat_process_manager).GetFrozenAddressCount(out_count); } diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp index 6580790de..520103a5a 100644 --- a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.hpp @@ -28,11 +28,16 @@ namespace ams::dmnt::cheat::impl { Result ReadCheatProcessMemoryUnsafe(u64 process_addr, void *out_data, size_t size); Result WriteCheatProcessMemoryUnsafe(u64 process_addr, void *data, size_t size); + Result BreakCheatProcessUnsafe(); + Result ContinueCheatProcessUnsafe(); + 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 BreakCheatProcess(); + Result ContinueCheatProcess(); Result GetCheatCount(u64 *out_count); Result GetCheats(CheatEntry *cheats, size_t max_count, u64 *out_count, u64 offset); @@ -40,6 +45,9 @@ namespace ams::dmnt::cheat::impl { Result ToggleCheat(u32 cheat_id); Result AddCheat(u32 *out_id, const CheatDefinition &def, bool enabled); Result RemoveCheat(u32 cheat_id); + Result ReadStaticRegister(u64 *out, size_t which); + Result WriteStaticRegister(size_t which, u64 value); + Result ResetStaticRegisters(); Result GetFrozenAddressCount(u64 *out_count); Result GetFrozenAddresses(FrozenAddressEntry *frz_addrs, size_t max_count, u64 *out_count, u64 offset); diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp index 173416126..4db17a5d8 100644 --- a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.cpp @@ -237,6 +237,22 @@ namespace ams::dmnt::cheat::impl { this->LogToDebugFile("Act[%02x]: %d\n", i, opcode->save_restore_regmask.should_operate[i]); } break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + this->LogToDebugFile("Opcode: Read/Write Static Register\n"); + if (opcode->rw_static_reg.static_idx < NumReadableStaticRegisters) { + this->LogToDebugFile("Op Type: ReadStaticRegister\n"); + } else { + this->LogToDebugFile("Op Type: WriteStaticRegister\n"); + } + this->LogToDebugFile("Reg Idx: %x\n", opcode->rw_static_reg.idx); + this->LogToDebugFile("Stc Idx: %x\n", opcode->rw_static_reg.static_idx); + break; + case CheatVmOpcodeType_BreakProcess: + this->LogToDebugFile("Opcode: Break Cheat Process\n"); + break; + case CheatVmOpcodeType_ContinueProcess: + this->LogToDebugFile("Opcode: Continue Cheat Process\n"); + break; case CheatVmOpcodeType_DebugLog: this->LogToDebugFile("Opcode: Debug Log\n"); this->LogToDebugFile("Bit Width: %x\n", opcode->debug_log.bit_width); @@ -570,6 +586,30 @@ namespace ams::dmnt::cheat::impl { } } break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + { + /* C3000XXx */ + /* C3 = opcode 0xC3. */ + /* XX = static register index. */ + /* x = register index. */ + opcode.rw_static_reg.static_idx = ((first_dword >> 4) & 0xFF); + opcode.rw_static_reg.idx = (first_dword & 0xF); + } + break; + case CheatVmOpcodeType_BreakProcess: + { + /* FF0????? */ + /* FF0 = opcode 0xFF0 */ + /* Breaks the current process. */ + } + break; + case CheatVmOpcodeType_ContinueProcess: + { + /* FF1????? */ + /* FF1 = opcode 0xFF1 */ + /* Continues the current process. */ + } + break; case CheatVmOpcodeType_DebugLog: { /* FFFTIX## */ @@ -1174,6 +1214,21 @@ namespace ams::dmnt::cheat::impl { } } break; + case CheatVmOpcodeType_ReadWriteStaticRegister: + if (cur_opcode.rw_static_reg.static_idx < NumReadableStaticRegisters) { + /* Load a register with a static register. */ + this->registers[cur_opcode.rw_static_reg.idx] = this->static_registers[cur_opcode.rw_static_reg.static_idx]; + } else { + /* Store a register to a static register. */ + this->static_registers[cur_opcode.rw_static_reg.static_idx] = this->registers[cur_opcode.rw_static_reg.idx]; + } + break; + case CheatVmOpcodeType_BreakProcess: + dmnt::cheat::impl::BreakCheatProcessUnsafe(); + break; + case CheatVmOpcodeType_ContinueProcess: + dmnt::cheat::impl::ContinueCheatProcessUnsafe(); + break; case CheatVmOpcodeType_DebugLog: { /* Read value from memory. */ diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp index 0f267496f..369af1212 100644 --- a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_vm.hpp @@ -42,12 +42,15 @@ namespace ams::dmnt::cheat::impl { CheatVmOpcodeType_BeginRegisterConditionalBlock = 0xC0, CheatVmOpcodeType_SaveRestoreRegister = 0xC1, CheatVmOpcodeType_SaveRestoreRegisterMask = 0xC2, + CheatVmOpcodeType_ReadWriteStaticRegister = 0xC3, /* 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_BreakProcess = 0xFF0, + CheatVmOpcodeType_ContinueProcess = 0xFF1, CheatVmOpcodeType_DebugLog = 0xFFF, }; @@ -223,6 +226,11 @@ namespace ams::dmnt::cheat::impl { bool should_operate[0x10]; }; + struct ReadWriteStaticRegisterOpcode { + u32 static_idx; + u32 idx; + }; + struct DebugLogOpcode { u32 bit_width; u32 log_id; @@ -252,6 +260,7 @@ namespace ams::dmnt::cheat::impl { BeginRegisterConditionalOpcode begin_reg_cond; SaveRestoreRegisterOpcode save_restore_reg; SaveRestoreRegisterMaskOpcode save_restore_regmask; + ReadWriteStaticRegisterOpcode rw_static_reg; DebugLogOpcode debug_log; }; }; @@ -260,6 +269,9 @@ namespace ams::dmnt::cheat::impl { public: constexpr static size_t MaximumProgramOpcodeCount = 0x400; constexpr static size_t NumRegisters = 0x10; + constexpr static size_t NumReadableStaticRegisters = 0x80; + constexpr static size_t NumWritableStaticRegisters = 0x80; + constexpr static size_t NumStaticRegisters = NumReadableStaticRegisters + NumWritableStaticRegisters; private: size_t num_opcodes = 0; size_t instruction_ptr = 0; @@ -268,6 +280,7 @@ namespace ams::dmnt::cheat::impl { u32 program[MaximumProgramOpcodeCount] = {0}; u64 registers[NumRegisters] = {0}; u64 saved_values[NumRegisters] = {0}; + u64 static_registers[NumStaticRegisters] = {0}; size_t loop_tops[NumRegisters] = {0}; private: bool DecodeNextOpcode(CheatVmOpcode *out); @@ -294,6 +307,18 @@ namespace ams::dmnt::cheat::impl { bool LoadProgram(const CheatEntry *cheats, size_t num_cheats); void Execute(const CheatProcessMetadata *metadata); + + u64 GetStaticRegister(size_t which) const { + return this->static_registers[which]; + } + + void SetStaticRegister(size_t which, u64 value) { + this->static_registers[which] = value; + } + + void ResetStaticRegisters() { + std::memset(this->static_registers, 0, sizeof(this->static_registers)); + } #ifdef DMNT_CHEAT_VM_DEBUG_LOG private: fs::FileHandle debug_log_file;