diff --git a/Dockerfile b/Dockerfile index cef262a..bc5df57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,12 @@ -FROM ghcr.io/wiiu-env/devkitppc:20260126 +FROM ghcr.io/wiiu-env/devkitppc:20260225 -COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20260126 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20260126 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libmappedmemory:20230621 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libwupsbackend:20240425 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libnotifications:20240426 /artifacts $DEVKITPRO -COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20250125-cb22627 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20260225 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20260225 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260208 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libmappedmemory:20260131 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libwupsbackend:20260131 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libnotifications:20260131 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20260112 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libiopshell:20260318 /artifacts $DEVKITPRO WORKDIR project diff --git a/Makefile b/Makefile index 240d89a..f6389ec 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,7 @@ CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g endif -LIBS := -lwums -lwups -lwut -lfunctionpatcher -lmappedmemory -lz -lnotifications -lbuttoncombo +LIBS := -lwums -lwups -lwut -lfunctionpatcher -lmappedmemory -lz -lnotifications -lbuttoncombo -liopshell #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level diff --git a/source/PluginManagement.cpp b/source/PluginManagement.cpp index e0689be..040b874 100644 --- a/source/PluginManagement.cpp +++ b/source/PluginManagement.cpp @@ -12,6 +12,7 @@ #include "plugin/PluginMetaInformationFactory.h" #include "plugin/RelocationData.h" #include "plugin/SectionInfo.h" +#include "plugin/TrackingPluginHeapMemoryAllocator.h" #include "utils/ElfUtils.h" #include "utils/StringTools.h" #include "utils/logger.h" @@ -42,10 +43,10 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi DisplayErrorNotificationMessage(errMsg, 15.0f); continue; } - plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData()); + plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData(), pluginDataWrapper.getHeapTrackingOptions()); } else { DEBUG_FUNCTION_LINE_INFO("LOAD (INACTIVE) %s", metaInfo->getName().c_str()); - plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData()); + plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData(), std::nullopt); } } else { auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str()); @@ -67,23 +68,18 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi bool PluginManagement::doRelocation(const std::vector &relocData, std::span trampData, - std::map &usedRPls) { + std::map &usedRPls, + const IPluginHeapMemoryAllocator &memory_allocator) { for (auto const &cur : relocData) { uint32_t functionAddress = 0; auto &functionName = cur.getName(); if (functionName == "MEMAllocFromDefaultHeap") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemory", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetAllocFunctionAddress()); } else if (functionName == "MEMAllocFromDefaultHeapEx") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemoryEx", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetAllocExFunctionAddress()); } else if (functionName == "MEMFreeToDefaultHeap") { - OSDynLoad_Module rplHandle; - OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle); - OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToMappedMemory", (void **) &functionAddress); + functionAddress = reinterpret_cast(memory_allocator.GetFreeFunctionAddress()); } if (functionAddress == 0) { @@ -160,7 +156,7 @@ bool PluginManagement::doRelocations(const std::vector &plugins DEBUG_FUNCTION_LINE_VERBOSE("Doing relocations for plugin: %s", pluginContainer.getMetaInformation().getName().c_str()); if (!PluginManagement::doRelocation(pluginContainer.getPluginLinkInformation().getRelocationDataList(), trampData, - usedRPls)) { + usedRPls, pluginContainer.getMemoryAllocator())) { return false; } } diff --git a/source/PluginManagement.h b/source/PluginManagement.h index 0b19e44..88efc9b 100644 --- a/source/PluginManagement.h +++ b/source/PluginManagement.h @@ -9,6 +9,7 @@ #include #include +class IPluginHeapMemoryAllocator; class RelocationData; class PluginLoadWrapper; class PluginContainer; @@ -25,7 +26,8 @@ public: static bool doRelocation(const std::vector &relocData, std::span trampData, - std::map &usedRPls); + std::map &usedRPls, + const IPluginHeapMemoryAllocator &); static bool DoFunctionPatches(std::vector &plugins); diff --git a/source/ShellCommands.cpp b/source/ShellCommands.cpp new file mode 100644 index 0000000..db2d1b3 --- /dev/null +++ b/source/ShellCommands.cpp @@ -0,0 +1,692 @@ +#include "ShellCommands.h" + +#include "globals.h" +#include "plugin/ButtonComboManager.h" +#include "plugin/FunctionData.h" +#include "plugin/HookData.h" +#include "plugin/PluginContainer.h" +#include "plugin/PluginData.h" +#include "plugin/SectionInfo.h" +#include "utils/StringTools.h" +#include "utils/utils.h" + +#include + +#include + +#include +#include +#include +#include + +namespace { + std::string toString(WUPSButtonCombo_ComboType type) { + switch (type) { + case WUPS_BUTTON_COMBO_COMBO_TYPE_INVALID: + return "Invalid"; + case WUPS_BUTTON_COMBO_COMBO_TYPE_HOLD: + return "Hold"; + case WUPS_BUTTON_COMBO_COMBO_TYPE_HOLD_OBSERVER: + return "Hold (Observer)"; + case WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN: + return "Press Down"; + case WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN_OBSERVER: + return "Press Down (Observer)"; + } + return "invalid"; + } + + std::string toString(WUPSButtonCombo_ComboStatus type) { + switch (type) { + case WUPS_BUTTON_COMBO_COMBO_STATUS_INVALID_STATUS: + return "Invalid"; + case WUPS_BUTTON_COMBO_COMBO_STATUS_VALID: + return "Valid"; + case WUPS_BUTTON_COMBO_COMBO_STATUS_CONFLICT: + return "Conflict"; + } + return "Invalid"; + } + + + std::string toString(const function_replacement_library_type_t type) { + switch (type) { + case LIBRARY_AVM: + return "AVM"; + case LIBRARY_CAMERA: + return "CAMERA"; + case LIBRARY_COREINIT: + return "COREINIT"; + case LIBRARY_DC: + return "DC"; + case LIBRARY_DMAE: + return "DMAE"; + case LIBRARY_DRMAPP: + return "DRMAPP"; + case LIBRARY_ERREULA: + return "ERREULA"; + case LIBRARY_GX2: + return "GX2"; + case LIBRARY_H264: + return "H264"; + case LIBRARY_LZMA920: + return "LZMA920"; + case LIBRARY_MIC: + return "MIC"; + case LIBRARY_NFC: + return "NFC"; + case LIBRARY_NIO_PROF: + return "NIO_PROF"; + case LIBRARY_NLIBCURL: + return "NLIBCURL"; + case LIBRARY_NLIBNSS: + return "NLIBNSS"; + case LIBRARY_NLIBNSS2: + return "NLIBNSS2"; + case LIBRARY_NN_AC: + return "NN_AC"; + case LIBRARY_NN_ACP: + return "NN_ACP"; + case LIBRARY_NN_ACT: + return "NN_ACT"; + case LIBRARY_NN_AOC: + return "NN_AOC"; + case LIBRARY_NN_BOSS: + return "NN_BOSS"; + case LIBRARY_NN_CCR: + return "NN_CCR"; + case LIBRARY_NN_CMPT: + return "NN_CMPT"; + case LIBRARY_NN_DLP: + return "NN_DLP"; + case LIBRARY_NN_EC: + return "NN_EC"; + case LIBRARY_NN_FP: + return "NN_FP"; + case LIBRARY_NN_HAI: + return "NN_HAI"; + case LIBRARY_NN_HPAD: + return "NN_HPAD"; + case LIBRARY_NN_IDBE: + return "NN_IDBE"; + case LIBRARY_NN_NDM: + return "NN_NDM"; + case LIBRARY_NN_NETS2: + return "NN_NETS2"; + case LIBRARY_NN_NFP: + return "NN_NFP"; + case LIBRARY_NN_NIM: + return "NN_NIM"; + case LIBRARY_NN_OLV: + return "NN_OLV"; + case LIBRARY_NN_PDM: + return "NN_PDM"; + case LIBRARY_NN_SAVE: + return "NN_SAVE"; + case LIBRARY_NN_SL: + return "NN_SL"; + case LIBRARY_NN_SPM: + return "NN_SPM"; + case LIBRARY_NN_TEMP: + return "NN_TEMP"; + case LIBRARY_NN_UDS: + return "NN_UDS"; + case LIBRARY_NN_VCTL: + return "NN_VCTL"; + case LIBRARY_NSYSCCR: + return "NSYSCCR"; + case LIBRARY_NSYSHID: + return "NSYSHID"; + case LIBRARY_NSYSKBD: + return "NSYSKBD"; + case LIBRARY_NSYSNET: + return "NSYSNET"; + case LIBRARY_NSYSUHS: + return "NSYSUHS"; + case LIBRARY_NSYSUVD: + return "NSYSUVD"; + case LIBRARY_NTAG: + return "NTAG"; + case LIBRARY_PADSCORE: + return "PADSCORE"; + case LIBRARY_PROC_UI: + return "PROC_UI"; + case LIBRARY_SND_CORE: + return "SND_CORE"; + case LIBRARY_SND_USER: + return "SND_USER"; + case LIBRARY_SNDCORE2: + return "SNDCORE2"; + case LIBRARY_SNDUSER2: + return "SNDUSER2"; + case LIBRARY_SWKBD: + return "SWKBD"; + case LIBRARY_SYSAPP: + return "SYSAPP"; + case LIBRARY_TCL: + return "TCL"; + case LIBRARY_TVE: + return "TVE"; + case LIBRARY_UAC: + return "UAC"; + case LIBRARY_UAC_RPL: + return "UAC_RPL"; + case LIBRARY_USB_MIC: + return "USB_MIC"; + case LIBRARY_UVC: + return "UVC"; + case LIBRARY_UVD: + return "UVD"; + case LIBRARY_VPAD: + return "VPAD"; + case LIBRARY_VPADBASE: + return "VPADBASE"; + case LIBRARY_ZLIB125: + return "ZLIB125"; + case LIBRARY_OTHER: + return "OTHER"; + } + return "OTHER"; + } + + const char *getButtonChar(const WUPSButtonCombo_Buttons value) { + std::string combo; + if (value & WUPS_BUTTON_COMBO_BUTTON_A) { + return "A"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_B) { + return "B"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_X) { + return "X"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_Y) { + return "Y"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_L) { + return "L"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_R) { + return "R"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_ZL) { + return "ZL"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_ZR) { + return "ZR"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_UP) { + return "UP"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_DOWN) { + return "DOWN"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_LEFT) { + return "LEFT"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_RIGHT) { + return "RIGHT"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_STICK_L) { + return "STICK-L"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_STICK_R) { + return "STICK-R"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_PLUS) { + return "PLUS"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_MINUS) { + return "MINUS"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_TV) { + return "TV"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_RESERVED_BIT) { + return "RESERVED-BIT"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_1) { + return "1"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_2) { + return "2"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_C) { + return "C"; + } + if (value & WUPS_BUTTON_COMBO_BUTTON_Z) { + return "Z"; + } + return ""; + } + + std::string getComboAsString(const uint32_t value) { + char comboString[60] = {}; + + for (uint32_t i = 0; i < 32; i++) { + uint32_t bitMask = 1 << i; + if (value & bitMask) { + auto val = getButtonChar(static_cast(bitMask)); + if (val[0] != '\0') { + strcat(comboString, val); + strcat(comboString, "+"); + } + } + } + std::string res(comboString); + if (res.ends_with("+")) { + res.pop_back(); + } + return res; + } + + std::string getMaskAsString(WUPSButtonCombo_ControllerTypes controller_mask) { + if (controller_mask == WUPS_BUTTON_COMBO_CONTROLLER_NONE) { + return "NONE"; + } + if (controller_mask == WUPS_BUTTON_COMBO_CONTROLLER_ALL) { + return "ALL"; + } + // Optional: Check strictly for full groups if you prefer that over listing individual bits + if (controller_mask == WUPS_BUTTON_COMBO_CONTROLLER_VPAD) { + return "VPAD (All)"; + } + if (controller_mask == WUPS_BUTTON_COMBO_CONTROLLER_WPAD) { + return "WPAD (All)"; + } + std::vector parts; + + // VPADs + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_VPAD_0) parts.emplace_back("VPAD_0"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_VPAD_1) parts.emplace_back("VPAD_1"); + + // WPADs + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_0) parts.emplace_back("WPAD_0"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_1) parts.emplace_back("WPAD_1"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_2) parts.emplace_back("WPAD_2"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_3) parts.emplace_back("WPAD_3"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_4) parts.emplace_back("WPAD_4"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_5) parts.emplace_back("WPAD_5"); + if (controller_mask & WUPS_BUTTON_COMBO_CONTROLLER_WPAD_6) parts.emplace_back("WPAD_6"); + + + if (parts.empty()) { + return "UNKNOWN"; + } + + std::string result; + for (size_t i = 0; i < parts.size(); ++i) { + if (i > 0) { + result += " | "; + } + result += parts[i]; + } + + return result; + } + + bool isHoldCombo(WUPSButtonCombo_ComboType type) { + switch (type) { + case WUPS_BUTTON_COMBO_COMBO_TYPE_HOLD: + case WUPS_BUTTON_COMBO_COMBO_TYPE_HOLD_OBSERVER: + return true; + case WUPS_BUTTON_COMBO_COMBO_TYPE_INVALID: + case WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN: + case WUPS_BUTTON_COMBO_COMBO_TYPE_PRESS_DOWN_OBSERVER: + return false; + } + return false; + } +} // namespace +namespace ShellCommands { + namespace { + class ConsoleTable { + public: + enum Alignment { LEFT, + RIGHT }; + + void AddColumn(const std::string &title, const Alignment align = LEFT) { + mCols.push_back({title, align, title.length()}); + } + + void AddRow(const std::vector &rowData) { + if (rowData.size() != mCols.size()) return; + mRows.push_back(rowData); + updateWidths(rowData); + } + + void AddFooter(const std::vector &footerData) { + if (footerData.size() != mCols.size()) return; + mFooter = footerData; + updateWidths(footerData); + } + + void Print() { + OSReport("\n"); + PrintSeparator(); + + OSReport("|"); + for (const auto &[title, align, width] : mCols) printCell(title, width, align); + OSReport("\n"); + + PrintSeparator(); + + for (const auto &row : mRows) { + OSReport("|"); + for (size_t i = 0; i < row.size(); ++i) printCell(row[i], mCols[i].width, mCols[i].align); + OSReport("\n"); + } + + if (!mFooter.empty()) { + PrintSeparator(); + OSReport("|"); + for (size_t i = 0; i < mFooter.size(); ++i) printCell(mFooter[i], mCols[i].width, mCols[i].align); + OSReport("\n"); + } + + size_t totalWidth = 1; + for (const auto &col : mCols) { + totalWidth += col.width + 3; + } + + std::string border(totalWidth, '-'); + OSReport("%s\n", border.c_str()); + } + + private: + void updateWidths(const std::vector &data) { + for (size_t i = 0; i < mCols.size(); ++i) { + if (data[i].length() > mCols[i].width) { + mCols[i].width = data[i].length(); + } + } + } + + void PrintSeparator() const { + OSReport("|"); + for (const auto &col : mCols) { + // Separator is width + 2 spaces padding + std::string line(col.width + 2, '-'); + OSReport("%s|", line.c_str()); + } + OSReport("\n"); + } + + static void printCell(const std::string &text, const size_t width, const Alignment align) { + if (align == LEFT) OSReport(" %-*s |", static_cast(width), text.c_str()); + else + OSReport(" %*s |", static_cast(width), text.c_str()); + } + + struct Column { + std::string title; + Alignment align; + size_t width; + }; + + std::vector mCols; + std::vector> mRows; + std::vector mFooter; + }; + } // namespace + + std::string BytesToHumanReadable(uint32_t bytes) { + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%.2f KiB", bytes / 1024.0f); + return std::string(buffer); + } + + void PrintHeapUsage() { + ConsoleTable table; + + table.AddColumn("Plugin name", ConsoleTable::LEFT); + table.AddColumn("Current usage", ConsoleTable::RIGHT); + table.AddColumn("Peak usage", ConsoleTable::RIGHT); + table.AddColumn("Current allocations", ConsoleTable::RIGHT); + table.AddColumn("Total allocations", ConsoleTable::RIGHT); + table.AddColumn("Total frees", ConsoleTable::RIGHT); + + uint32_t totalCurrentBytes = 0; + uint32_t totalPeakBytes = 0; + uint32_t totalChunks = 0; + + for (const auto &plugin : gLoadedPlugins) { + if (!plugin.isLinkedAndLoaded() || !plugin.isUsingTrackingPluginHeapMemoryAllocator()) { + continue; + } + + const auto tracking = plugin.getTrackingMemoryAllocator(); + if (!tracking) { continue; } + + const auto stats = tracking->GetHeapMemoryUsageSnapshot(); + if (!stats) { continue; } + + auto activeChunks = stats->allocationMap.size(); + + table.AddRow({stats->pluginName, + BytesToHumanReadable(stats->currentAllocated), + BytesToHumanReadable(stats->peakAllocated), + std::to_string(activeChunks), + std::to_string(stats->allocCount), + std::to_string(stats->freeCount)}); + + // Accumulate Totals + totalCurrentBytes += stats->currentAllocated; + totalPeakBytes += stats->peakAllocated; + totalChunks += activeChunks; + } + + // Add the Footer Row + table.AddFooter({"TOTAL (tracked plugins)", + BytesToHumanReadable(totalCurrentBytes), + BytesToHumanReadable(totalPeakBytes), + std::to_string(totalChunks), + "-", + "-"}); + + table.Print(); + } + + void ListPlugins(int argc, char **argv) { + bool showAll = false; + if (argc > 1 && (std::string_view(argv[1]) == "-a" || std::string_view(argv[1]) == "--all")) { + showAll = true; + } + + ConsoleTable table; + + table.AddColumn("Index", ConsoleTable::LEFT); + table.AddColumn("Name", ConsoleTable::LEFT); + table.AddColumn("Author", ConsoleTable::LEFT); + table.AddColumn("Version", ConsoleTable::LEFT); + if (showAll) { + table.AddColumn("Active", ConsoleTable::LEFT); + } + table.AddColumn("API", ConsoleTable::LEFT); + table.AddColumn("Memory footprint", ConsoleTable::RIGHT); + table.AddColumn("Heap usage", ConsoleTable::RIGHT); + uint32_t totalSizeOther = 0; + uint32_t id = 0; + for (const auto &plugin : gLoadedPlugins) { + if (!showAll && !plugin.isLinkedAndLoaded()) { + totalSizeOther += plugin.getMemoryFootprint(); + id++; + continue; + } + + auto &meta = plugin.getMetaInformation(); + + std::string heapUsage = "unknown"; + if (const auto tracking = plugin.getTrackingMemoryAllocator()) { + uint32_t heapUsageSize = 0; + if (const auto stats = tracking->GetHeapMemoryUsageSnapshot()) { + heapUsageSize = stats->currentAllocated; + } + heapUsage = BytesToHumanReadable(heapUsageSize); + } + + std::vector rowData; + rowData.emplace_back(std::to_string(id)); + rowData.emplace_back(meta.getName()); + rowData.emplace_back(meta.getAuthor()); + rowData.emplace_back(meta.getVersion()); + if (showAll) { + rowData.emplace_back(plugin.isLinkedAndLoaded() ? "Yes" : "No"); + } + rowData.emplace_back(meta.getWUPSVersion().toString()); + rowData.emplace_back("~ " + BytesToHumanReadable(plugin.getMemoryFootprint())); + rowData.emplace_back(heapUsage); + + table.AddRow(rowData); + id++; + } + + if (totalSizeOther > 0 && !showAll) { + std::vector rowData; + rowData.emplace_back("Inactive Plugins"); + rowData.emplace_back("-"); + rowData.emplace_back("-"); + + rowData.emplace_back("-"); + rowData.emplace_back("~ " + BytesToHumanReadable(totalSizeOther)); + rowData.emplace_back("-"); + + table.AddFooter(rowData); + } + + table.Print(); + } + + void PluginDetails(std::optional idOpt) { + uint32_t id = 0; + for (const auto &plugin : gLoadedPlugins) { + if (idOpt) { + if (*idOpt != id) { + id++; + continue; + } + } + auto &meta = plugin.getMetaInformation(); + OSReport("\n"); + OSReport("Index: %d\n", id); + OSReport("Name: %s\n", meta.getName().c_str()); + OSReport("Description: %s\n", meta.getDescription().c_str()); + OSReport("Author: %s\n", meta.getAuthor().c_str()); + OSReport("Version: %s\n", meta.getVersion().c_str()); + OSReport("Build date: %s\n", meta.getBuildTimestamp().c_str()); + if (!meta.getStorageId().empty()) { + OSReport("Storage Id: %s\n", meta.getStorageId().c_str()); + } + OSReport("Active: %s\n", plugin.isLinkedAndLoaded() ? "Yes" : "No"); + OSReport("API: %s\n", meta.getWUPSVersion().toString().c_str()); + + const auto memoryFootprint = plugin.getMemoryFootprint(); + const auto pluginSize = plugin.getPluginDataCopy()->getBuffer().size_bytes(); + size_t textSize = 0; + size_t dataSize = 0; + + OSReport("Memory footprint: ~%s\n", BytesToHumanReadable(memoryFootprint).c_str()); + OSReport("\t- .wps: ~%s\n", BytesToHumanReadable(pluginSize).c_str()); + if (plugin.isLinkedAndLoaded()) { + textSize = plugin.getPluginLinkInformation().getTextMemory().size(); + dataSize = plugin.getPluginLinkInformation().getDataMemory().size(); + OSReport("\t- CODE: ~%s\n", BytesToHumanReadable(textSize).c_str()); + OSReport("\t- DATA: ~%s\n", BytesToHumanReadable(dataSize).c_str()); + } + const auto otherSize = memoryFootprint - pluginSize - textSize - dataSize; + OSReport("\t- Other: ~%s\n", BytesToHumanReadable(otherSize).c_str()); + OSReport("\n"); + if (plugin.isLinkedAndLoaded()) { + const auto §ionInfoList = plugin.getPluginLinkInformation().getSectionInfoList(); + OSReport("Sections: %d\n", sectionInfoList.size()); + for (const auto §ionInfo : sectionInfoList) { + OSReport("\t- 0x%08X - 0x%08X %-15s %-11s\n", sectionInfo.second.getAddress(), sectionInfo.second.getAddress() + sectionInfo.second.getSize(), sectionInfo.first.c_str(), BytesToHumanReadable(sectionInfo.second.getSize()).c_str()); + } + OSReport("\n"); + + const auto &hookList = plugin.getPluginLinkInformation().getHookDataList(); + OSReport("WUPS Hooks: %d\n", hookList.size()); + for (const auto &hook : hookList) { + OSReport("\t- %p - %s\n", hook.getFunctionPointer(), hookNameToString(hook.getType()).c_str()); + } + OSReport("\n"); + const auto &buttonCombos = plugin.GetButtonComboData(); + OSReport("Button combos: %d\n", buttonCombos.size()); + for (const auto &combo : buttonCombos) { + OSReport("\t- \"%s\" \n", combo.label.c_str()); + OSReport("\t\tStatus: %s\n", toString(combo.status).c_str()); + OSReport("\t\tCallback: %p (%s)\n", combo.callbackOptions.callback, combo.callbackOptions.callback != nullptr ? getModuleAndSymbolName(reinterpret_cast(combo.callbackOptions.callback)).c_str() : ""); + OSReport("\t\tContext: %p\n", combo.callbackOptions.context); + OSReport("\t\tType: %s\n", toString(combo.buttonComboOptions.type).c_str()); + if (isHoldCombo(combo.buttonComboOptions.type)) { + OSReport("\t\tHold duration: %d\n", combo.buttonComboOptions.optionalHoldForXMs); + } + OSReport("\t\tCombo: %s\n", getComboAsString(combo.buttonComboOptions.basicCombo.combo).c_str()); + OSReport("\t\tController Mask: %s\n", getMaskAsString(combo.buttonComboOptions.basicCombo.controllerMask).c_str()); + } + OSReport("\n"); + const auto &functionPatches = plugin.getPluginLinkInformation().getFunctionDataList(); + OSReport("Function patches: %d\n", functionPatches.size()); + for (const auto &function : functionPatches) { + if (function.getPhysicalAddress() != nullptr) { + OSReport("\t- Hook: %p - PA: %p VA: %p\n", function.getReplaceAddress(), function.getPhysicalAddress(), function.getVirtualAddress()); + } else { + OSReport("\t- Hook: %p - %-9s - %s\n", function.getReplaceAddress(), toString(function.getLibrary()).c_str(), function.getName().c_str()); + } + } + + OSReport("\n"); + OSReport("Heap usage:\n"); + if (const auto tracking = plugin.getTrackingMemoryAllocator(); tracking != nullptr) { + if (const auto stats = tracking->GetHeapMemoryUsageSnapshot(); stats) { + OSReport("\t- Currently allocated: %s\n", BytesToHumanReadable(stats->currentAllocated).c_str()); + OSReport("\t- Peak allocated: %s\n", BytesToHumanReadable(stats->peakAllocated).c_str()); + OSReport("\t- Current allocations: %d\n", stats->allocationMap.size()); + OSReport("\t- Total allocations: %d\n", stats->allocCount); + OSReport("\t- Total frees: %d\n", stats->freeCount); + } + } else { + OSReport("\t Not tracked.\n"); + } + } + + OSReport("\n=================\n"); + + id++; + if (idOpt) { + break; + } + } + } + + static std::unique_ptr sPluginsGroup; + void InitPluginsCommandGroup() { + // Must be in memory while commands are active + sPluginsGroup = std::make_unique("plugins", "Manage aroma plugins"); + if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res)); + } + + if (const auto res = sPluginsGroup->AddCommand("heap_usage", PrintHeapUsage, "Show current heap usage for tracked plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins heap_usage\" command: %s", IOPShellModule::GetErrorString(res)); + } + if (const auto res = sPluginsGroup->AddRawCommand("list", ListPlugins, "Lists active plugins", "Usage: \"list -a\" to list all plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins list\" command: %s", IOPShellModule::GetErrorString(res)); + } + if (auto res = sPluginsGroup->AddCommand("details", PluginDetails, "Shows details for plugins"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" command: %s", IOPShellModule::GetErrorString(res)); + } else { + if (res = sPluginsGroup->AddAlias("details", "show"); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"aroma plugins details\" alias \"aroma plugins show\": %s", IOPShellModule::GetErrorString(res)); + } + } + + if (const auto res = sPluginsGroup->RegisterGroup(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to register \"aroma plugins\" command: %s", IOPShellModule::GetErrorString(res)); + } + } + + void Init() { + InitPluginsCommandGroup(); + } +} // namespace ShellCommands \ No newline at end of file diff --git a/source/ShellCommands.h b/source/ShellCommands.h new file mode 100644 index 0000000..ed0d464 --- /dev/null +++ b/source/ShellCommands.h @@ -0,0 +1,5 @@ +#pragma once + +namespace ShellCommands { + void Init(); +} diff --git a/source/hooks.cpp b/source/hooks.cpp index b4479e1..cf73d32 100644 --- a/source/hooks.cpp +++ b/source/hooks.cpp @@ -8,6 +8,7 @@ #include "utils/buttoncombo/ButtonComboUtils.h" #include "utils/logger.h" #include "utils/storage/StorageUtils.h" +#include "utils/utils.h" #include #include @@ -15,45 +16,13 @@ #include -static const char **hook_names = (const char *[]){ - "WUPS_LOADER_HOOK_INIT_WUT_MALLOC", - "WUPS_LOADER_HOOK_FINI_WUT_MALLOC", - "WUPS_LOADER_HOOK_INIT_WUT_NEWLIB", - "WUPS_LOADER_HOOK_FINI_WUT_NEWLIB", - "WUPS_LOADER_HOOK_INIT_WUT_STDCPP", - "WUPS_LOADER_HOOK_FINI_WUT_STDCPP", - "WUPS_LOADER_HOOK_INIT_WUT_DEVOPTAB", - "WUPS_LOADER_HOOK_FINI_WUT_DEVOPTAB", - "WUPS_LOADER_HOOK_INIT_WUT_SOCKETS", - "WUPS_LOADER_HOOK_FINI_WUT_SOCKETS", - - "WUPS_LOADER_HOOK_INIT_WRAPPER", - "WUPS_LOADER_HOOK_FINI_WRAPPER", - - "WUPS_LOADER_HOOK_GET_CONFIG_DEPRECATED", - "WUPS_LOADER_HOOK_CONFIG_CLOSED_DEPRECATED", - - "WUPS_LOADER_HOOK_INIT_STORAGE_DEPRECATED", - - "WUPS_LOADER_HOOK_INIT_PLUGIN", - "WUPS_LOADER_HOOK_DEINIT_PLUGIN", - "WUPS_LOADER_HOOK_APPLICATION_STARTS", - "WUPS_LOADER_HOOK_RELEASE_FOREGROUND", - "WUPS_LOADER_HOOK_ACQUIRED_FOREGROUND", - "WUPS_LOADER_HOOK_APPLICATION_REQUESTS_EXIT", - "WUPS_LOADER_HOOK_APPLICATION_ENDS", - "WUPS_LOADER_HOOK_INIT_STORAGE", - "WUPS_LOADER_HOOK_INIT_CONFIG", - "WUPS_LOADER_HOOK_INIT_BUTTON_COMBO", - "WUPS_LOADER_HOOK_INIT_WUT_THREAD", -}; void CallHook(const std::vector &plugins, const wups_loader_hook_type_t hook_type) { CallHook(plugins, hook_type, [](const auto &) { return true; }); } void CallHook(const std::vector &plugins, const wups_loader_hook_type_t hook_type, const std::function &pred) { - DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d]", hook_names[hook_type], hook_type); + DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d]", hookNameToString(hook.getType()).c_str(), hook_type); for (const auto &plugin : plugins) { if (pred(plugin)) { CallHook(plugin, hook_type); @@ -67,7 +36,7 @@ void CallHook(const PluginContainer &plugin, const wups_loader_hook_type_t hook_ } for (const auto &hook : plugin.getPluginLinkInformation().getHookDataList()) { if (hook.getType() == hook_type) { - DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s for plugin %s [%d]", hook_names[hook.getType()], plugin.getMetaInformation().getName().c_str(), hook_type); + DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s for plugin %s [%d]", hookNameToString(hook.getType()).c_str(), plugin.getMetaInformation().getName().c_str(), hook_type); void *func_ptr = hook.getFunctionPointer(); if (func_ptr != nullptr) { switch (hook_type) { @@ -172,7 +141,7 @@ void CallHook(const PluginContainer &plugin, const wups_loader_hook_type_t hook_ } default: { DEBUG_FUNCTION_LINE_ERR("######################################"); - DEBUG_FUNCTION_LINE_ERR("Hook is not implemented %s [%d]", hook_names[hook_type], hook_type); + DEBUG_FUNCTION_LINE_ERR("Hook is not implemented %s [%d]", hookNameToString(hook_type).c_str(), hook_type); DEBUG_FUNCTION_LINE_ERR("######################################"); } } diff --git a/source/main.cpp b/source/main.cpp index e1fa5d7..1d2d68e 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,4 +1,5 @@ #include "PluginManagement.h" +#include "ShellCommands.h" #include "globals.h" #include "hooks.h" #include "patcher/hooks_patcher_static.h" @@ -20,6 +21,7 @@ #include #include +#include #include #include @@ -39,6 +41,9 @@ WUMS_DEPENDS_ON(homebrew_memorymapping); WUMS_DEPENDS_ON(homebrew_notifications); WUMS_DEPENDS_ON(homebrew_buttoncombo); +// This should be a soft dependency +//WUMS_DEPENDS_ON(homebrew_iopshell); + using namespace std::chrono_literals; WUMS_INITIALIZE() { @@ -59,6 +64,12 @@ WUMS_INITIALIZE() { gNotificationModuleLoaded = true; } + if (const auto res = IOPShellModule::Init(); res != IOPSHELL_MODULE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to init IOPShellModule: %s (%d)", IOPShellModule::GetErrorString(res), res); + } else { + ShellCommands::Init(); + } + DEBUG_FUNCTION_LINE("Patching functions"); for (uint32_t i = 0; i < method_hooks_static_size; i++) { if (FunctionPatcher_AddFunctionPatch(&method_hooks_static[i], nullptr, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { @@ -240,26 +251,31 @@ WUMS_APPLICATION_STARTS() { // Check if we want to link a plugin that's currently unloaded // E.g. if you disable a plugin from the config menu and then wiiload it, the disabled plugin copy should be unloaded for (const auto &pluginLoadWrapper : gLoadOnNextLaunch) { + // If a plugin is not enabled... if (!pluginLoadWrapper.isLoadAndLink()) { const auto unloadedMetaInfoIt = pluginMetaInformationCache.find(pluginLoadWrapper.getPluginData()->getHandle()); if (unloadedMetaInfoIt == pluginMetaInformationCache.end()) { DEBUG_FUNCTION_LINE_WARN("Failed to find meta information for plugin data handle %08X", pluginLoadWrapper.getPluginData()->getHandle()); continue; } - if (const auto it = std::ranges::find_if(gLoadOnNextLaunch, [&pluginLoadWrapper, &pluginMetaInformationCache, &unloadedMetaInfoIt](const PluginLoadWrapper &plugin) { - const bool differentPluginData = plugin.getPluginData()->getHandle() != pluginLoadWrapper.getPluginData()->getHandle(); - const bool otherWillBeLinked = plugin.isLoadAndLink(); - bool sameAuthorAndName = false; - if (const auto otherMetaInfoIt = pluginMetaInformationCache.find(plugin.getPluginData()->getHandle()); otherMetaInfoIt != pluginMetaInformationCache.end()) { - const auto &otherMetaInfo = otherMetaInfoIt->second; - const auto &unloadedMetaInfo = unloadedMetaInfoIt->second; - sameAuthorAndName = otherMetaInfo == unloadedMetaInfo; - } - return differentPluginData && otherWillBeLinked && sameAuthorAndName; - }); + // Check if there is another plugin which will be loaded and has the same author/name + const auto isPluginWithSameMetaLoaded = [&pluginLoadWrapper, &pluginMetaInformationCache, &unloadedMetaInfoIt](const PluginLoadWrapper &plugin) { + const bool differentPluginData = plugin.getPluginData()->getHandle() != pluginLoadWrapper.getPluginData()->getHandle(); + const bool otherWillBeLinked = plugin.isLoadAndLink(); + bool sameAuthorAndName = false; + if (const auto otherMetaInfoIt = pluginMetaInformationCache.find(plugin.getPluginData()->getHandle()); otherMetaInfoIt != pluginMetaInformationCache.end()) { + const auto &otherMetaInfo = otherMetaInfoIt->second; + const auto &unloadedMetaInfo = unloadedMetaInfoIt->second; + sameAuthorAndName = otherMetaInfo == unloadedMetaInfo; + } + return differentPluginData && otherWillBeLinked && sameAuthorAndName; + }; + if (const auto it = std::ranges::find_if(gLoadOnNextLaunch, isPluginWithSameMetaLoaded); it != gLoadOnNextLaunch.end()) { + // If yes, we want to drop the currently unloaded one, so lets NOT add it to the list continue; } + // otherwise add it to the list to keep it added. } filteredLoadOnNextLaunch.push_back(pluginLoadWrapper); } @@ -268,8 +284,11 @@ WUMS_APPLICATION_STARTS() { // Check which plugins are already loaded and which needs to be for (const auto &pluginLoadWrapper : filteredLoadOnNextLaunch) { const auto &pluginNeedsNoReloadFn = [&pluginLoadWrapper](const PluginContainer &container) { + bool willBeLinked = pluginLoadWrapper.isLoadAndLink(); return (container.getPluginDataCopy()->getHandle() == pluginLoadWrapper.getPluginData()->getHandle()) && - (container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink()); + (container.isLinkedAndLoaded() == pluginLoadWrapper.isLoadAndLink()) && + // Only check if tracking is enabled if the plugin will actually be loaded. + (!willBeLinked || (container.isUsingTrackingPluginHeapMemoryAllocator() == pluginLoadWrapper.isHeapTrackingEnabled())); }; // Check if the plugin data is already loaded if (const auto it = std::ranges::find_if(gLoadedPlugins, pluginNeedsNoReloadFn); @@ -372,6 +391,21 @@ void CleanupPlugins(std::vector &pluginsToDeinit) { } plugin.DeinitButtonComboData(); } + + // Check for leaked memory + for (auto &plugin : pluginsToDeinit) { + if (const auto tracking = plugin.getTrackingMemoryAllocator()) { + if (const auto stats = tracking->GetHeapMemoryUsageSnapshot()) { + DEBUG_FUNCTION_LINE_WARN("Plugin \"%s\" leaked %d allocations", plugin.getMetaInformation().getName().c_str(), stats->allocationMap.size()); + for (const auto &alloc : stats->allocationMap) { + DEBUG_FUNCTION_LINE_WARN("Leaked %d bytes @ %p", alloc.second.size, alloc.first); + if (!alloc.second.stackTrace.empty()) { + PrintCapturedStackTrace(alloc.second.stackTrace); + } + } + } + } + } } void CheckCleanupCallbackUsage(const std::vector &plugins) { auto *curThread = OSGetCurrentThread(); diff --git a/source/plugin/ButtonComboManager.cpp b/source/plugin/ButtonComboManager.cpp index e2134e6..a73aead 100644 --- a/source/plugin/ButtonComboManager.cpp +++ b/source/plugin/ButtonComboManager.cpp @@ -598,4 +598,28 @@ WUPSButtonCombo_Error ButtonComboManager::ExecuteForWrapper(const WUPSButtonComb uint32_t ButtonComboManager::getHandle() const { return *mHandle; +} + +std::vector ButtonComboManager::GetButtonCombos() const { + std::vector result; + for (auto &wrapper : mComboWrappers) { + ButtonComboInfo res; + char text[256] = {}; + WUPSButtonCombo_MetaOptionsOut meta = {text, sizeof(text)}; + if (wrapper.GetButtonComboCallback(res.callbackOptions) != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + continue; + } + if (wrapper.GetButtonComboInfoEx(res.buttonComboOptions) != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + continue; + } + if (wrapper.GetButtonComboMeta(meta) != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + continue; + } + if (wrapper.GetButtonComboStatus(res.status) != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { + continue; + } + res.label = text; + result.push_back(res); + } + return result; } \ No newline at end of file diff --git a/source/plugin/ButtonComboManager.h b/source/plugin/ButtonComboManager.h index ceb3b3f..21813f2 100644 --- a/source/plugin/ButtonComboManager.h +++ b/source/plugin/ButtonComboManager.h @@ -10,12 +10,18 @@ class ButtonComboWrapper; +struct ButtonComboInfo { + std::string label; + WUPSButtonCombo_CallbackOptions callbackOptions; + WUPSButtonCombo_ButtonComboInfoEx buttonComboOptions; + WUPSButtonCombo_ComboStatus status; +}; + class ButtonComboManager { public: explicit ButtonComboManager(); ~ButtonComboManager(); - ButtonComboManager(const ButtonComboManager &) = delete; ButtonComboManager(ButtonComboManager &&src) noexcept; @@ -67,6 +73,8 @@ public: [[nodiscard]] uint32_t getHandle() const; + [[nodiscard]] std::vector GetButtonCombos() const; + private: std::forward_list mComboWrappers; std::unique_ptr mHandle = std::make_unique(); diff --git a/source/plugin/DefaultPluginHeapMemoryAllocator.cpp b/source/plugin/DefaultPluginHeapMemoryAllocator.cpp new file mode 100644 index 0000000..dbd096d --- /dev/null +++ b/source/plugin/DefaultPluginHeapMemoryAllocator.cpp @@ -0,0 +1,44 @@ +#include "DefaultPluginHeapMemoryAllocator.h" + +#include "utils/logger.h" + +#include + +DefaultPluginHeapMemoryAllocator DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator; + +DefaultPluginHeapMemoryAllocator::DefaultPluginHeapMemoryAllocator() { + OSDynLoad_Module rplHandle; + if (OSDynLoad_Acquire("homebrew_memorymapping", &rplHandle) == OS_DYNLOAD_OK) { + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemory", reinterpret_cast(&mMemoryMappingModuleAllocPtr)); + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemoryEx", reinterpret_cast(&mMemoryMappingModuleAllocExPtr)); + OSDynLoad_FindExport(rplHandle, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToMappedMemory", reinterpret_cast(&mMemoryMappingModuleFreePtr)); + } +} + +MEMAllocFromDefaultHeapFn *DefaultPluginHeapMemoryAllocator::GetAllocFunctionAddress() const { + if (mMemoryMappingModuleAllocPtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleAllocPtr was NULL!"); + return &MEMAllocFromDefaultHeap; + } + return mMemoryMappingModuleAllocPtr; +} + +MEMAllocFromDefaultHeapExFn *DefaultPluginHeapMemoryAllocator::GetAllocExFunctionAddress() const { + if (mMemoryMappingModuleAllocExPtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleAllocExPtr was NULL!"); + return &MEMAllocFromDefaultHeapEx; + } + return mMemoryMappingModuleAllocExPtr; +} + +MEMFreeToDefaultHeapFn *DefaultPluginHeapMemoryAllocator::GetFreeFunctionAddress() const { + if (mMemoryMappingModuleFreePtr == nullptr) { + DEBUG_FUNCTION_LINE_WARN("### mMemoryMappingModuleFreePtr was NULL!"); + return &MEMFreeToDefaultHeap; + } + return mMemoryMappingModuleFreePtr; +} + +std::optional DefaultPluginHeapMemoryAllocator::GetHeapMemoryUsageSnapshot() const { + return {}; +} \ No newline at end of file diff --git a/source/plugin/DefaultPluginHeapMemoryAllocator.h b/source/plugin/DefaultPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..3a5dc4e --- /dev/null +++ b/source/plugin/DefaultPluginHeapMemoryAllocator.h @@ -0,0 +1,20 @@ +#pragma once +#include "IPluginHeapMemoryAllocator.h" + +class DefaultPluginHeapMemoryAllocator : public IPluginHeapMemoryAllocator { +public: + ~DefaultPluginHeapMemoryAllocator() override = default; + MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const override; + MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const override; + MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const override; + std::optional GetHeapMemoryUsageSnapshot() const override; + + static DefaultPluginHeapMemoryAllocator gDefaultPluginHeapMemoryAllocator; + +private: + DefaultPluginHeapMemoryAllocator(); + + MEMAllocFromDefaultHeapFn *mMemoryMappingModuleAllocPtr = nullptr; + MEMAllocFromDefaultHeapExFn *mMemoryMappingModuleAllocExPtr = nullptr; + MEMFreeToDefaultHeapFn *mMemoryMappingModuleFreePtr = nullptr; +}; diff --git a/source/plugin/FunctionData.cpp b/source/plugin/FunctionData.cpp index b1bb932..984297c 100644 --- a/source/plugin/FunctionData.cpp +++ b/source/plugin/FunctionData.cpp @@ -92,3 +92,10 @@ bool FunctionData::RemovePatch() { return true; } + +size_t FunctionData::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mName.capacity(); + return totalSize; +} diff --git a/source/plugin/FunctionData.h b/source/plugin/FunctionData.h index 277999d..33ab2e4 100644 --- a/source/plugin/FunctionData.h +++ b/source/plugin/FunctionData.h @@ -53,6 +53,8 @@ public: bool RemovePatch(); private: + [[nodiscard]] size_t getMemoryFootprint() const; + void *mPAddress = nullptr; void *mVAddress = nullptr; std::string mName; @@ -62,4 +64,6 @@ private: void *mReplaceCall = nullptr; PatchedFunctionHandle mHandle = 0; + + friend class PluginLinkInformation; }; diff --git a/source/plugin/FunctionSymbolData.cpp b/source/plugin/FunctionSymbolData.cpp index 1692bf0..6728e8e 100644 --- a/source/plugin/FunctionSymbolData.cpp +++ b/source/plugin/FunctionSymbolData.cpp @@ -23,4 +23,10 @@ FunctionSymbolData::~FunctionSymbolData() = default; [[nodiscard]] uint32_t FunctionSymbolData::getSize() const { return mSize; +} + +size_t FunctionSymbolData::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + totalSize += mName.capacity(); + return totalSize; } \ No newline at end of file diff --git a/source/plugin/FunctionSymbolData.h b/source/plugin/FunctionSymbolData.h index 6daa3d9..61d0d7f 100644 --- a/source/plugin/FunctionSymbolData.h +++ b/source/plugin/FunctionSymbolData.h @@ -40,7 +40,11 @@ public: [[nodiscard]] uint32_t getSize() const; private: + [[nodiscard]] size_t getMemoryFootprint() const; + std::string mName; void *mAddress; uint32_t mSize; + + friend class PluginLinkInformation; }; \ No newline at end of file diff --git a/source/plugin/IPluginHeapMemoryAllocator.h b/source/plugin/IPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..d332885 --- /dev/null +++ b/source/plugin/IPluginHeapMemoryAllocator.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +struct AllocationInfo { + uint32_t size; + std::vector stackTrace; +}; + +struct PluginMemorySnapshot { + uint32_t currentAllocated = 0; + uint32_t peakAllocated = 0; + uint32_t allocCount = 0; + uint32_t freeCount = 0; + std::string pluginName = {}; + + std::map allocationMap; +}; + +class IPluginHeapMemoryAllocator { +public: + virtual ~IPluginHeapMemoryAllocator() = default; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const = 0; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const = 0; + + /** + * The returned address needs to be valid for the whole time + */ + virtual MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const = 0; + + virtual std::optional GetHeapMemoryUsageSnapshot() const = 0; +}; \ No newline at end of file diff --git a/source/plugin/PluginConfigData.cpp b/source/plugin/PluginConfigData.cpp index 6762a02..4732cc5 100644 --- a/source/plugin/PluginConfigData.cpp +++ b/source/plugin/PluginConfigData.cpp @@ -35,6 +35,14 @@ WUPSConfigAPIStatus PluginConfigData::CallMenuClosedCallback() const { return WUPSCONFIG_API_RESULT_SUCCESS; } +size_t PluginConfigData::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mName.capacity() * sizeof(char); + + return totalSize; +} + std::optional PluginConfigData::create(const WUPSConfigAPIOptions options, const WUPSConfigAPI_MenuOpenedCallback openedCallback, const WUPSConfigAPI_MenuClosedCallback closedCallback) { diff --git a/source/plugin/PluginConfigData.h b/source/plugin/PluginConfigData.h index 1e21290..459fb89 100644 --- a/source/plugin/PluginConfigData.h +++ b/source/plugin/PluginConfigData.h @@ -21,7 +21,10 @@ public: static std::optional create(WUPSConfigAPIOptions options, WUPSConfigAPI_MenuOpenedCallback openedCallback, WUPSConfigAPI_MenuClosedCallback closedCallback); private: + [[nodiscard]] size_t getMemoryFootprint() const; + std::string mName; WUPSConfigAPI_MenuOpenedCallback mOpenedCallback; WUPSConfigAPI_MenuClosedCallback mClosedCallback; + friend class PluginContainer; }; diff --git a/source/plugin/PluginContainer.cpp b/source/plugin/PluginContainer.cpp index 7dc0951..3c83dfb 100644 --- a/source/plugin/PluginContainer.cpp +++ b/source/plugin/PluginContainer.cpp @@ -1,4 +1,6 @@ #include "PluginContainer.h" + +#include "plugin/ButtonComboManager.h" #include "plugin/FunctionData.h" #include "plugin/HookData.h" #include "plugin/PluginConfigData.h" @@ -6,21 +8,34 @@ #include "plugin/PluginLinkInformation.h" #include "plugin/RelocationData.h" #include "plugin/SectionInfo.h" + +#include "plugin/ButtonComboManager.h" #include "utils/buttoncombo/ButtonComboUtils.h" +#include "utils/logger.h" #include "utils/storage/StorageUtils.h" #include -PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData) +PluginContainer::PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData, std::optional heapTrackingOptions) : mMetaInformation(std::move(metaInformation)), mPluginLinkInformation(std::move(pluginLinkInformation)), mPluginData(std::move(pluginData)) { // Abuse this as a stable handle that references itself and survives std::move *mHandle = reinterpret_cast(mHandle.get()); + + auto trackingOptions = mMetaInformation.getHeapTrackingOptions(); + if (heapTrackingOptions) { + trackingOptions = *heapTrackingOptions; + } + + if (const bool res = useTrackingPluginHeapMemoryAllocator(trackingOptions); !res) { + DEBUG_FUNCTION_LINE_WARN("Failed to set heap tracking options for \"%s\"", mMetaInformation.getName().c_str()); + } } PluginContainer::PluginContainer(PluginContainer &&src) noexcept : mMetaInformation(std::move(src.mMetaInformation)), mPluginLinkInformation(std::move(src.mPluginLinkInformation)), + mTrackingHeapAllocatorOpt(std::move(src.mTrackingHeapAllocatorOpt)), mPluginData(std::move(src.mPluginData)), mHandle(std::move(src.mHandle)), mPluginConfigData(std::move(src.mPluginConfigData)), @@ -39,6 +54,7 @@ PluginContainer &PluginContainer::operator=(PluginContainer &&src) noexcept { this->mPluginData = std::move(src.mPluginData); this->mPluginConfigData = std::move(src.mPluginConfigData); this->mHandle = std::move(src.mHandle); + this->mTrackingHeapAllocatorOpt = std::move(src.mTrackingHeapAllocatorOpt); this->mStorageRootItem = src.mStorageRootItem; this->mInitDone = src.mInitDone; this->mButtonComboManagerHandle = src.mButtonComboManagerHandle; @@ -126,6 +142,7 @@ void PluginContainer::InitButtonComboData() { } mButtonComboManagerHandle = ButtonComboUtils::API::Internal::CreateButtonComboData(); } + void PluginContainer::DeinitButtonComboData() { if (getMetaInformation().getWUPSVersion() < WUPSVersion(0, 8, 2)) { return; @@ -133,6 +150,81 @@ void PluginContainer::DeinitButtonComboData() { ButtonComboUtils::API::Internal::RemoveButtonComboData(mButtonComboManagerHandle); } + +std::vector PluginContainer::GetButtonComboData() const { + if (getMetaInformation().getWUPSVersion() < WUPSVersion(0, 8, 2)) { + return {}; + } + return ButtonComboUtils::API::Internal::GetButtonComboData(mButtonComboManagerHandle); +} + uint32_t PluginContainer::getButtonComboManagerHandle() const { return mButtonComboManagerHandle; +} + +bool PluginContainer::useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options) { + if (options != PluginMetaInformation::TRACK_HEAP_OPTIONS_NONE) { + if (!this->mTrackingHeapAllocatorOpt) { + uint32_t stackTraceDepth = 0; + if (options == PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES) { + stackTraceDepth = 8; + } + this->mTrackingHeapAllocatorOpt = TrackingPluginHeapMemoryAllocator::Create(this->mMetaInformation.getName(), stackTraceDepth); + } + + return this->mTrackingHeapAllocatorOpt.has_value(); + } + this->mTrackingHeapAllocatorOpt.reset(); + return true; +} + +bool PluginContainer::isUsingTrackingPluginHeapMemoryAllocator() const { + return mTrackingHeapAllocatorOpt.has_value(); +} + +const IPluginHeapMemoryAllocator &PluginContainer::getMemoryAllocator() const { + if (mTrackingHeapAllocatorOpt) { + return *mTrackingHeapAllocatorOpt; + } + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator; +} + +const TrackingPluginHeapMemoryAllocator *PluginContainer::getTrackingMemoryAllocator() const { + if (mTrackingHeapAllocatorOpt) { + return &(mTrackingHeapAllocatorOpt.value()); + } + return nullptr; +} + +size_t PluginContainer::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + if (mHandle) { + totalSize += sizeof(uint32_t); + } + + if (mPluginData) { + totalSize += mPluginData->getMemoryFootprint(); + } + + if (mPluginConfigData.has_value()) { + size_t configFootprint = mPluginConfigData->getMemoryFootprint(); + if (configFootprint > sizeof(PluginConfigData)) { + totalSize += (configFootprint - sizeof(PluginConfigData)); + } + } + { + size_t metaFootprint = mMetaInformation.getMemoryFootprint(); + if (metaFootprint > sizeof(PluginMetaInformation)) { + totalSize += (metaFootprint - sizeof(PluginMetaInformation)); + } + } + { + size_t linkFootprint = mPluginLinkInformation.getMemoryFootprint(); + if (linkFootprint > sizeof(PluginLinkInformation)) { + totalSize += (linkFootprint - sizeof(PluginLinkInformation)); + } + } + + return totalSize; } \ No newline at end of file diff --git a/source/plugin/PluginContainer.h b/source/plugin/PluginContainer.h index 3aa3a4f..68770c1 100644 --- a/source/plugin/PluginContainer.h +++ b/source/plugin/PluginContainer.h @@ -20,17 +20,20 @@ #include "PluginConfigData.h" #include "PluginLinkInformation.h" #include "PluginMetaInformation.h" +#include "TrackingPluginHeapMemoryAllocator.h" #include #include #include + +struct ButtonComboInfo; class PluginData; class PluginContainer { public: - PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData); + PluginContainer(PluginMetaInformation metaInformation, PluginLinkInformation pluginLinkInformation, std::shared_ptr pluginData, std::optional heapTrackingOptions); PluginContainer(const PluginContainer &) = delete; @@ -66,12 +69,27 @@ public: void InitButtonComboData(); void DeinitButtonComboData(); + std::vector GetButtonComboData() const; [[nodiscard]] uint32_t getButtonComboManagerHandle() const; + [[nodiscard]] bool isUsingTrackingPluginHeapMemoryAllocator() const; + + [[nodiscard]] const IPluginHeapMemoryAllocator &getMemoryAllocator() const; + + [[nodiscard]] const TrackingPluginHeapMemoryAllocator *getTrackingMemoryAllocator() const; + + [[nodiscard]] size_t getMemoryFootprint() const; + private: + /** + * @return Returns true if setting the value was successful + */ + bool useTrackingPluginHeapMemoryAllocator(PluginMetaInformation::HeapTrackingOptions options); + PluginMetaInformation mMetaInformation; PluginLinkInformation mPluginLinkInformation; + std::optional mTrackingHeapAllocatorOpt; std::shared_ptr mPluginData; std::unique_ptr mHandle = std::make_unique(); diff --git a/source/plugin/PluginData.cpp b/source/plugin/PluginData.cpp index 0d4b823..39c4b21 100644 --- a/source/plugin/PluginData.cpp +++ b/source/plugin/PluginData.cpp @@ -33,3 +33,17 @@ std::span PluginData::getBuffer() const { const std::string &PluginData::getSource() const { return mSource; } + +size_t PluginData::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mBuffer.capacity() * sizeof(uint8_t); + + totalSize += mSource.capacity() * sizeof(char); + + if (mHandle) { + totalSize += sizeof(uint32_t); + } + + return totalSize; +} diff --git a/source/plugin/PluginData.h b/source/plugin/PluginData.h index fc18cb8..1ca4ce1 100644 --- a/source/plugin/PluginData.h +++ b/source/plugin/PluginData.h @@ -43,6 +43,8 @@ public: [[nodiscard]] const std::string &getSource() const; + size_t getMemoryFootprint() const; + private: std::vector mBuffer; std::string mSource; diff --git a/source/plugin/PluginDataFactory.cpp b/source/plugin/PluginDataFactory.cpp index 3814f71..b7a1f4b 100644 --- a/source/plugin/PluginDataFactory.cpp +++ b/source/plugin/PluginDataFactory.cpp @@ -42,7 +42,11 @@ std::vector PluginDataFactory::loadDir(const std::string_view if (inactivePluginsFilenames.contains(fileName)) { shouldBeLoadedAndLinked = false; } - result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked); + bool enableHeapTracking = false; +#ifdef DEBUG + enableHeapTracking = true; +#endif + result.emplace_back(std::move(pluginData), shouldBeLoadedAndLinked, enableHeapTracking); } else { auto errMsg = string_format("Failed to load plugin: %s", full_file_path.c_str()); DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); diff --git a/source/plugin/PluginLinkInformation.cpp b/source/plugin/PluginLinkInformation.cpp index 244c9fa..8f40725 100644 --- a/source/plugin/PluginLinkInformation.cpp +++ b/source/plugin/PluginLinkInformation.cpp @@ -120,3 +120,35 @@ std::span PluginLinkInformation::getTrampData() c const auto &entry = mAllocatedTextAndTrampMemoryAddress[1]; // 1 is tramp data return std::span(static_cast(entry.data()), entry.size() / sizeof(relocation_trampoline_entry_t)); } + +size_t PluginLinkInformation::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mHookDataList.capacity() * sizeof(HookData); + + totalSize += mFunctionDataList.capacity() * sizeof(FunctionData); + for (const auto &func : mFunctionDataList) { + totalSize += (func.getMemoryFootprint() - sizeof(FunctionData)); + } + + totalSize += mRelocationDataList.capacity() * sizeof(RelocationData); + for (const auto &reloc : mRelocationDataList) { + totalSize += (reloc.getMemoryFootprint() - sizeof(RelocationData)); + } + + for (const auto &symbol : mSymbolDataList) { + totalSize += sizeof(FunctionSymbolData); + totalSize += (symbol.getMemoryFootprint() - sizeof(FunctionSymbolData)); + } + + for (const auto &[key, section] : mSectionInfoList) { + totalSize += sizeof(std::string) + sizeof(SectionInfo); + totalSize += key.capacity(); + totalSize += (section.getMemoryFootprint() - sizeof(SectionInfo)); + } + + totalSize += (mAllocatedTextAndTrampMemoryAddress.getMemoryFootprint() - sizeof(HeapMemoryFixedSizePool)); + totalSize += (mAllocatedDataMemoryAddress.getMemoryFootprint() - sizeof(HeapMemoryFixedSizePool)); + + return totalSize; +} diff --git a/source/plugin/PluginLinkInformation.h b/source/plugin/PluginLinkInformation.h index 451163c..a415275 100644 --- a/source/plugin/PluginLinkInformation.h +++ b/source/plugin/PluginLinkInformation.h @@ -70,10 +70,10 @@ public: [[nodiscard]] bool hasValidData() const; - [[nodiscard]] int numberOfSegments() const; - [[nodiscard]] std::span getTrampData() const; + size_t getMemoryFootprint() const; + private: PluginLinkInformation() = default; diff --git a/source/plugin/PluginLoadWrapper.h b/source/plugin/PluginLoadWrapper.h index 879a148..6fd8acb 100644 --- a/source/plugin/PluginLoadWrapper.h +++ b/source/plugin/PluginLoadWrapper.h @@ -1,12 +1,16 @@ #pragma once +#include "PluginMetaInformation.h" + + #include class PluginData; class PluginLoadWrapper { public: - PluginLoadWrapper(std::shared_ptr pluginData, const bool linkAndLoad) : mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad) { + PluginLoadWrapper(std::shared_ptr pluginData, const bool linkAndLoad, const bool heapTrackingEnabled = false) + : mPluginData(std::move(pluginData)), mIsLoadAndLink(linkAndLoad), mIsHeapTrackingEnabled(heapTrackingEnabled) { } [[nodiscard]] const std::shared_ptr &getPluginData() const { @@ -17,7 +21,19 @@ public: return mIsLoadAndLink; } + [[nodiscard]] bool isHeapTrackingEnabled() const { + return mIsHeapTrackingEnabled; + } + + [[nodiscard]] std::optional getHeapTrackingOptions() const { + if (mIsHeapTrackingEnabled) { + return PluginMetaInformation::HeapTrackingOptions::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES; + } + return std::nullopt; + } + private: std::shared_ptr mPluginData; - bool mIsLoadAndLink = false; + bool mIsLoadAndLink = false; + bool mIsHeapTrackingEnabled = false; }; \ No newline at end of file diff --git a/source/plugin/PluginMetaInformation.cpp b/source/plugin/PluginMetaInformation.cpp index fd87657..79918a3 100644 --- a/source/plugin/PluginMetaInformation.cpp +++ b/source/plugin/PluginMetaInformation.cpp @@ -38,6 +38,10 @@ return mSize; } +[[nodiscard]] PluginMetaInformation::HeapTrackingOptions PluginMetaInformation::getHeapTrackingOptions() const { + return mHeapTrackingOptions; +} + PluginMetaInformation::PluginMetaInformation() = default; void PluginMetaInformation::setName(std::string name) { @@ -78,4 +82,22 @@ void PluginMetaInformation::setSize(const size_t size) { void PluginMetaInformation::setStorageId(std::string storageId) { mStorageId = std::move(storageId); +} + +void PluginMetaInformation::setHeapTrackingOptions(HeapTrackingOptions value) { + mHeapTrackingOptions = value; +} + +size_t PluginMetaInformation::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mName.capacity(); + totalSize += mAuthor.capacity(); + totalSize += mVersion.capacity(); + totalSize += mLicense.capacity(); + totalSize += mBuildTimestamp.capacity(); + totalSize += mDescription.capacity(); + totalSize += mStorageId.capacity(); + + return totalSize; } \ No newline at end of file diff --git a/source/plugin/PluginMetaInformation.h b/source/plugin/PluginMetaInformation.h index a5764f1..1056ead 100644 --- a/source/plugin/PluginMetaInformation.h +++ b/source/plugin/PluginMetaInformation.h @@ -26,7 +26,14 @@ class WUPSVersion; class PluginMetaInformation { + public: + enum HeapTrackingOptions { + TRACK_HEAP_OPTIONS_NONE = 0, + TRACK_HEAP_OPTIONS_TRACK_SIZE = 1, + TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES = 2, + }; + [[nodiscard]] const std::string &getName() const; [[nodiscard]] const std::string &getAuthor() const; @@ -45,6 +52,8 @@ public: [[nodiscard]] size_t getSize() const; + [[nodiscard]] HeapTrackingOptions getHeapTrackingOptions() const; + private: PluginMetaInformation(); @@ -68,6 +77,10 @@ private: void setStorageId(std::string storageId); + void setHeapTrackingOptions(HeapTrackingOptions value); + + size_t getMemoryFootprint() const; + std::string mName; std::string mAuthor; std::string mVersion; @@ -75,8 +88,9 @@ private: std::string mBuildTimestamp; std::string mDescription; std::string mStorageId; - size_t mSize = {}; - WUPSVersion mWUPSVersion = WUPSVersion(0, 0, 0); + size_t mSize = {}; + WUPSVersion mWUPSVersion = WUPSVersion(0, 0, 0); + HeapTrackingOptions mHeapTrackingOptions = TRACK_HEAP_OPTIONS_NONE; friend class PluginMetaInformationFactory; diff --git a/source/plugin/PluginMetaInformationFactory.cpp b/source/plugin/PluginMetaInformationFactory.cpp index e4199b6..0ae8044 100644 --- a/source/plugin/PluginMetaInformationFactory.cpp +++ b/source/plugin/PluginMetaInformationFactory.cpp @@ -108,6 +108,12 @@ std::optional PluginMetaInformationFactory::loadPlugin(st pluginInfo.setDescription(value); } else if (key == "storage_id") { pluginInfo.setStorageId(value); + } else if (key == "debug") { + if (value == "track_heap") { + pluginInfo.setHeapTrackingOptions(PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE); + } else if (value == "track_heap_with_stack_trace") { + pluginInfo.setHeapTrackingOptions(PluginMetaInformation::TRACK_HEAP_OPTIONS_TRACK_SIZE_AND_COLLECT_STACK_TRACES); + } } else if (key == "wups") { if (value == "0.7.1") { pluginInfo.setWUPSVersion(0, 7, 1); diff --git a/source/plugin/RelocationData.cpp b/source/plugin/RelocationData.cpp index b51b026..11dc1c8 100644 --- a/source/plugin/RelocationData.cpp +++ b/source/plugin/RelocationData.cpp @@ -39,4 +39,12 @@ RelocationData::~RelocationData() = default; [[nodiscard]] const ImportRPLInformation &RelocationData::getImportRPLInformation() const { return *mRPLInfo; +} + +size_t RelocationData::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + totalSize += mName.capacity(); + + return totalSize; } \ No newline at end of file diff --git a/source/plugin/RelocationData.h b/source/plugin/RelocationData.h index 36edb30..2dce232 100644 --- a/source/plugin/RelocationData.h +++ b/source/plugin/RelocationData.h @@ -46,10 +46,13 @@ public: [[nodiscard]] const ImportRPLInformation &getImportRPLInformation() const; private: + [[nodiscard]] size_t getMemoryFootprint() const; + char mType; size_t mOffset; int32_t mAddend; void *mDestination; std::string mName; std::shared_ptr mRPLInfo; + friend class PluginLinkInformation; }; diff --git a/source/plugin/SectionInfo.cpp b/source/plugin/SectionInfo.cpp index f109220..317e0b9 100644 --- a/source/plugin/SectionInfo.cpp +++ b/source/plugin/SectionInfo.cpp @@ -21,4 +21,10 @@ SectionInfo::SectionInfo(std::string name, [[nodiscard]] uint32_t SectionInfo::isInSection(uint32_t addr) const { return addr >= mAddress && addr < mAddress + mSectionSize; +} + +size_t SectionInfo::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + totalSize += mName.capacity(); + return totalSize; } \ No newline at end of file diff --git a/source/plugin/SectionInfo.h b/source/plugin/SectionInfo.h index 13fae4f..69882ff 100644 --- a/source/plugin/SectionInfo.h +++ b/source/plugin/SectionInfo.h @@ -35,7 +35,11 @@ public: [[nodiscard]] uint32_t isInSection(uint32_t addr) const; private: + [[nodiscard]] size_t getMemoryFootprint() const; + std::string mName; uint32_t mAddress = {}; uint32_t mSectionSize = {}; + + friend class PluginLinkInformation; }; diff --git a/source/plugin/TrackingPluginHeapMemoryAllocator.cpp b/source/plugin/TrackingPluginHeapMemoryAllocator.cpp new file mode 100644 index 0000000..7bf3b60 --- /dev/null +++ b/source/plugin/TrackingPluginHeapMemoryAllocator.cpp @@ -0,0 +1,177 @@ +#include "TrackingPluginHeapMemoryAllocator.h" + +#include "utils/logger.h" +#include "utils/utils.h" + +#include + +namespace { + struct PluginMemoryStatsInternal { + mutable std::mutex mutex; + bool inUse = false; + uint32_t stackTraceDepth = 0; + PluginMemorySnapshot data; + }; + + // Generate 64 different thunk functions so we can track 64 plugins at a time which should be more than enough + constexpr int MAX_SLOTS = 64; + template + struct MemoryThunk { + inline static PluginMemoryStatsInternal stats; + + static void *Alloc(uint32_t size) { + const auto ptr = (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocFunctionAddress())(size); + + if (ptr) { + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + stats.data.allocCount++; + stats.data.currentAllocated += size; + if (stats.data.currentAllocated > stats.data.peakAllocated) { + stats.data.peakAllocated = stats.data.currentAllocated; + } + + stats.data.allocationMap[ptr] = {size, stats.stackTraceDepth > 0 ? CaptureStackTrace(stats.stackTraceDepth) : std::vector()}; + } + } + return ptr; + } + + static void *AllocEx(uint32_t size, int align) { + const auto ptr = (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocExFunctionAddress())(size, align); + if (ptr) { + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + stats.data.allocCount++; + stats.data.currentAllocated += size; + if (stats.data.currentAllocated > stats.data.peakAllocated) { + stats.data.peakAllocated = stats.data.currentAllocated; + } + stats.data.allocationMap[ptr] = {size, stats.stackTraceDepth > 0 ? CaptureStackTrace(stats.stackTraceDepth) : std::vector()}; + } + } + return ptr; + } + + static void Free(void *ptr) { + if (!ptr) return; + + std::lock_guard lock(stats.mutex); + if (stats.inUse) { + if (const auto it = stats.data.allocationMap.find(ptr); it != stats.data.allocationMap.end()) { + uint32_t size = it->second.size; + if (stats.data.currentAllocated >= size) { + stats.data.currentAllocated -= size; + } else { + stats.data.currentAllocated = 0; + } + stats.data.allocationMap.erase(it); + } else { + DEBUG_FUNCTION_LINE_ERR("free() of for unknown ptr detected (double free?). \"%s\": %p", stats.data.pluginName.c_str(), ptr); + auto stackTrace = CaptureStackTrace(16); + PrintCapturedStackTrace(stackTrace); + } + stats.data.freeCount++; + } + + (*DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetFreeFunctionAddress())(ptr); + } + }; + + template + constexpr std::array CreateAllocTable(std::integer_sequence) { return {MemoryThunk::Alloc...}; } + template + constexpr std::array CreateAllocExTable(std::integer_sequence) { return {MemoryThunk::AllocEx...}; } + template + constexpr std::array CreateFreeTable(std::integer_sequence) { return {MemoryThunk::Free...}; } + + template + constexpr std::array CreateStatsTable(std::integer_sequence) { + return {&MemoryThunk::stats...}; + } + + auto sAllocThunks = CreateAllocTable(std::make_integer_sequence{}); + auto sAllocExThunks = CreateAllocExTable(std::make_integer_sequence{}); + auto sFreeThunks = CreateFreeTable(std::make_integer_sequence{}); + auto sStatsTable = CreateStatsTable(std::make_integer_sequence{}); +} // namespace + +TrackingPluginHeapMemoryAllocator::TrackingPluginHeapMemoryAllocator(const int32_t index) : sIndex(index) { +} + +std::optional TrackingPluginHeapMemoryAllocator::Create(const std::string_view pluginName, uint32_t stackTraceDepth) { + int32_t index = 0; + for (auto *stat : sStatsTable) { + std::lock_guard lock(stat->mutex); + if (!stat->inUse) { + stat->inUse = true; + stat->stackTraceDepth = stackTraceDepth; + stat->data = {}; + stat->data.pluginName = pluginName; + return TrackingPluginHeapMemoryAllocator{index}; + } + ++index; + } + return std::nullopt; +} + +TrackingPluginHeapMemoryAllocator::~TrackingPluginHeapMemoryAllocator() { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return; + } + auto *stats = sStatsTable[sIndex]; + std::lock_guard lock(stats->mutex); + stats->inUse = false; +} + +TrackingPluginHeapMemoryAllocator::TrackingPluginHeapMemoryAllocator(TrackingPluginHeapMemoryAllocator &&src) noexcept { + this->sIndex = src.sIndex; + src.sIndex = -1; +} + +TrackingPluginHeapMemoryAllocator &TrackingPluginHeapMemoryAllocator::operator=(TrackingPluginHeapMemoryAllocator &&src) noexcept { + if (this != &src) { + // Release current slot if we have one + if (this->sIndex >= 0 && this->sIndex < MAX_SLOTS) { + auto *stats = sStatsTable[this->sIndex]; + std::lock_guard lock(stats->mutex); + stats->inUse = false; + stats->data = {}; + } + + this->sIndex = src.sIndex; + src.sIndex = -1; + } + return *this; +} + +MEMAllocFromDefaultHeapFn *TrackingPluginHeapMemoryAllocator::GetAllocFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocFunctionAddress(); + } + + return &sAllocThunks[sIndex]; +} + +MEMAllocFromDefaultHeapExFn *TrackingPluginHeapMemoryAllocator::GetAllocExFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetAllocExFunctionAddress(); + } + return &sAllocExThunks[sIndex]; +} + +MEMFreeToDefaultHeapFn *TrackingPluginHeapMemoryAllocator::GetFreeFunctionAddress() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return DefaultPluginHeapMemoryAllocator::gDefaultPluginHeapMemoryAllocator.GetFreeFunctionAddress(); + } + return &sFreeThunks[sIndex]; +} + +std::optional TrackingPluginHeapMemoryAllocator::GetHeapMemoryUsageSnapshot() const { + if (sIndex < 0 || sIndex >= MAX_SLOTS) { + return std::nullopt; + } + const auto *stats = sStatsTable[sIndex]; + std::lock_guard lock(stats->mutex); + return stats->data; +} diff --git a/source/plugin/TrackingPluginHeapMemoryAllocator.h b/source/plugin/TrackingPluginHeapMemoryAllocator.h new file mode 100644 index 0000000..c3eacdc --- /dev/null +++ b/source/plugin/TrackingPluginHeapMemoryAllocator.h @@ -0,0 +1,29 @@ +#pragma once +#include "DefaultPluginHeapMemoryAllocator.h" + +#include +#include + +#include + +class TrackingPluginHeapMemoryAllocator : public IPluginHeapMemoryAllocator { +public: + static std::optional Create(std::string_view pluginName, uint32_t stackTraceDepth); + + ~TrackingPluginHeapMemoryAllocator() override; + + TrackingPluginHeapMemoryAllocator(const TrackingPluginHeapMemoryAllocator &) = delete; + + TrackingPluginHeapMemoryAllocator(TrackingPluginHeapMemoryAllocator &&src) noexcept; + + TrackingPluginHeapMemoryAllocator &operator=(TrackingPluginHeapMemoryAllocator &&src) noexcept; + + MEMAllocFromDefaultHeapFn *GetAllocFunctionAddress() const override; + MEMAllocFromDefaultHeapExFn *GetAllocExFunctionAddress() const override; + MEMFreeToDefaultHeapFn *GetFreeFunctionAddress() const override; + std::optional GetHeapMemoryUsageSnapshot() const override; + +private: + int sIndex = -1; + TrackingPluginHeapMemoryAllocator(int index); +}; diff --git a/source/utils/HeapMemoryFixedSize.cpp b/source/utils/HeapMemoryFixedSize.cpp index 478f3d9..f0a5f5f 100644 --- a/source/utils/HeapMemoryFixedSize.cpp +++ b/source/utils/HeapMemoryFixedSize.cpp @@ -64,6 +64,19 @@ HeapMemoryFixedSizePool::operator bool() const { HeapMemoryFixedSizePool::MemorySegmentInfo HeapMemoryFixedSizePool::operator[](const int idx) const { if (idx < 0 || idx >= static_cast(mSegmentInfos.size())) { DEBUG_FUNCTION_LINE_ERR("Out of bounce access (tried to access index %d; size is %d", idx, mSegmentInfos.size()); + return {nullptr, 0}; } return mSegmentInfos[idx]; +} + +size_t HeapMemoryFixedSizePool::getMemoryFootprint() const { + size_t totalSize = sizeof(*this); + + if (mData) { + totalSize += mTotalSize; + } + + totalSize += mSegmentInfos.capacity() * sizeof(MemorySegmentInfo); + + return totalSize; } \ No newline at end of file diff --git a/source/utils/HeapMemoryFixedSize.h b/source/utils/HeapMemoryFixedSize.h index b576624..8a206c9 100644 --- a/source/utils/HeapMemoryFixedSize.h +++ b/source/utils/HeapMemoryFixedSize.h @@ -39,7 +39,11 @@ public: MemorySegmentInfo operator[](int idx) const; private: + [[nodiscard]] size_t getMemoryFootprint() const; + std::unique_ptr mData{}; std::size_t mTotalSize{}; std::vector mSegmentInfos; + + friend class PluginLinkInformation; }; diff --git a/source/utils/buttoncombo/ButtonComboUtils.cpp b/source/utils/buttoncombo/ButtonComboUtils.cpp index e606870..afadc8d 100644 --- a/source/utils/buttoncombo/ButtonComboUtils.cpp +++ b/source/utils/buttoncombo/ButtonComboUtils.cpp @@ -24,6 +24,16 @@ namespace ButtonComboUtils::API { DEBUG_FUNCTION_LINE_WARN("Tried to remove ButtonComboManager by invalid handle: %08X", buttonComboManagerHandle); } } + + std::vector GetButtonComboData(uint32_t buttonComboManagerHandle) { + std::lock_guard lock(sButtonComboMutex); + for (auto &manager : sButtonComboManager) { + if (manager.getHandle() == buttonComboManagerHandle) { + return manager.GetButtonCombos(); + } + } + return {}; + } } // namespace Internal namespace { diff --git a/source/utils/buttoncombo/ButtonComboUtils.h b/source/utils/buttoncombo/ButtonComboUtils.h index aa3ae67..3c06f08 100644 --- a/source/utils/buttoncombo/ButtonComboUtils.h +++ b/source/utils/buttoncombo/ButtonComboUtils.h @@ -3,11 +3,15 @@ #include #include +#include + +struct ButtonComboInfo; namespace ButtonComboUtils::API { namespace Internal { uint32_t CreateButtonComboData(); void RemoveButtonComboData(uint32_t buttonComboManagerHandle); + std::vector GetButtonComboData(uint32_t buttonComboManagerHandle); } // namespace Internal WUPSButtonCombo_Error AddButtonCombo(void *identifier, diff --git a/source/utils/config/ConfigDisplayItem.cpp b/source/utils/config/ConfigDisplayItem.cpp index 126d257..a7bcc3c 100644 --- a/source/utils/config/ConfigDisplayItem.cpp +++ b/source/utils/config/ConfigDisplayItem.cpp @@ -2,16 +2,18 @@ #include "config/WUPSConfig.h" -#include - #include +#include ConfigDisplayItem::ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, - const bool isActive) : mConfig(std::move(config)), - mInfo(std::move(info)), - mIsActivePlugin(isActive), - mInitialIsActivePlugin(isActive) { + const bool isActive, + const bool isHeapTracking) : mConfig(std::move(config)), + mInfo(std::move(info)), + mIsActivePlugin(isActive), + mInitialIsActivePlugin(isActive), + mIsHeapTrackingEnabled(isHeapTracking), + mInitialIsHeapTrackingEnabled(isHeapTracking) { assert(mConfig); } @@ -33,4 +35,16 @@ void ConfigDisplayItem::toggleIsActivePlugin() { void ConfigDisplayItem::resetIsActivePlugin() { mIsActivePlugin = mInitialIsActivePlugin; +} + +bool ConfigDisplayItem::isHeapTrackingEnabled() const { + return mIsHeapTrackingEnabled; +} + +void ConfigDisplayItem::toggleIsHeapTrackingEnabled() { + mIsHeapTrackingEnabled = !mIsHeapTrackingEnabled; +} + +void ConfigDisplayItem::resetIsHeapTrackingEnabled() { + mIsHeapTrackingEnabled = mInitialIsHeapTrackingEnabled; } \ No newline at end of file diff --git a/source/utils/config/ConfigDisplayItem.h b/source/utils/config/ConfigDisplayItem.h index acc286b..3de036c 100644 --- a/source/utils/config/ConfigDisplayItem.h +++ b/source/utils/config/ConfigDisplayItem.h @@ -15,21 +15,25 @@ struct GeneralConfigInformation { class ConfigDisplayItem { public: - ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, bool isActive); + ConfigDisplayItem(GeneralConfigInformation &info, std::unique_ptr config, bool isActive, bool isHeapTracking = false); [[nodiscard]] const GeneralConfigInformation &getConfigInformation() const; [[nodiscard]] const WUPSConfigAPIBackend::WUPSConfig &getConfig() const; [[nodiscard]] bool isActivePlugin() const; - void toggleIsActivePlugin(); - void resetIsActivePlugin(); + [[nodiscard]] bool isHeapTrackingEnabled() const; + void toggleIsHeapTrackingEnabled(); + void resetIsHeapTrackingEnabled(); + private: std::unique_ptr mConfig; GeneralConfigInformation mInfo; bool mIsActivePlugin; bool mInitialIsActivePlugin; + bool mIsHeapTrackingEnabled; + bool mInitialIsHeapTrackingEnabled; }; \ No newline at end of file diff --git a/source/utils/config/ConfigRenderer.cpp b/source/utils/config/ConfigRenderer.cpp index bed3d52..df70989 100644 --- a/source/utils/config/ConfigRenderer.cpp +++ b/source/utils/config/ConfigRenderer.cpp @@ -1,4 +1,5 @@ #include "ConfigRenderer.h" +#include "ConfigRendererStates.h" #include "CategoryRenderer.h" #include "ConfigDisplayItem.h" @@ -11,15 +12,11 @@ #include "utils/logger.h" #include +#include +#include ConfigRenderer::ConfigRenderer(std::vector &&vec) : mConfigs(std::move(vec)) { - std::ranges::copy(mConfigs, - std::back_inserter(mAllConfigs)); - std::ranges::copy_if(mConfigs, - std::back_inserter(mActiveConfigs), - [&](const auto &value) { - return value.isActivePlugin(); - }); + SetListState(std::make_unique()); } ConfigRenderer::~ConfigRenderer() = default; @@ -44,6 +41,10 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat if (complexInputData.vpad.vpadError == VPAD_READ_SUCCESS && complexInputData.vpad.data.hold != 0) { mLastInputWasOnWiimote = false; } + + // Reset transient return state + mNextSubState = SUB_STATE_RUNNING; + switch (mState) { case STATE_MAIN: return UpdateStateMain(input); @@ -51,9 +52,9 @@ ConfigSubState ConfigRenderer::Update(Input &input, const WUPSConfigSimplePadDat if (mCategoryRenderer) { auto subResult = mCategoryRenderer->Update(input, simpleInputData, complexInputData); if (subResult != SUB_STATE_RUNNING) { - mNeedRedraw = true; - mActivePluginsDirty = false; - mState = STATE_MAIN; + mNeedRedraw = true; + mPluginListDirty = false; + mState = STATE_MAIN; return SUB_STATE_RUNNING; } return SUB_STATE_RUNNING; @@ -99,90 +100,50 @@ void ConfigRenderer::ResetNeedsRedraw() { } } +void ConfigRenderer::RequestRedraw() { + mNeedRedraw = true; +} + ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) { - auto &configs = GetConfigList(); + if (!mListState) return SUB_STATE_ERROR; + auto &configs = GetDisplayedConfigList(); const auto prevSelectedItem = mCursorPos; + auto totalElementSize = (int32_t) configs.size(); - const auto &savePendingConfigFn = [&configs, this]() { - for (const auto &element : configs) { - CallOnCloseCallback(element.get().getConfigInformation(), element.get().getConfig()); - } - }; + // Delegate specific inputs to the State + bool inputHandled = mListState->HandleInput(*this, input); - auto totalElementSize = (int32_t) configs.size(); + if (mNextSubState != SUB_STATE_RUNNING) { + return mNextSubState; + } + + if (inputHandled) { + return SUB_STATE_RUNNING; + } + + // Handle Navigation (Common to all states) if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) { mCursorPos++; } else if (input.data.buttons_d & Input::eButtons::BUTTON_LEFT) { - // Paging up mCursorPos -= MAX_BUTTONS_ON_SCREEN - 1; - // Don't jump past the top - if (mCursorPos < 0) - mCursorPos = 0; + if (mCursorPos < 0) mCursorPos = 0; } else if (input.data.buttons_d & Input::eButtons::BUTTON_RIGHT) { - // Paging down mCursorPos += MAX_BUTTONS_ON_SCREEN - 1; - // Don't jump past the bottom - if (mCursorPos >= totalElementSize) - mCursorPos = totalElementSize - 1; + if (mCursorPos >= totalElementSize) mCursorPos = totalElementSize - 1; } else if (input.data.buttons_d & Input::eButtons::BUTTON_UP) { mCursorPos--; - } else if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { - if (mSetActivePluginsMode) { - mNeedRedraw = true; - mCategoryRenderer.reset(); - savePendingConfigFn(); - return SUB_STATE_RETURN_WITH_PLUGIN_RELOAD; - } - } else if (input.data.buttons_d & Input::eButtons::BUTTON_X) { - if (!mSetActivePluginsMode && !mAllConfigs.empty()) { - mSetActivePluginsMode = true; - mNeedRedraw = true; - return SUB_STATE_RUNNING; - } - } else if (input.data.buttons_d & Input::eButtons::BUTTON_A) { - if (mSetActivePluginsMode) { - mActivePluginsDirty = true; - mNeedRedraw = true; - configs[mCursorPos].get().toggleIsActivePlugin(); - return SUB_STATE_RUNNING; - } else if (!configs.empty()) { - if (mCursorPos != mCurrentOpen) { - mCategoryRenderer.reset(); - mCategoryRenderer = make_unique_nothrow(&(configs[mCursorPos].get().getConfigInformation()), &(configs[mCursorPos].get().getConfig()), true); - } - mNeedRedraw = true; - mCurrentOpen = mCursorPos; - mState = STATE_SUB; - return SUB_STATE_RUNNING; - } - } else if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { - if (mSetActivePluginsMode) { - for (auto &cur : mConfigs) { - cur.resetIsActivePlugin(); - } - mActivePluginsDirty = false; - mNeedRedraw = true; - mSetActivePluginsMode = false; - return SUB_STATE_RUNNING; - } else { - mNeedRedraw = true; - mCategoryRenderer.reset(); - savePendingConfigFn(); - return SUB_STATE_RETURN; - } } - if (mCursorPos < 0) { - mCursorPos = totalElementSize - 1; - } else if (mCursorPos >= totalElementSize) { - mCursorPos = 0; - } - if (mCursorPos < 0) { + if (totalElementSize > 0) { + if (mCursorPos < 0) mCursorPos = totalElementSize - 1; + else if (mCursorPos >= totalElementSize) + mCursorPos = 0; + } else { mCursorPos = 0; } - // Adjust the render offset when reaching the boundaries + // Adjust render offset if (mCursorPos < mRenderOffset) { mRenderOffset = mCursorPos; } else if (mCursorPos >= mRenderOffset + MAX_BUTTONS_ON_SCREEN - 1) { @@ -197,71 +158,70 @@ ConfigSubState ConfigRenderer::UpdateStateMain(const Input &input) { } void ConfigRenderer::RenderStateMain() const { - auto &configs = GetConfigList(); + auto &configs = GetDisplayedConfigList(); DrawUtils::beginDraw(); DrawUtils::clear(COLOR_BACKGROUND); auto totalElementSize = (int32_t) configs.size(); + int start = std::max(0, mRenderOffset); + int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize); - // Calculate the range of items to display - int start = std::max(0, mRenderOffset); - int end = std::min(start + MAX_BUTTONS_ON_SCREEN, totalElementSize); - - if (mActiveConfigs.empty() && !mSetActivePluginsMode) { + if (configs.empty()) { DrawUtils::setFontSize(24); - std::string noConfigText = "No active plugins"; - uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data()); + std::string noConfigText = "No plugins available"; - if (!mAllConfigs.empty()) { + if (!mListState->IsMainView()) { + noConfigText = "No active plugins"; + } + + uint32_t szNoConfig = DrawUtils::getTextWidth(noConfigText.data()); + + if (mListState->IsMainView()) { + DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data()); + } else { const auto activateHint = string_format("Press %s to activate inactive plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002"); const auto szHint = DrawUtils::getTextWidth(activateHint.c_str()); - DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2) - 16, noConfigText.data()); DrawUtils::print((SCREEN_WIDTH / 2) - (szHint / 2), (SCREEN_HEIGHT / 2) + 16, activateHint.data()); - } else { - DrawUtils::print((SCREEN_WIDTH / 2) - (szNoConfig / 2), (SCREEN_HEIGHT / 2), noConfigText.data()); } } else { uint32_t yOffset = 8 + 24 + 8 + 4; for (int32_t i = start; i < end; i++) { - DrawConfigEntry(yOffset, configs[i].get().getConfigInformation(), i == mCursorPos, configs[i].get().isActivePlugin()); + DrawConfigEntry(yOffset, configs[i].get(), i == mCursorPos); yOffset += 42 + 8; } } DrawUtils::setFontColor(COLOR_TEXT); - // draw top bar + // Top Bar DrawUtils::setFontSize(24); - if (mSetActivePluginsMode) { - DrawUtils::print(16, 6 + 24, "Please select the plugins that should be active"); - } else { - DrawUtils::print(16, 6 + 24, "Wii U Plugin System Config Menu"); + DrawUtils::print(16, 6 + 24, mListState->GetTitle().c_str()); - auto countInactivePlugins = mAllConfigs.size() - mActiveConfigs.size(); + if (mListState->IsMainView()) { + auto countInactivePlugins = mConfigs.size() - mFilteredConfigs.size(); if (countInactivePlugins > 0) { DrawUtils::setFontSize(14); const std::string plugin_unloaded = string_format("Found %d inactive plugins", countInactivePlugins); DrawUtils::print(SCREEN_WIDTH - 16 - DrawUtils::getTextWidth(MODULE_VERSION_FULL) - 32, 8 + 24, plugin_unloaded.c_str(), true); } } + DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, 8 + 24, MODULE_VERSION_FULL, true); DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK); - // draw bottom bar + // Bottom Bar DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_BLACK); DrawUtils::setFontSize(18); - DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate "); - if (mSetActivePluginsMode) { - DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, "\uE000 Toggle | \uE045 Apply", true); - } else if (totalElementSize > 0) { - const auto text = string_format("\ue000 Select | %s Manage plugins", mLastInputWasOnWiimote ? "\uE048" : "\uE002"); - DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, text.c_str(), true); - } - // draw scroll indicator + if (totalElementSize > 0) { + DrawUtils::print(16, SCREEN_HEIGHT - 10, "\uE07D/\uE07E Navigate "); + } + DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 10, mListState->GetBottomBar(mLastInputWasOnWiimote).c_str(), true); + + // Scroll Indicator DrawUtils::setFontSize(24); if (end < totalElementSize) { DrawUtils::print(SCREEN_WIDTH / 2 + 12, SCREEN_HEIGHT - 32, "\ufe3e", true); @@ -270,18 +230,15 @@ void ConfigRenderer::RenderStateMain() const { DrawUtils::print(SCREEN_WIDTH / 2 + 12, 32 + 20, "\ufe3d", true); } - // draw home button + // Home Button DrawUtils::setFontSize(18); - const char *exitHint = "\ue044 Exit"; - if (mSetActivePluginsMode) { - exitHint = "\ue001 Abort"; - } + const char *exitHint = mListState->IsMainView() ? "\ue001 Abort" : "\ue044 Exit"; DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHint) / 2, SCREEN_HEIGHT - 10, exitHint, true); DrawUtils::endDraw(); } -void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const { +void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const { DrawUtils::setFontColor(COLOR_TEXT); if (isHighlighted) { @@ -291,18 +248,15 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform } int textXOffset = 16 * 2; - if (mSetActivePluginsMode) { - DrawUtils::setFontSize(24); - if (isActive) { - DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25C9"); - } else { - DrawUtils::print(textXOffset, yOffset + 8 + 24, "\u25CE"); - } + + // Delegate Icon drawing to state, returns true if icon was drawn + if (mListState->RenderItemIcon(item, textXOffset, yOffset + 8 + 24)) { textXOffset += 32; } DrawUtils::setFontSize(24); + const auto &configInformation = item.getConfigInformation(); DrawUtils::print(textXOffset, yOffset + 8 + 24, configInformation.name.c_str()); uint32_t sz = DrawUtils::getTextWidth(configInformation.name.c_str()); DrawUtils::setFontSize(12); @@ -310,6 +264,82 @@ void ConfigRenderer::DrawConfigEntry(uint32_t yOffset, const GeneralConfigInform DrawUtils::print(SCREEN_WIDTH - 16 * 2, yOffset + 8 + 24, configInformation.version.c_str(), true); } +void ConfigRenderer::SetListState(std::unique_ptr state) { + mListState = std::move(state); + mNeedRedraw = true; + mCursorPos = 0; + mRenderOffset = 0; + // Fallback to "show all" + std::function pred = [](const auto &) { return true; }; + if (mListState) { + pred = mListState->GetConfigFilter(); + } + // Copy references into filteredConfigView + mFilteredConfigs.clear(); + std::ranges::copy_if(mConfigs, std::back_inserter(mFilteredConfigs), + std::move(pred)); +} + +const std::vector &ConfigRenderer::GetConfigItems() { + return mConfigs; +} + +std::vector> &ConfigRenderer::GetFilteredConfigItems() { + return mFilteredConfigs; +} + +int32_t ConfigRenderer::GetCursorPos() const { + return mCursorPos; +} + +void ConfigRenderer::EnterSelectedCategory() { + auto &items = GetDisplayedConfigList(); + if (mCursorPos < 0 || static_cast(mCursorPos) >= items.size()) return; + + if (mCursorPos != mCurrentOpen) { + mCategoryRenderer.reset(); + mCategoryRenderer = make_unique_nothrow(&(items[mCursorPos].get().getConfigInformation()), &(items[mCursorPos].get().getConfig()), true); + } + mNeedRedraw = true; + mCurrentOpen = mCursorPos; + mState = STATE_SUB; +} + +void ConfigRenderer::SavePendingConfigs() { + for (const auto &element : mConfigs) { + CallOnCloseCallback(element.getConfigInformation(), element.getConfig()); + } +} + +void ConfigRenderer::Exit() { + mNeedRedraw = true; + mCategoryRenderer.reset(); + SavePendingConfigs(); + mNextSubState = SUB_STATE_RETURN; +} + +void ConfigRenderer::ExitWithReload() { + mNeedRedraw = true; + mCategoryRenderer.reset(); + SavePendingConfigs(); + mNextSubState = SUB_STATE_RETURN_WITH_PLUGIN_RELOAD; +} + +void ConfigRenderer::SetPluginsListDirty(bool dirty) { + mPluginListDirty = dirty; +} + +bool ConfigRenderer::GetPluginsListIfChanged(std::vector &result) { + if (mPluginListDirty) { + result.clear(); + for (const auto &cur : mConfigs) { + result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin(), cur.isHeapTrackingEnabled()); + } + return true; + } + return false; +} + void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector> &categories) { for (const auto &cat : categories) { if (!cat->getCategories().empty()) { @@ -321,18 +351,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c } } -bool ConfigRenderer::GetActivePluginsIfChanged(std::vector &result) { - if (mActivePluginsDirty) { - std::vector inactive_plugins; - result.clear(); - for (const auto &cur : mConfigs) { - result.emplace_back(cur.getConfigInformation().pluginData, cur.isActivePlugin()); - } - return true; - } - return false; -} - void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config) { CallOnCloseCallback(info, config.getCategories()); for (const auto &item : config.getItems()) { @@ -340,9 +358,6 @@ void ConfigRenderer::CallOnCloseCallback(const GeneralConfigInformation &info, c } } -const std::vector> &ConfigRenderer::GetConfigList() const { - if (mSetActivePluginsMode) { - return mAllConfigs; - } - return mActiveConfigs; -} +const std::vector> &ConfigRenderer::GetDisplayedConfigList() const { + return mFilteredConfigs; +} \ No newline at end of file diff --git a/source/utils/config/ConfigRenderer.h b/source/utils/config/ConfigRenderer.h index 7b15154..df7f8c7 100644 --- a/source/utils/config/ConfigRenderer.h +++ b/source/utils/config/ConfigRenderer.h @@ -5,11 +5,11 @@ #include +#include +#include #include #include -#include - namespace WUPSConfigAPIBackend { class WUPSConfig; class WUPSConfigCategory; @@ -18,6 +18,8 @@ class PluginLoadWrapper; class Input; class CategoryRenderer; class ConfigDisplayItem; +class ConfigListState; + class ConfigRenderer { public: @@ -30,22 +32,31 @@ public: void Render() const; [[nodiscard]] bool NeedsRedraw() const; - void ResetNeedsRedraw(); + void RequestRedraw(); - bool GetActivePluginsIfChanged(std::vector &result); + bool GetPluginsListIfChanged(std::vector &result); + + void SetListState(std::unique_ptr state); + const std::vector &GetConfigItems(); + std::vector> &GetFilteredConfigItems(); // Mutable access + int32_t GetCursorPos() const; + void EnterSelectedCategory(); + void Exit(); + void ExitWithReload(); + void SetPluginsListDirty(bool dirty); private: ConfigSubState UpdateStateMain(const Input &input); - void RenderStateMain() const; - - void DrawConfigEntry(uint32_t yOffset, const GeneralConfigInformation &configInformation, bool isHighlighted, bool isActive) const; + void DrawConfigEntry(uint32_t yOffset, const ConfigDisplayItem &item, bool isHighlighted) const; void CallOnCloseCallback(const GeneralConfigInformation &info, const std::vector> &categories); void CallOnCloseCallback(const GeneralConfigInformation &info, const WUPSConfigAPIBackend::WUPSConfig &config); + void SavePendingConfigs(); - [[nodiscard]] const std::vector> &GetConfigList() const; + + [[nodiscard]] const std::vector> &GetDisplayedConfigList() const; enum State { STATE_MAIN = 0, @@ -53,18 +64,20 @@ private: }; std::vector mConfigs; - std::vector> mAllConfigs; - std::vector> mActiveConfigs; + mutable std::vector> mFilteredConfigs; + + std::unique_ptr mListState; std::unique_ptr mCategoryRenderer; State mState = STATE_MAIN; + // Used to signal the main loop to return a specific state + ConfigSubState mNextSubState = SUB_STATE_RUNNING; int32_t mCursorPos = 0; int32_t mRenderOffset = 0; int32_t mCurrentOpen = -1; bool mNeedRedraw = true; - bool mSetActivePluginsMode = false; - bool mActivePluginsDirty = false; + bool mPluginListDirty = false; bool mLastInputWasOnWiimote = false; -}; +}; \ No newline at end of file diff --git a/source/utils/config/ConfigRendererStates.cpp b/source/utils/config/ConfigRendererStates.cpp new file mode 100644 index 0000000..6d0b0c4 --- /dev/null +++ b/source/utils/config/ConfigRendererStates.cpp @@ -0,0 +1,177 @@ +#include "ConfigRendererStates.h" +#include "ConfigDisplayItem.h" +#include "ConfigRenderer.h" +#include "utils/DrawUtils.h" +#include "utils/StringTools.h" +#include "utils/input/Input.h" + +std::function DefaultListState::GetConfigFilter() const { + return [](const auto &item) { return item.isActivePlugin(); }; +} + +bool DefaultListState::IsMainView() const { + return true; +} + +bool DefaultListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_DOWN) { + // Hidden Combo: L + R + Down + constexpr auto COMBO_HOLD = Input::eButtons::BUTTON_L | Input::eButtons::BUTTON_R; + if ((input.data.buttons_h & COMBO_HOLD) == COMBO_HOLD) { + // Switch to Heap Tracking Mode + if (!renderer.GetConfigItems().empty()) { + renderer.SetListState(std::make_unique()); + return true; + } + } + return false; // Allow scrolling + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + if (!renderer.GetConfigItems().empty()) { + renderer.EnterSelectedCategory(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_X) { + if (!renderer.GetConfigItems().empty()) { + renderer.SetListState(std::make_unique()); + } + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + renderer.Exit(); + return true; + } + + return false; +} + +std::string DefaultListState::GetTitle() const { + return "Wii U Plugin System Config Menu"; +} + +std::string DefaultListState::GetBottomBar(bool isWiimote) const { + return string_format("\ue000 Select | %s Manage plugins", isWiimote ? "\uE048" : "\uE002"); +} + +bool DefaultListState::RenderItemIcon(const ConfigDisplayItem & /*item*/, int /*x*/, int /*y*/) const { + return false; +} + + +bool ActivePluginsListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + auto &items = renderer.GetFilteredConfigItems(); + int pos = renderer.GetCursorPos(); + if (pos >= 0 && static_cast(pos) < items.size()) { + items[pos].get().toggleIsActivePlugin(); + renderer.SetPluginsListDirty(true); + renderer.RequestRedraw(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { + // Apply and Reload + renderer.ExitWithReload(); + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + // Abort / Reset + for (auto &item : renderer.GetFilteredConfigItems()) { + item.get().resetIsActivePlugin(); + } + renderer.SetPluginsListDirty(false); + renderer.SetListState(std::make_unique()); + return true; + } + + return false; +} + +std::string ActivePluginsListState::GetTitle() const { + return "Please select the plugins that should be active"; +} + +std::string ActivePluginsListState::GetBottomBar(bool /*isWiimote*/) const { + return "\uE000 Toggle | \uE001 Abort | \uE045 Apply"; +} + +bool ActivePluginsListState::RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const { + DrawUtils::setFontSize(24); + if (item.isActivePlugin()) { + DrawUtils::print(x, y, "\u25C9"); + } else { + DrawUtils::print(x, y, "\u25CE"); + } + return true; +} + +std::function ActivePluginsListState::GetConfigFilter() const { + return [](const auto &) { return true; }; +} + +bool ActivePluginsListState::IsMainView() const { + return false; +} + +bool HeapTrackingListState::HandleInput(ConfigRenderer &renderer, const Input &input) { + if (input.data.buttons_d & Input::eButtons::BUTTON_A) { + auto &items = renderer.GetFilteredConfigItems(); + int pos = renderer.GetCursorPos(); + if (pos >= 0 && static_cast(pos) < items.size()) { + items[pos].get().toggleIsHeapTrackingEnabled(); + renderer.SetPluginsListDirty(true); + renderer.RequestRedraw(); + } + return true; + } + + if (input.data.buttons_d & Input::eButtons::BUTTON_PLUS) { + // Apply and Reload + renderer.ExitWithReload(); + return true; + } + + if (input.data.buttons_d & (Input::eButtons::BUTTON_B | Input::eButtons::BUTTON_HOME)) { + // Abort + for (auto &item : renderer.GetFilteredConfigItems()) { + item.get().resetIsHeapTrackingEnabled(); + } + renderer.SetPluginsListDirty(false); + renderer.SetListState(std::make_unique()); + return true; + } + + return false; +} + +std::string HeapTrackingListState::GetTitle() const { + return "Select plugins to enable Heap Tracking"; +} + +std::string HeapTrackingListState::GetBottomBar(bool /*isWiimote*/) const { + return "\uE000 Toggle | \uE001 Abort | \uE045 Apply"; +} + +bool HeapTrackingListState::RenderItemIcon(const ConfigDisplayItem &item, const int x, const int y) const { + DrawUtils::setFontSize(24); + if (item.isHeapTrackingEnabled()) { + DrawUtils::print(x, y, "\u25C9"); + } else { + DrawUtils::print(x, y, "\u25CE"); + } + return true; +} + +std::function HeapTrackingListState::GetConfigFilter() const { + return [](const auto &item) { return item.isActivePlugin(); }; +} + +bool HeapTrackingListState::IsMainView() const { + return false; +} diff --git a/source/utils/config/ConfigRendererStates.h b/source/utils/config/ConfigRendererStates.h new file mode 100644 index 0000000..6cf5f59 --- /dev/null +++ b/source/utils/config/ConfigRendererStates.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +class ConfigRenderer; +class Input; +class ConfigDisplayItem; + +class ConfigListState { +public: + virtual ~ConfigListState() = default; + + virtual bool HandleInput(ConfigRenderer &renderer, const Input &input) = 0; + + virtual std::string GetTitle() const = 0; + virtual std::string GetBottomBar(bool isWiimote) const = 0; + virtual bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const = 0; + + virtual std::function GetConfigFilter() const = 0; + virtual bool IsMainView() const = 0; +}; + +class DefaultListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; + +class ActivePluginsListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; + +class HeapTrackingListState : public ConfigListState { +public: + bool HandleInput(ConfigRenderer &renderer, const Input &input) override; + std::string GetTitle() const override; + std::string GetBottomBar(bool isWiimote) const override; + bool RenderItemIcon(const ConfigDisplayItem &item, int x, int y) const override; + std::function GetConfigFilter() const override; + bool IsMainView() const override; +}; \ No newline at end of file diff --git a/source/utils/config/ConfigUtils.cpp b/source/utils/config/ConfigUtils.cpp index 19eb871..c0d6cc9 100644 --- a/source/utils/config/ConfigUtils.cpp +++ b/source/utils/config/ConfigUtils.cpp @@ -133,7 +133,7 @@ void ConfigUtils::displayMenu() { config = make_unique_nothrow(info.name); } - configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded()); + configs.emplace_back(info, std::move(config), plugin.isLinkedAndLoaded(), plugin.isUsingTrackingPluginHeapMemoryAllocator()); } // Sort Configs by name @@ -226,7 +226,7 @@ void ConfigUtils::displayMenu() { std::vector newActivePluginsList; - if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetActivePluginsIfChanged(newActivePluginsList)) { + if (subStateReturnValue == SUB_STATE_RETURN_WITH_PLUGIN_RELOAD && renderer.GetPluginsListIfChanged(newActivePluginsList)) { startTime = OSGetTime(); renderBasicScreen("Applying changes, app will now restart..."); @@ -243,6 +243,7 @@ void ConfigUtils::displayMenu() { } } } + gLoadOnNextLaunch = newActivePluginsList; WUPSBackendSettings::SetInactivePluginFilenames(newInactivePluginsList); if (!WUPSBackendSettings::SaveSettings()) { diff --git a/source/utils/exports.cpp b/source/utils/exports.cpp index a8ac206..7518bf8 100644 --- a/source/utils/exports.cpp +++ b/source/utils/exports.cpp @@ -38,7 +38,17 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back for (const auto &pluginData : gLoadedData) { if (pluginData->getHandle() == handle) { - gLoadOnNextLaunch.emplace_back(pluginData, true); + bool heapTrackingActive = false; +#ifdef DEBUG + heapTrackingActive = true; +#endif + for (const auto &plugin : gLoadedPlugins) { + if (plugin.getPluginDataCopy()->getHandle() == handle) { + heapTrackingActive = plugin.isUsingTrackingPluginHeapMemoryAllocator(); + break; + } + } + gLoadOnNextLaunch.emplace_back(pluginData, true, heapTrackingActive); found = true; break; } @@ -52,7 +62,7 @@ extern "C" PluginBackendApiErrorType WUPSLoadAndLinkByDataHandle(const wups_back // Keep inactive plugins loaded. Duplicates will be eliminated by comparing name/author for (const auto &plugin : gLoadedPlugins) { if (!plugin.isLinkedAndLoaded()) { - gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false); + gLoadOnNextLaunch.emplace_back(plugin.getPluginDataCopy(), false, false); } } diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index 8b37a1d..32304f5 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -6,8 +6,10 @@ #include "json.hpp" #include "logger.h" +#include #include +#include #include #include @@ -198,4 +200,145 @@ std::vector getNonBaseAromaPluginFilenames(std::string_view basePat } } return result; +} + +__attribute__((noinline)) +std::vector +CaptureStackTrace(uint32_t maxDepth) { + std::vector trace; + trace.reserve(maxDepth); + + uint32_t *stackPointer; + // Grab the current Stack Pointer (r1) + asm volatile("mr %0, 1" + : "=r"(stackPointer)); + + for (uint32_t i = 0; i < maxDepth + 1; ++i) { + // Basic alignment check + if (!stackPointer || (reinterpret_cast(stackPointer) & 0x3)) { + break; + } + + uint32_t backChain = stackPointer[0]; + uint32_t returnAddr = stackPointer[1]; + + if (returnAddr == 0) break; + if (i != 0) { + trace.push_back(returnAddr); + } + + if (backChain == 0) break; + stackPointer = reinterpret_cast(backChain); + } + + return trace; +} + +#define SC17_FindClosestSymbol ((uint32_t(*)(uint32_t addr, int32_t * outDistance, char *symbolNameBuffer, uint32_t symbolNameBufferLength, char *moduleNameBuffer, uint32_t moduleNameBufferLength))(0x101C400 + 0x1f934)) + +std::string getModuleAndSymbolName(uint32_t addr) { + int distance = 0; + char moduleName[50] = {}; + char symbolName[256] = {}; + + if (SC17_FindClosestSymbol(addr, &distance, symbolName, sizeof(symbolName) - 1, moduleName, sizeof(moduleName) - 1) != 0) { + return string_format("0x%08X", addr); + } else { + moduleName[sizeof(moduleName) - 1] = '\0'; + symbolName[sizeof(symbolName) - 1] = '\0'; + return string_format("%s|%s+0x%X", + moduleName, + symbolName, + distance); + } +} + +void PrintCapturedStackTrace(const std::span trace) { + if (trace.empty()) { + DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐"); + DEBUG_FUNCTION_LINE_INFO("│ "); + DEBUG_FUNCTION_LINE_INFO("└────────────────────────────────────────────────────────────┘"); + return; + } + + DEBUG_FUNCTION_LINE_INFO("┌────────────────────── CAPTURED TRACE ──────────────────────┐"); + for (size_t i = 0; i < trace.size(); ++i) { + uint32_t addr = trace[i]; + int distance = 0; + char moduleName[50] = {}; + char symbolName[256] = {}; + + if (SC17_FindClosestSymbol(addr, &distance, symbolName, sizeof(symbolName) - 1, moduleName, sizeof(moduleName) - 1) != 0) { + DEBUG_FUNCTION_LINE_INFO("│ [%02d] 0x%08X", i, addr); + } else { + moduleName[sizeof(moduleName) - 1] = '\0'; + symbolName[sizeof(symbolName) - 1] = '\0'; + DEBUG_FUNCTION_LINE_INFO("│ [%02d] %s : %s + 0x%X (0x%08X)", + i, + moduleName, + symbolName, + distance, + addr); + } + } + + DEBUG_FUNCTION_LINE_INFO("└────────────────────────────────────────────────────────────┘"); +} + +std::string hookNameToString(const wups_loader_hook_type_t type) { + switch (type) { + case WUPS_LOADER_HOOK_INIT_WUT_MALLOC: + return "WUPS_LOADER_HOOK_INIT_WUT_MALLOC"; + case WUPS_LOADER_HOOK_FINI_WUT_MALLOC: + return "WUPS_LOADER_HOOK_FINI_WUT_MALLOC"; + case WUPS_LOADER_HOOK_INIT_WUT_NEWLIB: + return "WUPS_LOADER_HOOK_INIT_WUT_NEWLIB"; + case WUPS_LOADER_HOOK_FINI_WUT_NEWLIB: + return "WUPS_LOADER_HOOK_FINI_WUT_NEWLIB"; + case WUPS_LOADER_HOOK_INIT_WUT_STDCPP: + return "WUPS_LOADER_HOOK_INIT_WUT_STDCPP"; + case WUPS_LOADER_HOOK_FINI_WUT_STDCPP: + return "WUPS_LOADER_HOOK_FINI_WUT_STDCPP"; + case WUPS_LOADER_HOOK_INIT_WUT_DEVOPTAB: + return "WUPS_LOADER_HOOK_INIT_WUT_DEVOPTAB"; + case WUPS_LOADER_HOOK_FINI_WUT_DEVOPTAB: + return "WUPS_LOADER_HOOK_FINI_WUT_DEVOPTAB"; + case WUPS_LOADER_HOOK_INIT_WUT_SOCKETS: + return "WUPS_LOADER_HOOK_INIT_WUT_SOCKETS"; + case WUPS_LOADER_HOOK_FINI_WUT_SOCKETS: + return "WUPS_LOADER_HOOK_FINI_WUT_SOCKETS"; + case WUPS_LOADER_HOOK_INIT_WRAPPER: + return "WUPS_LOADER_HOOK_INIT_WRAPPER"; + case WUPS_LOADER_HOOK_FINI_WRAPPER: + return "WUPS_LOADER_HOOK_FINI_WRAPPER"; + case WUPS_LOADER_HOOK_GET_CONFIG_DEPRECATED: + return "WUPS_LOADER_HOOK_GET_CONFIG_DEPRECATED"; + case WUPS_LOADER_HOOK_CONFIG_CLOSED_DEPRECATED: + return "WUPS_LOADER_HOOK_CONFIG_CLOSED_DEPRECATED"; + case WUPS_LOADER_HOOK_INIT_STORAGE_DEPRECATED: + return "WUPS_LOADER_HOOK_INIT_STORAGE_DEPRECATED"; + case WUPS_LOADER_HOOK_INIT_PLUGIN: + return "WUPS_LOADER_HOOK_INIT_PLUGIN"; + case WUPS_LOADER_HOOK_DEINIT_PLUGIN: + return "WUPS_LOADER_HOOK_DEINIT_PLUGIN"; + case WUPS_LOADER_HOOK_APPLICATION_STARTS: + return "WUPS_LOADER_HOOK_APPLICATION_STARTS"; + case WUPS_LOADER_HOOK_RELEASE_FOREGROUND: + return "WUPS_LOADER_HOOK_RELEASE_FOREGROUND"; + case WUPS_LOADER_HOOK_ACQUIRED_FOREGROUND: + return "WUPS_LOADER_HOOK_ACQUIRED_FOREGROUND"; + case WUPS_LOADER_HOOK_APPLICATION_REQUESTS_EXIT: + return "WUPS_LOADER_HOOK_APPLICATION_REQUESTS_EXIT"; + case WUPS_LOADER_HOOK_APPLICATION_ENDS: + return "WUPS_LOADER_HOOK_APPLICATION_ENDS"; + case WUPS_LOADER_HOOK_INIT_STORAGE: + return "WUPS_LOADER_HOOK_INIT_STORAGE"; + case WUPS_LOADER_HOOK_INIT_CONFIG: + return "WUPS_LOADER_HOOK_INIT_CONFIG"; + case WUPS_LOADER_HOOK_INIT_BUTTON_COMBO: + return "WUPS_LOADER_HOOK_INIT_BUTTON_COMBO"; + case WUPS_LOADER_HOOK_INIT_WUT_THREAD: + return "WUPS_LOADER_HOOK_INIT_WUT_THREAD"; + } + return ""; } \ No newline at end of file diff --git a/source/utils/utils.h b/source/utils/utils.h index 3206f5c..d86b641 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -4,6 +4,8 @@ #include +#include + #include #include #include @@ -161,4 +163,12 @@ UtilsIOError ParseJsonFromFile(const std::string &filePath, nlohmann::json &outJ std::vector getPluginFilePaths(std::string_view basePath); -std::vector getNonBaseAromaPluginFilenames(std::string_view basePath); \ No newline at end of file +std::vector getNonBaseAromaPluginFilenames(std::string_view basePath); + +std::vector CaptureStackTrace(uint32_t maxDepth); + +std::string getModuleAndSymbolName(uint32_t addr); + +void PrintCapturedStackTrace(std::span trace); + +std::string hookNameToString(wups_loader_hook_type_t type); \ No newline at end of file