diff --git a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp index 9a507fb3a..492368aee 100644 --- a/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp +++ b/stratosphere/dmnt/source/cheat/dmnt_cheat_service.hpp @@ -16,43 +16,39 @@ #pragma once #include +/* TODO: In libstratosphere, eventually? */ +#define AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 65000, void, HasCheatProcess, (sf::Out out), (out)) \ + AMS_SF_METHOD_INFO(C, H, 65001, void, GetCheatProcessEvent, (sf::OutCopyHandle out_event), (out_event)) \ + AMS_SF_METHOD_INFO(C, H, 65002, Result, GetCheatProcessMetadata, (sf::Out out_metadata), (out_metadata)) \ + AMS_SF_METHOD_INFO(C, H, 65003, Result, ForceOpenCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65004, Result, PauseCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65005, Result, ResumeCheatProcess, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65100, Result, GetCheatProcessMappingCount, (sf::Out out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65101, Result, GetCheatProcessMappings, (const sf::OutArray &mappings, sf::Out out_count, u64 offset), (mappings, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65102, Result, ReadCheatProcessMemory, (const sf::OutBuffer &buffer, u64 address, u64 out_size), (buffer, address, out_size)) \ + AMS_SF_METHOD_INFO(C, H, 65103, Result, WriteCheatProcessMemory, (const sf::InBuffer &buffer, u64 address, u64 in_size), (buffer, address, in_size)) \ + AMS_SF_METHOD_INFO(C, H, 65104, Result, QueryCheatProcessMemory, (sf::Out mapping, u64 address), (mapping, address)) \ + AMS_SF_METHOD_INFO(C, H, 65200, Result, GetCheatCount, (sf::Out out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65201, Result, GetCheats, (const sf::OutArray &cheats, sf::Out out_count, u64 offset), (cheats, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65202, Result, GetCheatById, (sf::Out cheat, u32 cheat_id), (cheat, cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65203, Result, ToggleCheat, (u32 cheat_id), (cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65204, Result, AddCheat, (const dmnt::cheat::CheatDefinition &cheat, sf::Out out_cheat_id, bool enabled), (cheat, out_cheat_id, enabled)) \ + AMS_SF_METHOD_INFO(C, H, 65205, Result, RemoveCheat, (u32 cheat_id), (cheat_id)) \ + AMS_SF_METHOD_INFO(C, H, 65206, Result, ReadStaticRegister, (sf::Out out, u8 which), (out, which)) \ + AMS_SF_METHOD_INFO(C, H, 65207, Result, WriteStaticRegister, (u8 which, u64 value), (which, value)) \ + AMS_SF_METHOD_INFO(C, H, 65208, Result, ResetStaticRegisters, (), ()) \ + AMS_SF_METHOD_INFO(C, H, 65300, Result, GetFrozenAddressCount, (sf::Out out_count), (out_count)) \ + AMS_SF_METHOD_INFO(C, H, 65301, Result, GetFrozenAddresses, (const sf::OutArray &addresses, sf::Out out_count, u64 offset), (addresses, out_count, offset)) \ + AMS_SF_METHOD_INFO(C, H, 65302, Result, GetFrozenAddress, (sf::Out entry, u64 address), (entry, address)) \ + AMS_SF_METHOD_INFO(C, H, 65303, Result, EnableFrozenAddress, (sf::Out out_value, u64 address, u64 width), (out_value, address, width)) \ + AMS_SF_METHOD_INFO(C, H, 65304, Result, DisableFrozenAddress, (u64 address), (address)) + +AMS_SF_DEFINE_INTERFACE(ams::dmnt::cheat::impl, ICheatInterface, AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO) + namespace ams::dmnt::cheat { - /* TODO: In libstratosphere, eventually? */ - namespace impl { - - #define AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO(C, H) \ - AMS_SF_METHOD_INFO(C, H, 65000, void, HasCheatProcess, (sf::Out out)) \ - AMS_SF_METHOD_INFO(C, H, 65001, void, GetCheatProcessEvent, (sf::OutCopyHandle out_event)) \ - AMS_SF_METHOD_INFO(C, H, 65002, Result, GetCheatProcessMetadata, (sf::Out out_metadata)) \ - AMS_SF_METHOD_INFO(C, H, 65003, Result, ForceOpenCheatProcess, ()) \ - AMS_SF_METHOD_INFO(C, H, 65004, Result, PauseCheatProcess, ()) \ - AMS_SF_METHOD_INFO(C, H, 65005, Result, ResumeCheatProcess, ()) \ - AMS_SF_METHOD_INFO(C, H, 65100, Result, GetCheatProcessMappingCount, (sf::Out out_count)) \ - AMS_SF_METHOD_INFO(C, H, 65101, Result, GetCheatProcessMappings, (const sf::OutArray &mappings, sf::Out out_count, u64 offset)) \ - AMS_SF_METHOD_INFO(C, H, 65102, Result, ReadCheatProcessMemory, (const sf::OutBuffer &buffer, u64 address, u64 out_size)) \ - AMS_SF_METHOD_INFO(C, H, 65103, Result, WriteCheatProcessMemory, (const sf::InBuffer &buffer, u64 address, u64 in_size)) \ - AMS_SF_METHOD_INFO(C, H, 65104, Result, QueryCheatProcessMemory, (sf::Out mapping, u64 address)) \ - AMS_SF_METHOD_INFO(C, H, 65200, Result, GetCheatCount, (sf::Out out_count)) \ - AMS_SF_METHOD_INFO(C, H, 65201, Result, GetCheats, (const sf::OutArray &cheats, sf::Out out_count, u64 offset)) \ - AMS_SF_METHOD_INFO(C, H, 65202, Result, GetCheatById, (sf::Out cheat, u32 cheat_id)) \ - AMS_SF_METHOD_INFO(C, H, 65203, Result, ToggleCheat, (u32 cheat_id)) \ - AMS_SF_METHOD_INFO(C, H, 65204, Result, AddCheat, (const CheatDefinition &cheat, sf::Out out_cheat_id, bool enabled)) \ - AMS_SF_METHOD_INFO(C, H, 65205, Result, RemoveCheat, (u32 cheat_id)) \ - AMS_SF_METHOD_INFO(C, H, 65206, Result, ReadStaticRegister, (sf::Out out, u8 which)) \ - AMS_SF_METHOD_INFO(C, H, 65207, Result, WriteStaticRegister, (u8 which, u64 value)) \ - AMS_SF_METHOD_INFO(C, H, 65208, Result, ResetStaticRegisters, ()) \ - AMS_SF_METHOD_INFO(C, H, 65300, Result, GetFrozenAddressCount, (sf::Out out_count)) \ - AMS_SF_METHOD_INFO(C, H, 65301, Result, GetFrozenAddresses, (const sf::OutArray &addresses, sf::Out out_count, u64 offset)) \ - AMS_SF_METHOD_INFO(C, H, 65302, Result, GetFrozenAddress, (sf::Out entry, u64 address)) \ - AMS_SF_METHOD_INFO(C, H, 65303, Result, EnableFrozenAddress, (sf::Out out_value, u64 address, u64 width)) \ - AMS_SF_METHOD_INFO(C, H, 65304, Result, DisableFrozenAddress, (u64 address)) - - AMS_SF_DEFINE_INTERFACE(ICheatInterface, AMS_DMNT_I_CHEAT_INTERFACE_INTERFACE_INFO) - - } - - class CheatService final { + class CheatService { public: void HasCheatProcess(sf::Out out); void GetCheatProcessEvent(sf::OutCopyHandle out_event); diff --git a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp index 52678d88b..365f249e0 100644 --- a/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp +++ b/stratosphere/dmnt/source/cheat/impl/dmnt_cheat_api.cpp @@ -26,6 +26,58 @@ namespace ams::dmnt::cheat::impl { constexpr size_t MaxCheatCount = 0x80; constexpr size_t MaxFrozenAddressCount = 0x80; + class FrozenAddressMapEntry : public util::IntrusiveRedBlackTreeBaseNode { + public: + using LightCompareType = u64; + private: + u64 m_address; + FrozenAddressValue m_value; + public: + constexpr FrozenAddressMapEntry(u64 address, FrozenAddressValue value) : m_address(address), m_value(value) { /* ... */ } + + constexpr u64 GetAddress() const { return m_address; } + + constexpr const FrozenAddressValue &GetValue() const { return m_value; } + constexpr FrozenAddressValue &GetValue() { return m_value; } + + static constexpr ALWAYS_INLINE int Compare(const LightCompareType &lval, const FrozenAddressMapEntry &rhs) { + const auto rval = rhs.GetAddress(); + + if (lval < rval) { + return -1; + } else if (lval == rval) { + return 0; + } else { + return 1; + } + } + + static constexpr ALWAYS_INLINE int Compare(const FrozenAddressMapEntry &lhs, const FrozenAddressMapEntry &rhs) { + return Compare(lhs.GetAddress(), rhs); + } + }; + + constinit os::SdkMutex g_text_file_buffer_lock; + constinit char g_text_file_buffer[64_KB]; + + constinit u8 g_frozen_address_map_memory[sizeof(FrozenAddressMapEntry) * MaxFrozenAddressCount]; + constinit lmem::HeapHandle g_frozen_address_map_heap; + + FrozenAddressMapEntry *AllocateFrozenAddress(u64 address, FrozenAddressValue value) { + FrozenAddressMapEntry *entry = static_cast(lmem::AllocateFromUnitHeap(g_frozen_address_map_heap)); + if (entry != nullptr) { + new (entry) FrozenAddressMapEntry(address, value); + } + return entry; + } + + void DeallocateFrozenAddress(FrozenAddressMapEntry *entry) { + entry->~FrozenAddressMapEntry(); + lmem::FreeToUnitHeap(g_frozen_address_map_heap, entry); + } + + using FrozenAddressMap = typename util::IntrusiveRedBlackTreeBaseTraits::TreeType; + /* Manager class. */ class CheatProcessManager { private: @@ -48,7 +100,7 @@ namespace ams::dmnt::cheat::impl { bool always_save_cheat_toggles = false; bool should_save_cheat_toggles = false; CheatEntry cheat_entries[MaxCheatCount] = {}; - std::map frozen_addresses_map; + FrozenAddressMap frozen_addresses_map = {}; alignas(os::MemoryPageSize) u8 detect_thread_stack[ThreadStackSize] = {}; alignas(os::MemoryPageSize) u8 debug_events_thread_stack[ThreadStackSize] = {}; @@ -149,7 +201,14 @@ namespace ams::dmnt::cheat::impl { this->ResetAllCheatEntries(); /* Clear frozen addresses. */ - this->frozen_addresses_map.clear(); + { + auto it = this->frozen_addresses_map.begin(); + while (it != this->frozen_addresses_map.end()) { + FrozenAddressMapEntry *entry = std::addressof(*it); + it = this->frozen_addresses_map.erase(it); + DeallocateFrozenAddress(entry); + } + } /* Signal to our fans. */ this->cheat_process_event.Signal(); @@ -249,7 +308,11 @@ namespace ams::dmnt::cheat::impl { 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) { + for (auto &entry : this->frozen_addresses_map) { + /* Get address/value. */ + const u64 address = entry.GetAddress(); + auto &value = entry.GetValue(); + /* Map is ordered, so break when we can. */ if (address >= proc_addr + size) { break; @@ -508,7 +571,7 @@ namespace ams::dmnt::cheat::impl { R_TRY(this->EnsureCheatProcess()); - *out_count = this->frozen_addresses_map.size(); + *out_count = std::distance(this->frozen_addresses_map.begin(), this->frozen_addresses_map.end()); return ResultSuccess(); } @@ -518,14 +581,14 @@ namespace ams::dmnt::cheat::impl { R_TRY(this->EnsureCheatProcess()); u64 total_count = 0, written_count = 0; - for (auto const& [address, value] : this->frozen_addresses_map) { + for (const auto &entry : 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; + frz_addrs[written_count].address = entry.GetAddress(); + frz_addrs[written_count].value = entry.GetValue(); written_count++; } total_count++; @@ -540,11 +603,11 @@ namespace ams::dmnt::cheat::impl { R_TRY(this->EnsureCheatProcess()); - const auto it = this->frozen_addresses_map.find(address); + const auto it = this->frozen_addresses_map.find_light(address); R_UNLESS(it != this->frozen_addresses_map.end(), ResultFrozenAddressNotFound()); - frz_addr->address = it->first; - frz_addr->value = it->second; + frz_addr->address = it->GetAddress(); + frz_addr->value = it->GetValue(); return ResultSuccess(); } @@ -553,16 +616,17 @@ namespace ams::dmnt::cheat::impl { R_TRY(this->EnsureCheatProcess()); - R_UNLESS(this->frozen_addresses_map.size() < MaxFrozenAddressCount, ResultFrozenAddressOutOfResource()); - - const auto it = this->frozen_addresses_map.find(address); + const auto it = this->frozen_addresses_map.find_light(address); R_UNLESS(it == this->frozen_addresses_map.end(), ResultFrozenAddressAlreadyExists()); FrozenAddressValue value = {}; value.width = width; R_TRY(this->ReadCheatProcessMemoryUnsafe(address, &value.value, width)); - this->frozen_addresses_map[address] = value; + FrozenAddressMapEntry *entry = AllocateFrozenAddress(address, value); + R_UNLESS(entry != nullptr, ResultFrozenAddressOutOfResource()); + + this->frozen_addresses_map.insert(*entry); *out_value = value.value; return ResultSuccess(); } @@ -572,10 +636,13 @@ namespace ams::dmnt::cheat::impl { R_TRY(this->EnsureCheatProcess()); - const auto it = this->frozen_addresses_map.find(address); + const auto it = this->frozen_addresses_map.find_light(address); R_UNLESS(it != this->frozen_addresses_map.end(), ResultFrozenAddressNotFound()); + FrozenAddressMapEntry *entry = std::addressof(*it); this->frozen_addresses_map.erase(it); + DeallocateFrozenAddress(entry); + return ResultSuccess(); } @@ -659,7 +726,10 @@ namespace ams::dmnt::cheat::impl { } /* Apply frozen addresses. */ - for (auto const& [address, value] : this_ptr->frozen_addresses_map) { + for (const auto &entry : this_ptr->frozen_addresses_map) { + const auto address = entry.GetAddress(); + const auto &value = entry.GetValue(); + /* Use Write SVC directly, to avoid the usual frozen address update logic. */ svcWriteDebugProcessMemory(this_ptr->GetCheatProcessHandle(), &value.value, address, value.width); } @@ -983,22 +1053,20 @@ namespace ams::dmnt::cheat::impl { if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { return false; } - - /* Allocate cheat txt buffer. */ - char *cht_txt = static_cast(std::malloc(file_size + 1)); - if (cht_txt == nullptr) { + if (file_size < 0 || file_size >= static_cast(sizeof(g_text_file_buffer))) { return false; } - ON_SCOPE_EXIT { std::free(cht_txt); }; + + std::scoped_lock lk(g_text_file_buffer_lock); /* Read cheats into buffer. */ - if (R_FAILED(fs::ReadFile(file, 0, cht_txt, file_size))) { + if (R_FAILED(fs::ReadFile(file, 0, g_text_file_buffer, file_size))) { return false; } - cht_txt[file_size] = '\x00'; + g_text_file_buffer[file_size] = '\x00'; /* Parse cheat buffer. */ - return this->ParseCheats(cht_txt, std::strlen(cht_txt)); + return this->ParseCheats(g_text_file_buffer, std::strlen(g_text_file_buffer)); } bool CheatProcessManager::LoadCheatToggles(const ncm::ProgramId program_id) { @@ -1022,22 +1090,20 @@ namespace ams::dmnt::cheat::impl { if (R_FAILED(fs::GetFileSize(std::addressof(file_size), file))) { return false; } - - /* Allocate toggle txt buffer. */ - char *tg_txt = static_cast(std::malloc(file_size + 1)); - if (tg_txt == nullptr) { + if (file_size < 0 || file_size >= static_cast(sizeof(g_text_file_buffer))) { return false; } - ON_SCOPE_EXIT { std::free(tg_txt); }; + + std::scoped_lock lk(g_text_file_buffer_lock); /* Read cheats into buffer. */ - if (R_FAILED(fs::ReadFile(file, 0, tg_txt, file_size))) { + if (R_FAILED(fs::ReadFile(file, 0, g_text_file_buffer, file_size))) { return false; } - tg_txt[file_size] = '\x00'; + g_text_file_buffer[file_size] = '\x00'; /* Parse toggle buffer. */ - this->should_save_cheat_toggles = this->ParseCheatToggles(tg_txt, std::strlen(tg_txt)); + this->should_save_cheat_toggles = this->ParseCheatToggles(g_text_file_buffer, std::strlen(g_text_file_buffer)); return this->should_save_cheat_toggles; } @@ -1086,6 +1152,9 @@ namespace ams::dmnt::cheat::impl { /* Initialize the debug events manager (spawning its threads). */ InitializeDebugEventsManager(); + /* Initialize the frozen address map heap. */ + g_frozen_address_map_heap = lmem::CreateUnitHeap(g_frozen_address_map_memory, sizeof(g_frozen_address_map_memory), sizeof(FrozenAddressMapEntry), lmem::CreateOption_ThreadSafe); + /* Create the cheat process manager (spawning its threads). */ new (GetPointer(g_cheat_process_manager)) CheatProcessManager; } diff --git a/stratosphere/dmnt/source/dmnt_main.cpp b/stratosphere/dmnt/source/dmnt_main.cpp index ff99db916..2070a672c 100644 --- a/stratosphere/dmnt/source/dmnt_main.cpp +++ b/stratosphere/dmnt/source/dmnt_main.cpp @@ -24,14 +24,16 @@ extern "C" { u32 __nx_applet_type = AppletType_None; u32 __nx_fs_num_sessions = 1; - /* TODO: Evaluate how much this can be reduced by. */ - #define INNER_HEAP_SIZE 0x20000 + #define INNER_HEAP_SIZE 0x0 size_t nx_inner_heap_size = INNER_HEAP_SIZE; char nx_inner_heap[INNER_HEAP_SIZE]; void __libnx_initheap(void); void __appInit(void); void __appExit(void); + + void *__libnx_thread_alloc(size_t size); + void __libnx_thread_free(void *mem); } namespace ams { @@ -60,9 +62,32 @@ void __libnx_initheap(void) { fake_heap_end = (char*)addr + size; } +namespace { + + + constinit u8 g_fs_heap_memory[4_KB]; + lmem::HeapHandle g_fs_heap_handle; + + void *AllocateForFs(size_t size) { + return lmem::AllocateFromExpHeap(g_fs_heap_handle, size); + } + + void DeallocateForFs(void *p, size_t size) { + return lmem::FreeToExpHeap(g_fs_heap_handle, p); + } + + void InitializeFsHeap() { + g_fs_heap_handle = lmem::CreateExpHeap(g_fs_heap_memory, sizeof(g_fs_heap_memory), lmem::CreateOption_None); + } + +} + void __appInit(void) { hos::InitializeForStratosphere(); + InitializeFsHeap(); + fs::SetAllocator(AllocateForFs, DeallocateForFs); + sm::DoWithSession([&]() { R_ABORT_UNLESS(pmdmntInitialize()); R_ABORT_UNLESS(pminfoInitialize()); @@ -114,6 +139,8 @@ namespace { sf::hipc::ServerManager g_server_manager; + constinit sf::UnmanagedServiceObject g_cheat_service; + void LoopServerThread(void *arg) { g_server_manager.LoopProcess(); } @@ -128,6 +155,34 @@ namespace { } +namespace ams { + + void *Malloc(size_t size) { + AMS_ABORT("ams::Malloc was called"); + } + + void Free(void *ptr) { + AMS_ABORT("ams::Free was called"); + } + +} + +void *operator new(size_t size) { + AMS_ABORT("operator new(size_t) was called"); +} + +void operator delete(void *p) { + AMS_ABORT("operator delete(void *) was called"); +} + +void *__libnx_thread_alloc(size_t size) { + AMS_ABORT("__libnx_thread_alloc was called"); +} + +void __libnx_thread_free(void *mem) { + AMS_ABORT("__libnx_thread_free was called"); +} + int main(int argc, char **argv) { /* Set thread name. */ @@ -139,8 +194,8 @@ int main(int argc, char **argv) /* Create services. */ /* TODO: Implement rest of dmnt:- in ams.tma development branch. */ - /* R_ABORT_UNLESS((g_server_manager.RegisterServer(DebugMonitorServiceName, DebugMonitorMaxSessions))); */ - R_ABORT_UNLESS((g_server_manager.RegisterServer(CheatServiceName, CheatMaxSessions))); + /* TODO: register debug service */ + R_ABORT_UNLESS(g_server_manager.RegisterObjectForServer(g_cheat_service.GetShared(), CheatServiceName, CheatMaxSessions)); /* Loop forever, servicing our services. */ /* Nintendo loops four threads processing on the manager -- we'll loop an extra fifth for our cheat service. */