diff --git a/Dockerfile b/Dockerfile index bc5df57..2df6487 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ FROM ghcr.io/wiiu-env/devkitppc:20260225 -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/wiiumodulesystem:reentfix-dev-20260403-5ca1144 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:abifix-dev-20260408-77ab748 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libmappedmemory:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libwupsbackend:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libnotifications:20260331 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libbuttoncombo:20260331 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libiopshell:20260318 /artifacts $DEVKITPRO WORKDIR project diff --git a/source/hooks.cpp b/source/hooks.cpp index b2e4784..edfa10b 100644 --- a/source/hooks.cpp +++ b/source/hooks.cpp @@ -7,11 +7,13 @@ #include "utils/StorageUtilsDeprecated.h" #include "utils/buttoncombo/ButtonComboUtils.h" #include "utils/logger.h" +#include "utils/reent.h" #include "utils/storage/StorageUtils.h" #include "utils/utils.h" #include #include +#include #include #include @@ -102,7 +104,8 @@ void CallHook(const PluginContainer &plugin, const wups_loader_hook_type_t hook_ } case WUPS_LOADER_HOOK_INIT_CONFIG: { wups_loader_init_config_args_t args{.arg_version = 1, .plugin_identifier = plugin.getHandle()}; - auto res = ((WUPSConfigAPIStatus(*)(wups_loader_init_config_args_t))((uint32_t *) func_ptr))(args); + // clang-format off + auto res = ((WUPSConfigAPIStatus (*)(wups_loader_init_config_args_t))((uint32_t *) func_ptr))(args); // clang-format on if (res != WUPSCONFIG_API_RESULT_SUCCESS) { // TODO: More error handling? Notification? @@ -131,7 +134,8 @@ void CallHook(const PluginContainer &plugin, const wups_loader_hook_type_t hook_ args.check_button_combo_available_function_ptr = &ButtonComboUtils::API::CheckComboAvailable; args.detect_button_combo_blocking_function_ptr = &ButtonComboUtils::API::DetectButtonCombo_Blocking; - auto res = ((WUPSButtonCombo_Error(*)(wups_loader_init_button_combo_args_t))((uint32_t *) func_ptr))(args); + // clang-format off + auto res = ((WUPSButtonCombo_Error (*)(wups_loader_init_button_combo_args_t))((uint32_t *) func_ptr))(args); // clang-format on if (res != WUPS_BUTTON_COMBO_ERROR_SUCCESS) { // TODO: More error handling? Notification? @@ -139,6 +143,23 @@ void CallHook(const PluginContainer &plugin, const wups_loader_hook_type_t hook_ } break; } + case WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS: { + if (plugin.getMetaInformation().getWUPSVersion() <= WUPSVersion(0, 9, 0)) { + break; + } + wups_loader_init_reent_args_t_ args; + args.version = WUPS_REENT_CUR_API_VERSION; + args.restore_head_ptr = &wups_backend_restore_head; + args.get_context_ptr = &wups_backend_get_context; + args.set_sentinel_ptr = &wups_backend_set_sentinel; + args.add_reent_context_ptr = &wups_backend_register_context; + + // clang-format off + ((void (*)(wups_loader_init_reent_args_t_))((uint32_t *) func_ptr))(args); + // clang-format on + + break; + } default: { DEBUG_FUNCTION_LINE_ERR("######################################"); DEBUG_FUNCTION_LINE_ERR("Hook is not implemented %s [%d]", hookNameToString(hook_type), hook_type); diff --git a/source/main.cpp b/source/main.cpp index 1d2d68e..d3be681 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -17,6 +17,7 @@ #include "utils/WUPSBackendSettings.h" #include "utils/input/VPADInput.h" #include "utils/logger.h" +#include "utils/reent.h" #include "utils/utils.h" #include @@ -164,7 +165,6 @@ WUMS_APPLICATION_ENDS() { deinitLogging(); } -void CheckCleanupCallbackUsage(const std::vector &plugins); void CleanupPlugins(std::vector &pluginsToDeinit); @@ -190,6 +190,8 @@ WUMS_APPLICATION_STARTS() { initLogging(); + ClearDanglingReentPtr(); + std::lock_guard lock(gLoadedDataMutex); std::vector newLoadedPlugins; @@ -332,6 +334,7 @@ WUMS_APPLICATION_STARTS() { // PluginManagement::memsetBSS(plugins); const auto &needsInitsCheck = [](const PluginContainer &container) { return !container.isInitDone(); }; + CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS, needsInitsCheck); CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_INIT_WUT_MALLOC, needsInitsCheck); CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_INIT_WUT_NEWLIB, needsInitsCheck); CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_INIT_WUT_STDCPP, needsInitsCheck); @@ -367,20 +370,21 @@ void CleanupPlugins(std::vector &pluginsToDeinit) { DEBUG_FUNCTION_LINE_INFO("De-init plugin %s from %s. PluginData: %08X", pluginContainer.getMetaInformation().getName().c_str(), pluginContainer.getMetaInformation().getAuthor().c_str(), pluginContainer.getPluginDataCopy()->getHandle()); } - currentThread->reserved[4] = 0; + { // legacy reent code + currentThread->reserved[4] = 0; + } CallHook(pluginsToDeinit, WUPS_LOADER_HOOK_DEINIT_PLUGIN); CallHook(pluginsToDeinit, WUPS_LOADER_HOOK_FINI_WRAPPER); - CheckCleanupCallbackUsage(pluginsToDeinit); - - if (currentThread->cleanupCallback != saved_cleanupCallback) { - DEBUG_FUNCTION_LINE_WARN("WUPS_LOADER_HOOK_DEINIT_PLUGIN overwrote the ThreadCleanupCallback, we need to restore it!\n"); - OSSetThreadCleanupCallback(OSGetCurrentThread(), saved_cleanupCallback); + { // legacy reent code + if (currentThread->cleanupCallback != saved_cleanupCallback) { + DEBUG_FUNCTION_LINE_WARN("WUPS_LOADER_HOOK_DEINIT_PLUGIN overwrote the ThreadCleanupCallback, we need to restore it!\n"); + OSSetThreadCleanupCallback(OSGetCurrentThread(), saved_cleanupCallback); + } + currentThread->reserved[4] = saved_reent; } - currentThread->reserved[4] = saved_reent; - DEBUG_FUNCTION_LINE("Restore function patches of plugins."); PluginManagement::RestoreFunctionPatches(pluginsToDeinit); @@ -392,6 +396,8 @@ void CleanupPlugins(std::vector &pluginsToDeinit) { plugin.DeinitButtonComboData(); } + ClearReentDataForPlugins(pluginsToDeinit); + // Check for leaked memory for (auto &plugin : pluginsToDeinit) { if (const auto tracking = plugin.getTrackingMemoryAllocator()) { @@ -406,34 +412,4 @@ void CleanupPlugins(std::vector &pluginsToDeinit) { } } } -} -void CheckCleanupCallbackUsage(const std::vector &plugins) { - auto *curThread = OSGetCurrentThread(); - for (const auto &cur : plugins) { - if (!cur.isLinkedAndLoaded()) { - continue; - } - - const auto textSection = cur.getPluginLinkInformation().getSectionInfo(".text"); - if (!textSection) { - continue; - } - const uint32_t startAddress = textSection->getAddress(); - const uint32_t endAddress = textSection->getAddress() + textSection->getSize(); - auto *pluginName = cur.getMetaInformation().getName().c_str(); - { - __OSLockScheduler(curThread); - const int state = OSDisableInterrupts(); - OSThread *t = *reinterpret_cast(0x100567F8); - while (t) { - const auto address = reinterpret_cast(t->cleanupCallback); - if (address != 0 && address >= startAddress && address <= endAddress) { - OSReport("[WARN] PluginBackend: Thread 0x%p is using a function from plugin %s for the threadCleanupCallback\n", t, pluginName); - } - t = t->activeLink.next; - } - OSRestoreInterrupts(state); - __OSUnlockScheduler(curThread); - } - } -} +} \ No newline at end of file diff --git a/source/patcher/hooks_patcher_static.cpp b/source/patcher/hooks_patcher_static.cpp index 5f06921..1b68276 100644 --- a/source/patcher/hooks_patcher_static.cpp +++ b/source/patcher/hooks_patcher_static.cpp @@ -8,6 +8,7 @@ #include "plugin/SectionInfo.h" #include "utils/config/ConfigUtils.h" #include "utils/logger.h" +#include "utils/reent.h" #include #include @@ -276,6 +277,15 @@ DECL_FUNCTION(uint32_t, KiGetAppSymbolName, uint32_t addr, char *buffer, int32_t #pragma GCC pop_options + +DECL_FUNCTION(uint32_t, SomeExitHook) { + // Thats the last thing called in __PPCExit + const auto res = real_SomeExitHook(); + + MarkReentNodesForDeletion(); + return res; +} + function_replacement_data_t method_hooks_static[] __attribute__((section(".data"))) = { REPLACE_FUNCTION(GX2SwapScanBuffers, LIBRARY_GX2, GX2SwapScanBuffers), REPLACE_FUNCTION(GX2SetTVBuffer, LIBRARY_GX2, GX2SetTVBuffer), @@ -287,6 +297,7 @@ function_replacement_data_t method_hooks_static[] __attribute__((section(".data" REPLACE_FUNCTION(WPADRead, LIBRARY_PADSCORE, WPADRead), REPLACE_FUNCTION_VIA_ADDRESS(SC17_FindClosestSymbol, 0xfff10218, 0xfff10218), REPLACE_FUNCTION_VIA_ADDRESS(KiGetAppSymbolName, 0xfff0e3a0, 0xfff0e3a0), + REPLACE_FUNCTION_VIA_ADDRESS(SomeExitHook, 0x3201C400 + 0x40b4c, 0x101C400 + 0x40b4c), }; uint32_t method_hooks_static_size __attribute__((section(".data"))) = sizeof(method_hooks_static) / sizeof(function_replacement_data_t); diff --git a/source/plugin/PluginMetaInformationFactory.cpp b/source/plugin/PluginMetaInformationFactory.cpp index 0ae8044..3bbdb46 100644 --- a/source/plugin/PluginMetaInformationFactory.cpp +++ b/source/plugin/PluginMetaInformationFactory.cpp @@ -123,6 +123,8 @@ std::optional PluginMetaInformationFactory::loadPlugin(st pluginInfo.setWUPSVersion(0, 8, 2); } else if (value == "0.9.0") { pluginInfo.setWUPSVersion(0, 9, 0); + } else if (value == "0.9.1") { + pluginInfo.setWUPSVersion(0, 9, 1); } else { error = PLUGIN_PARSE_ERROR_INCOMPATIBLE_VERSION; DEBUG_FUNCTION_LINE_ERR("Warning: Ignoring plugin - Unsupported WUPS version: %s.", value.c_str()); diff --git a/source/utils/logger.h b/source/utils/logger.h index b9da9fa..c1f51b7 100644 --- a/source/utils/logger.h +++ b/source/utils/logger.h @@ -1,6 +1,5 @@ #pragma once - #include #include #include @@ -9,19 +8,24 @@ extern "C" { #endif -#define LOG_APP_TYPE "M" -#define LOG_APP_NAME "wupsbackend" +#define LOG_APP_TYPE "M" +#define LOG_APP_NAME "wupsbackend" -#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) -#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) -#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", FMT, ##ARGS) +#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX_DEFAULT(LOG_FUNC, "", "", "", FMT, ##ARGS) -#define LOG_EX_DEFAULT(LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ##ARGS) +#define CONSOLE_COLOR_RED "\033[31m" +#define CONSOLE_COLOR_YELLOW "\033[33m" +#define CONSOLE_COLOR_CYAN "\033[36m" +#define CONSOLE_COLOR_RESET "\033[0m" -#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_LEVEL, LINE_END, FMT, ARGS...) \ - do { \ - LOG_FUNC("[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ +#define LOG_EX_DEFAULT(LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ARGS...) LOG_EX(__FILENAME__, __FUNCTION__, __LINE__, LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ##ARGS) + +#define LOG_EX(FILENAME, FUNCTION, LINE, LOG_FUNC, LOG_COLOR, LOG_LEVEL, LINE_END, FMT, ARGS...) \ + do { \ + LOG_FUNC(LOG_COLOR "[(%s)%18s][%23s]%30s@L%04d: " LOG_LEVEL "" FMT "" LINE_END, LOG_APP_TYPE, LOG_APP_NAME, FILENAME, FUNCTION, LINE, ##ARGS); \ } while (0) #ifdef DEBUG @@ -38,11 +42,11 @@ extern "C" { #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_RED, "## ERROR## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_YELLOW, "##WARN ## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, CONSOLE_COLOR_CYAN, "##INFO ## ", CONSOLE_COLOR_RESET, FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS); +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET, FMT, ##ARGS); #else @@ -54,11 +58,11 @@ extern "C" { #define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0) -#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##WARN ## ", "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##INFO ## ", "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_YELLOW, "##WARN ## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) +#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, CONSOLE_COLOR_CYAN, "##INFO ## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS) -#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS); +#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, CONSOLE_COLOR_RED, "##ERROR## ", CONSOLE_COLOR_RESET "\n", FMT, ##ARGS); #endif diff --git a/source/utils/reent.cpp b/source/utils/reent.cpp new file mode 100644 index 0000000..6fa379b --- /dev/null +++ b/source/utils/reent.cpp @@ -0,0 +1,311 @@ +#include "reent.h" + +#include "logger.h" +#include "plugin/PluginContainer.h" +#include "plugin/SectionInfo.h" + +#include +#include +#include +#include +#include +#include + +#define __WUPS_CONTEXT_THREAD_SPECIFIC_ID 0 +#define WUPS_REENT_ALLOC_SENTINEL ((__wups_reent_node *) 0xFFFFFFFF) +#define WUPS_REENT_NODE_VERSION 1 +#define WUPS_REENT_NODE_MAGIC 0x57555053 // WUPS + +void wups_set_thread_specific_ex(const int id, void *value, OSThread *thread) { + if (thread != nullptr) { + if (id == 0) { + thread->reserved[0] = reinterpret_cast(value); + } else { + OSReport("wups_set_thread_specific: invalid id\n"); + OSFatal("wups_set_thread_specific: invalid id"); + } + } else { + OSReport("wups_set_thread_specific: invalid thread\n"); + OSFatal("wups_set_thread_specific: invalid thread"); + } +} + +void *wups_get_thread_specific_ex(const int id, const OSThread *thread) { + if (thread != nullptr) { + if (id == 0) { + return reinterpret_cast(thread->reserved[0]); + } else { + OSReport("wups_get_thread_specific: invalid id\n"); + OSFatal("wups_get_thread_specific: invalid id"); + } + } else { + OSReport("wups_get_thread_specific: invalid thread\n"); + OSFatal("wups_get_thread_specific: invalid thread\n"); + } + return nullptr; +} + +void wups_set_thread_specific(int id, void *value) { + return wups_set_thread_specific_ex(id, value, OSGetCurrentThread()); +} + +void *wups_get_thread_specific(int id) { + return wups_get_thread_specific_ex(id, OSGetCurrentThread()); +} + +struct __wups_reent_node { + uint32_t magic; + uint32_t version; + __wups_reent_node *next; + + const void *pluginId; + void *reentPtr; // The ABI payload + void (*cleanupFn)(void *); // The trampoline to clean up the payload + OSThreadCleanupCallbackFn savedCleanup; +}; + +namespace { + std::vector<__wups_reent_node *> sGlobalNodesCopy; + std::vector<__wups_reent_node *> sGlobalNodes; + std::recursive_mutex sGlobalNodesMutex; +} // namespace + +void MarkReentNodesForDeletion() { + sGlobalNodesCopy = std::move(sGlobalNodes); +} + +void ClearDanglingReentPtr() { + for (auto nodeToFree : sGlobalNodesCopy) { + if (nodeToFree->cleanupFn) { + nodeToFree->cleanupFn(nodeToFree->reentPtr); + } + free(nodeToFree); + } + sGlobalNodesCopy.clear(); +} + +static void __wups_thread_cleanup(OSThread *thread, void *stack) { + auto *head = static_cast<__wups_reent_node *>(wups_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID)); + + if (!head || head == WUPS_REENT_ALLOC_SENTINEL || head->magic != WUPS_REENT_NODE_MAGIC) { + return; + } + + OSThreadCleanupCallbackFn savedCleanup = head->savedCleanup; + + // Set to effective global during free to prevent malloc re-entrancy loops + wups_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, WUPS_REENT_ALLOC_SENTINEL); + + auto *curr = head; + while (curr) { + __wups_reent_node *next = curr->next; + + if (curr->cleanupFn) { + curr->cleanupFn(curr->reentPtr); + } + + { + std::lock_guard lock(sGlobalNodesMutex); + std::erase(sGlobalNodes, curr); + } + + free(curr); + curr = next; + } + + wups_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, nullptr); + + // Chain to previous OS callback + if (savedCleanup) { + savedCleanup(thread, stack); + } +} + +void *wups_backend_get_context(const void *pluginId, wups_loader_init_reent_errors_t_ *outError) { + if (!outError) { + OSFatal("Called wups_backend_get_context with error nullptr"); + return nullptr; + } + + if (!OSGetCurrentThread()) { + *outError = WUPSReent_ERROR_NO_THREAD; + return nullptr; + } + + auto *head = static_cast<__wups_reent_node *>(wups_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID)); + + if (head == WUPS_REENT_ALLOC_SENTINEL) { + *outError = WUPSReent_ERROR_GLOBAL_REENT_REQUESTED; + return nullptr; + } + if (head && head->magic != WUPS_REENT_NODE_MAGIC) { + *outError = WUPSReent_ERROR_GLOBAL_REENT_REQUESTED; + return nullptr; + } + + const __wups_reent_node *curr = head; + while (curr) { + if (curr->version >= 1 && curr->pluginId == pluginId) { + return curr->reentPtr; + } + curr = curr->next; + } + + *outError = WUPSReent_ERROR_NONE; + + return nullptr; +} + +void *wups_backend_set_sentinel() { + auto *head = wups_get_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID); + wups_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, WUPS_REENT_ALLOC_SENTINEL); + return head; +} + +void wups_backend_restore_head(void *oldHead) { + wups_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, oldHead); +} + +bool wups_backend_register_context(const void *pluginId, void *reentPtr, void (*cleanupFn)(void *), void *oldHeadVoid) { + auto *oldHead = static_cast<__wups_reent_node *>(oldHeadVoid); + + auto *newNode = static_cast<__wups_reent_node *>(malloc(sizeof(__wups_reent_node))); + if (!newNode) { + return false; + } + + newNode->magic = WUPS_REENT_NODE_MAGIC; + newNode->version = WUPS_REENT_NODE_VERSION; + newNode->next = oldHead; + newNode->pluginId = pluginId; + newNode->reentPtr = reentPtr; + newNode->cleanupFn = cleanupFn; + newNode->savedCleanup = nullptr; + + if (oldHead == nullptr || oldHead == WUPS_REENT_ALLOC_SENTINEL || oldHead->magic != WUPS_REENT_NODE_MAGIC || oldHead->version < WUPS_REENT_NODE_VERSION) { + newNode->savedCleanup = OSSetThreadCleanupCallback(OSGetCurrentThread(), &__wups_thread_cleanup); + } else { + newNode->savedCleanup = oldHead->savedCleanup; + oldHead->savedCleanup = nullptr; + } + + wups_set_thread_specific(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, newNode); + + { + std::lock_guard lock(sGlobalNodesMutex); + sGlobalNodes.push_back(newNode); + } + + return true; +} + +void ClearReentDataForPlugins(const std::vector &plugins) { + auto *curThread = OSGetCurrentThread(); + + for (const auto &cur : plugins) { + if (!cur.isLinkedAndLoaded()) { + continue; + } + + if (cur.getMetaInformation().getWUPSVersion() <= WUPSVersion(0, 9, 0)) { + continue; + } + + const auto dataMemory = cur.getPluginLinkInformation().getDataMemory(); + const auto startAddress = reinterpret_cast(dataMemory.data()); + const auto endAddress = reinterpret_cast(dataMemory.data()) + dataMemory.size(); + + // Zero-allocation deferred free list + __wups_reent_node *deferredFreeHead = nullptr; + + struct PendingRestore { + OSThread *thread; + OSThreadCleanupCallbackFn callback; + }; + std::vector pendingRestores; + constexpr int PENDING_RESTORES_SIZE = 128; + // Pre-allocate to prevent malloc() from firing while the scheduler is locked! + pendingRestores.reserve(PENDING_RESTORES_SIZE); + { + // Acquire GLOBAL scheduler lock + const int state = OSDisableInterrupts(); + __OSLockScheduler(curThread); + + OSThread *t = *reinterpret_cast(0x100567F8); + + while (t) { + auto *head = static_cast<__wups_reent_node *>(wups_get_thread_specific_ex(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, t)); + + // Safety checks with Sentinel/Magic + if (!head || head == WUPS_REENT_ALLOC_SENTINEL || head->magic != WUPS_REENT_NODE_MAGIC) { + t = t->activeLink.next; + continue; + } + + __wups_reent_node *prev = nullptr; + auto *curr = head; + + while (curr) { + __wups_reent_node *next = curr->next; + auto pluginIdAddr = reinterpret_cast(curr->pluginId); + + // plugin id lives in the .data/.bss section of a plugin + if (pluginIdAddr >= startAddress && pluginIdAddr < endAddress) { + // remove from linked list + if (prev) { + prev->next = next; + } else { + head = next; + if (curr->savedCleanup) { + if (head) { + head->savedCleanup = curr->savedCleanup; + } else { + // No WUPS nodes left, mark for restoring. + if (pendingRestores.size() == PENDING_RESTORES_SIZE) { + OSFatal("WUPSBackend pendingRestores size limit hit"); + } + pendingRestores.push_back({t, curr->savedCleanup}); + } + } + } + + curr->next = deferredFreeHead; + deferredFreeHead = curr; + } else { + prev = curr; + } + curr = next; + } + + // Restore the updated head to the thread + wups_set_thread_specific_ex(__WUPS_CONTEXT_THREAD_SPECIFIC_ID, head, t); + t = t->activeLink.next; + } + + __OSUnlockScheduler(curThread); + OSRestoreInterrupts(state); + } + + for (const auto &restore : pendingRestores) { + DEBUG_FUNCTION_LINE_VERBOSE("Set cleanup function for thread %p to %p", restore.thread, restore.callback); + OSSetThreadCleanupCallback(restore.thread, restore.callback); + } + + // Free removed entries + auto *oldHead = wups_backend_set_sentinel(); + __wups_reent_node *nodeToFree = deferredFreeHead; + while (nodeToFree) { + __wups_reent_node *nextNode = nodeToFree->next; + if (nodeToFree->cleanupFn) { + nodeToFree->cleanupFn(nodeToFree->reentPtr); + } + free(nodeToFree); + { + std::lock_guard lock(sGlobalNodesMutex); + std::erase(sGlobalNodes, nodeToFree); + } + nodeToFree = nextNode; + } + wups_backend_restore_head(oldHead); + } +} diff --git a/source/utils/reent.h b/source/utils/reent.h new file mode 100644 index 0000000..730dd00 --- /dev/null +++ b/source/utils/reent.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include + +class PluginContainer; + +void *wups_backend_get_context(const void *pluginId, wups_loader_init_reent_errors_t_ *outError); + +void *wups_backend_set_sentinel(); + +void wups_backend_restore_head(void *oldHead); + +bool wups_backend_register_context(const void *pluginId, void *reentPtr, void (*cleanupFn)(void *), void *oldHead); + +void ClearReentDataForPlugins(const std::vector &plugins); + +void MarkReentNodesForDeletion(); + +void ClearDanglingReentPtr(); diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index df47220..c0532d2 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -339,6 +339,8 @@ const char *hookNameToString(const wups_loader_hook_type_t type) { return "WUPS_LOADER_HOOK_INIT_BUTTON_COMBO"; case WUPS_LOADER_HOOK_INIT_WUT_THREAD: return "WUPS_LOADER_HOOK_INIT_WUT_THREAD"; + case WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS: + return "WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS"; } return ""; } \ No newline at end of file