Implement support for WUPS 0.9.1, add proper plugin reent handling

This commit is contained in:
Maschell 2026-04-08 19:56:14 +02:00
parent 8c973db737
commit 2e5de3e6e7
9 changed files with 413 additions and 67 deletions

View File

@ -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

View File

@ -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 <wups/button_combo/api.h>
#include <wups/button_combo_internal.h>
#include <wups/reent_internal.h>
#include <wups/storage.h>
#include <functional>
@ -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);

View File

@ -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 <buttoncombo/api.h>
@ -164,7 +165,6 @@ WUMS_APPLICATION_ENDS() {
deinitLogging();
}
void CheckCleanupCallbackUsage(const std::vector<PluginContainer> &plugins);
void CleanupPlugins(std::vector<PluginContainer> &pluginsToDeinit);
@ -190,6 +190,8 @@ WUMS_APPLICATION_STARTS() {
initLogging();
ClearDanglingReentPtr();
std::lock_guard lock(gLoadedDataMutex);
std::vector<PluginContainer> 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<PluginContainer> &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<PluginContainer> &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<PluginContainer> &pluginsToDeinit) {
}
}
}
}
void CheckCleanupCallbackUsage(const std::vector<PluginContainer> &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<OSThread **>(0x100567F8);
while (t) {
const auto address = reinterpret_cast<uint32_t>(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);
}
}
}
}

View File

@ -8,6 +8,7 @@
#include "plugin/SectionInfo.h"
#include "utils/config/ConfigUtils.h"
#include "utils/logger.h"
#include "utils/reent.h"
#include <coreinit/cache.h>
#include <coreinit/core.h>
@ -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);

View File

@ -123,6 +123,8 @@ std::optional<PluginMetaInformation> 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());

View File

@ -1,6 +1,5 @@
#pragma once
#include <coreinit/debug.h>
#include <string.h>
#include <whb/log.h>
@ -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

311
source/utils/reent.cpp Normal file
View File

@ -0,0 +1,311 @@
#include "reent.h"
#include "logger.h"
#include "plugin/PluginContainer.h"
#include "plugin/SectionInfo.h"
#include <coreinit/debug.h>
#include <coreinit/interrupts.h>
#include <coreinit/scheduler.h>
#include <coreinit/thread.h>
#include <forward_list>
#include <wups/reent_internal.h>
#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<uint32_t>(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<void *>(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<PluginContainer> &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<uint32_t>(dataMemory.data());
const auto endAddress = reinterpret_cast<uint32_t>(dataMemory.data()) + dataMemory.size();
// Zero-allocation deferred free list
__wups_reent_node *deferredFreeHead = nullptr;
struct PendingRestore {
OSThread *thread;
OSThreadCleanupCallbackFn callback;
};
std::vector<PendingRestore> 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<OSThread **>(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<uint32_t>(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);
}
}

19
source/utils/reent.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <vector>
#include <wups/reent_internal.h>
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<PluginContainer> &plugins);
void MarkReentNodesForDeletion();
void ClearDanglingReentPtr();

View File

@ -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 "<UNKNOWN>";
}