From a22da69f2d91e817462d7067d39308f3d2e59816 Mon Sep 17 00:00:00 2001 From: Maschell Date: Fri, 17 Apr 2026 23:45:49 +0200 Subject: [PATCH] Add logic to handle the "reent" bug --- source/NotificationsUtils.cpp | 12 ++- source/NotificationsUtils.h | 4 +- source/PluginManagement.cpp | 124 +++++++++++++++++++++++---- source/main.cpp | 2 +- source/plugin/PluginData.cpp | 8 ++ source/plugin/PluginData.h | 5 ++ source/utils/WUPSBackendSettings.cpp | 60 +++++++++++-- source/utils/WUPSBackendSettings.h | 4 + source/utils/config/ConfigUtils.cpp | 8 +- source/utils/utils.cpp | 44 ++++++++++ source/utils/utils.h | 10 ++- 11 files changed, 247 insertions(+), 34 deletions(-) diff --git a/source/NotificationsUtils.cpp b/source/NotificationsUtils.cpp index d9b0c9c..37ea4c6 100644 --- a/source/NotificationsUtils.cpp +++ b/source/NotificationsUtils.cpp @@ -16,6 +16,7 @@ bool DisplayNotificationMessage(std::string_view text, NotificationModuleNotific } if (type == NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO) { + NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, true); NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, duration); NotificationModule_AddInfoNotification(text.data()); } else if (type == NOTIFICATION_MODULE_NOTIFICATION_TYPE_ERROR) { @@ -35,4 +36,13 @@ bool DisplayInfoNotificationMessage(std::string_view text, float duration) { bool DisplayErrorNotificationMessage(std::string_view text, float duration) { return DisplayNotificationMessage(text, NOTIFICATION_MODULE_NOTIFICATION_TYPE_ERROR, duration); -} \ No newline at end of file +} + +bool DisplayWarnNotificationMessage(std::string_view text, float duration) { + if (!gNotificationModuleLoaded) { + return false; + } + + NotificationModule_AddErrorNotificationEx(text.data(), duration, 0.5f, {0, 0, 0, 255}, {255, 242, 0, 255}, nullptr, nullptr, true); + return true; +} diff --git a/source/NotificationsUtils.h b/source/NotificationsUtils.h index 787d127..068426d 100644 --- a/source/NotificationsUtils.h +++ b/source/NotificationsUtils.h @@ -3,5 +3,5 @@ #include bool DisplayInfoNotificationMessage(std::string_view text, float duration); - -bool DisplayErrorNotificationMessage(std::string_view text, float duration); \ No newline at end of file +bool DisplayErrorNotificationMessage(std::string_view text, float duration); +bool DisplayWarnNotificationMessage(std::string_view text, float duration); \ No newline at end of file diff --git a/source/PluginManagement.cpp b/source/PluginManagement.cpp index 040b874..d1faa8d 100644 --- a/source/PluginManagement.cpp +++ b/source/PluginManagement.cpp @@ -15,25 +15,125 @@ #include "plugin/TrackingPluginHeapMemoryAllocator.h" #include "utils/ElfUtils.h" #include "utils/StringTools.h" +#include "utils/WUPSBackendSettings.h" #include "utils/logger.h" #include #include +#include +#include #include -#include +namespace { + bool hasPossiblyReentBug(const PluginMetaInformation &meta) { + if (meta.getWUPSVersion() < WUPSVersion(0, 7, 1)) { + // get_reent only got implemented with 0.7.1 or higher + return false; + } + if (meta.getWUPSVersion() >= WUPSVersion(0, 9, 1)) { + // Any plugin compiled with 0.9.1 is fine! + return false; + } + + const time_t buildTime = parseBuildDate(meta.getBuildTimestamp().c_str()); + + // Threshold: Jan 25 2026 00:00:00 + tm thresholdTm = {}; + thresholdTm.tm_mday = 25; + thresholdTm.tm_mon = 0; // Jan + thresholdTm.tm_year = 2026 - 1900; + thresholdTm.tm_isdst = -1; + const time_t threshold = mktime(&thresholdTm); + + // Date-only comparison + return buildTime >= threshold; + } + + void displayUseAnywayWarning(const PluginMetaInformation &info) { + DEBUG_FUNCTION_LINE_INFO("But the user decided to use it anyway. Show them an info"); + // Has been explicitly re-enabled by the user. + DEBUG_FUNCTION_LINE_WARN("Using plugin \"%s\" is unstable. It may cause crashes: Please recompile with WUPS 0.9.1", info.getName().c_str()); + const auto errMsg = string_format("Using plugin \"%s\" is unstable and may cause crashes. Consider updating or disabling it.", info.getName().c_str()); + DisplayInfoNotificationMessage(errMsg, 30.0f); + } + + bool HandlePossibleReentBug(const PluginLoadWrapper &pluginDataWrapper, const PluginMetaInformation &metaInfo, bool &reentBugWarningShown) { + const auto &brokenReentFilenames = WUPSBackendSettings::GetBrokenReentPluginFilenames(); + if (pluginDataWrapper.isLoadAndLink()) { // Only check plugins are actually loaded + const std::optional pluginFilenameOpt = getPluginFilename(pluginDataWrapper.getPluginData()->getSource()); + if (hasPossiblyReentBug(metaInfo)) { + DEBUG_FUNCTION_LINE_INFO("Plugin \"%s\" has been (possibly) compiled with the reent bug. Please recompile with WUPS 0.9.1", metaInfo.getName().c_str()); + + bool errorHasBeenShownFile = pluginFilenameOpt && brokenReentFilenames.contains(*pluginFilenameOpt); + bool errorHasBeenShownWiiloaded = pluginDataWrapper.getPluginData()->isReentBugAllowed(); + + if (errorHasBeenShownFile || errorHasBeenShownWiiloaded) { + displayUseAnywayWarning(metaInfo); + } else { + if (pluginFilenameOpt) { // loaded from sd card + DEBUG_FUNCTION_LINE_INFO("Disable it and show an warning."); + WUPSBackendSettings::AddBrokenReentPluginFilename(*pluginFilenameOpt); + WUPSBackendSettings::AddInactivePluginFilename(*pluginFilenameOpt); + + if (!reentBugWarningShown) { + DisplayWarnNotificationMessage("Some plugins have been disabled to avoid potential crashes. Update them if possible.", 300.0f); + reentBugWarningShown = true; + } + } else { + constexpr float REENT_BUG_WARN_DURATION = 30.0f; + // wiiloaded + DEBUG_FUNCTION_LINE_INFO("Its wiiloaded -> we disable it."); + DisplayWarnNotificationMessage("Enable it in the config menu if you want to use it anyway", REENT_BUG_WARN_DURATION); + auto errMsg = string_format("Using plugin \"%s\" might causes crashes and has NOT been loaded.", metaInfo.getName().c_str()); + DisplayWarnNotificationMessage(errMsg, REENT_BUG_WARN_DURATION); + pluginDataWrapper.getPluginData()->setAllowWithReentBug(); + } + + // We don't want to load it. + return false; + } + } else if (pluginFilenameOpt) { + // If plugin is compatible, remove it from the reent broken list. + DEBUG_FUNCTION_LINE_VERBOSE("Plugin \"%s\" has no reent bug, remove it from settings file", metaInfo.getName().c_str()); + WUPSBackendSettings::RemoveBrokenReentPluginFilename(*pluginFilenameOpt); + } + } + return true; + } +} // namespace std::vector PluginManagement::loadPlugins(const std::vector &pluginDataList) { std::vector plugins; + WUPSBackendSettings::LoadSettings(); + + bool reentBugWarningShown = false; + for (const auto &pluginDataWrapper : pluginDataList) { PluginParseErrors error = PLUGIN_PARSE_ERROR_UNKNOWN; auto metaInfo = PluginMetaInformationFactory::loadPlugin(*pluginDataWrapper.getPluginData(), error); - if (metaInfo && error == PLUGIN_PARSE_ERROR_NONE) { - if (pluginDataWrapper.isLoadAndLink()) { + + // Early exit for failed parsing + if (!metaInfo || error != PLUGIN_PARSE_ERROR_NONE) { + auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str()); + if (error == PLUGIN_PARSE_ERROR_INCOMPATIBLE_VERSION) { + errMsg += ". Incompatible version."; + } + DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); + DisplayErrorNotificationMessage(errMsg, 15.0f); + continue; + } + + auto addPluginAsStub = [&]() { + plugins.emplace_back(std::move(*metaInfo), PluginLinkInformation::CreateStub(), pluginDataWrapper.getPluginData(), std::nullopt); + }; + + if (pluginDataWrapper.isLoadAndLink()) { + const bool pluginShouldBeLoaded = HandlePossibleReentBug(pluginDataWrapper, *metaInfo, reentBugWarningShown); + if (pluginShouldBeLoaded) { DEBUG_FUNCTION_LINE_INFO("LOAD (ACTIVE) %s", metaInfo->getName().c_str()); auto linkInfo = PluginLinkInformationFactory::load(*pluginDataWrapper.getPluginData()); @@ -45,16 +145,11 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi } 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(), std::nullopt); + addPluginAsStub(); } } else { - auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str()); - if (error == PLUGIN_PARSE_ERROR_INCOMPATIBLE_VERSION) { - errMsg += ". Incompatible version."; - } - DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); - DisplayErrorNotificationMessage(errMsg, 15.0f); + DEBUG_FUNCTION_LINE_INFO("LOAD (INACTIVE) %s", metaInfo->getName().c_str()); + addPluginAsStub(); } } @@ -63,6 +158,7 @@ PluginManagement::loadPlugins(const std::vector &pluginDataLi OSFatal("WiiUPluginLoaderBackend: Failed to patch functions"); } + WUPSBackendSettings::SaveSettings(); return plugins; } @@ -94,8 +190,7 @@ bool PluginManagement::doRelocation(const std::vector &relocData DEBUG_FUNCTION_LINE_ERR("Failed to acquire %s", rplName.c_str()); return false; } - // Keep track RPLs we are using. - // They will be released on exit + // Keep track RPLs we are using. They will be released on exit usedRPls[rplName] = rplHandle; } else { rplHandle = usedRPls[rplName]; @@ -134,8 +229,6 @@ bool PluginManagement::doRelocation(const std::vector &relocData bool PluginManagement::doRelocations(const std::vector &plugins, std::map &usedRPls) { - - OSDynLoadAllocFn prevDynLoadAlloc = nullptr; OSDynLoadFreeFn prevDynLoadFree = nullptr; @@ -162,7 +255,6 @@ bool PluginManagement::doRelocations(const std::vector &plugins } OSDynLoad_SetAllocator(prevDynLoadAlloc, prevDynLoadFree); - return true; } diff --git a/source/main.cpp b/source/main.cpp index 4d9b671..87bd203 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -330,7 +330,7 @@ WUMS_APPLICATION_STARTS() { if (!PluginManagement::doRelocations(gLoadedPlugins, gUsedRPLs)) { DEBUG_FUNCTION_LINE_ERR("Relocations failed"); - OSFatal("WiiUPluginLoaderBackend: Relocations failed.\n See crash logs for more information."); + OSFatal("WiiUPluginLoaderBackend: Relocations failed.\nSee crash logs for more information."); } // PluginManagement::memsetBSS(plugins); diff --git a/source/plugin/PluginData.cpp b/source/plugin/PluginData.cpp index 39c4b21..36fed4d 100644 --- a/source/plugin/PluginData.cpp +++ b/source/plugin/PluginData.cpp @@ -47,3 +47,11 @@ size_t PluginData::getMemoryFootprint() const { return totalSize; } + +void PluginData::setAllowWithReentBug() { + mIsReentBugAllowed = true; +} + +bool PluginData::isReentBugAllowed() const { + return mIsReentBugAllowed; +} diff --git a/source/plugin/PluginData.h b/source/plugin/PluginData.h index 1ca4ce1..cc83daf 100644 --- a/source/plugin/PluginData.h +++ b/source/plugin/PluginData.h @@ -45,10 +45,15 @@ public: size_t getMemoryFootprint() const; + void setAllowWithReentBug(); + + bool isReentBugAllowed() const; + private: std::vector mBuffer; std::string mSource; std::unique_ptr mHandle = std::make_unique(); + bool mIsReentBugAllowed = false; }; struct PluginDataSharedPtrComparator { diff --git a/source/utils/WUPSBackendSettings.cpp b/source/utils/WUPSBackendSettings.cpp index 9a39b15..3f0d7da 100644 --- a/source/utils/WUPSBackendSettings.cpp +++ b/source/utils/WUPSBackendSettings.cpp @@ -5,15 +5,22 @@ #include "utils/logger.h" #include "utils/utils.h" +#include #include -#include namespace WUPSBackendSettings { namespace { std::set sInactivePlugins; - } + std::set sBrokenReentPlugins; + bool isDirty = false; + } // namespace -#define INACTIVE_PLUGINS_KEY "inactive_plugins" +#define INACTIVE_PLUGINS_KEY "inactive_plugins" +#define BROKEN_REENT_PLUGINS_KEY "possibly_broken_reent_plugins" + + bool IsDirty() { + return isDirty; + } bool LoadSettings() { nlohmann::json j = nlohmann::json::object(); @@ -28,15 +35,29 @@ namespace WUPSBackendSettings { if (j.contains(INACTIVE_PLUGINS_KEY) && j[INACTIVE_PLUGINS_KEY].is_array()) { for (auto &cur : j[INACTIVE_PLUGINS_KEY]) { if (cur.is_string()) { - sInactivePlugins.insert(cur); + sInactivePlugins.insert(cur.get()); } } } + sBrokenReentPlugins.clear(); + if (j.contains(BROKEN_REENT_PLUGINS_KEY) && j[BROKEN_REENT_PLUGINS_KEY].is_array()) { + for (auto &cur : j[BROKEN_REENT_PLUGINS_KEY]) { + if (cur.is_string()) { + sBrokenReentPlugins.insert(cur.get()); + } + } + } + + isDirty = false; return true; } bool SaveSettings() { + if (!isDirty) { + return true; + } + std::string folderPath = getModulePath() + "/configs/"; std::string filePath = folderPath + "wupsbackend.json"; if (!FSUtils::CreateSubfolder(folderPath)) { @@ -49,8 +70,9 @@ namespace WUPSBackendSettings { return false; } - nlohmann::json j = nlohmann::json::object(); - j[INACTIVE_PLUGINS_KEY] = sInactivePlugins; + nlohmann::json j = nlohmann::json::object(); + j[INACTIVE_PLUGINS_KEY] = sInactivePlugins; + j[BROKEN_REENT_PLUGINS_KEY] = sBrokenReentPlugins; std::string jsonString = j.dump(4, ' ', false, nlohmann::json::error_handler_t::ignore); auto writeResult = file.write((const uint8_t *) jsonString.c_str(), jsonString.size()); @@ -61,19 +83,41 @@ namespace WUPSBackendSettings { return false; } + isDirty = false; return true; } void ClearInactivePluginFilenames() { - sInactivePlugins.clear(); + if (!sInactivePlugins.empty()) { + sInactivePlugins.clear(); + isDirty = true; + } } void AddInactivePluginFilename(const std::string &filename) { - sInactivePlugins.insert(filename); + if (sInactivePlugins.insert(filename).second) { + isDirty = true; + } + } + + void AddBrokenReentPluginFilename(const std::string &filename) { + if (sBrokenReentPlugins.insert(filename).second) { + isDirty = true; + } + } + + void RemoveBrokenReentPluginFilename(const std::string &filename) { + if (sBrokenReentPlugins.erase(filename) > 0) { + isDirty = true; + } } const std::set &GetInactivePluginFilenames() { return sInactivePlugins; } + const std::set &GetBrokenReentPluginFilenames() { + return sBrokenReentPlugins; + } + } // namespace WUPSBackendSettings \ No newline at end of file diff --git a/source/utils/WUPSBackendSettings.h b/source/utils/WUPSBackendSettings.h index 842c50a..a6a7695 100644 --- a/source/utils/WUPSBackendSettings.h +++ b/source/utils/WUPSBackendSettings.h @@ -21,4 +21,8 @@ namespace WUPSBackendSettings { } const std::set &GetInactivePluginFilenames(); + + const std::set &GetBrokenReentPluginFilenames(); + void AddBrokenReentPluginFilename(const std::string &filename); + void RemoveBrokenReentPluginFilename(const std::string &filename); }; // namespace WUPSBackendSettings diff --git a/source/utils/config/ConfigUtils.cpp b/source/utils/config/ConfigUtils.cpp index c0d6cc9..053470e 100644 --- a/source/utils/config/ConfigUtils.cpp +++ b/source/utils/config/ConfigUtils.cpp @@ -235,11 +235,9 @@ void ConfigUtils::displayMenu() { std::vector newInactivePluginsList; for (const auto &cur : newActivePluginsList) { if (!cur.isLoadAndLink()) { - auto &source = cur.getPluginData()->getSource(); - if (source.starts_with(getPluginPath()) && source.ends_with(".wps")) { - std::size_t found = source.find_last_of("/\\"); - std::string filename = source.substr(found + 1); - newInactivePluginsList.push_back(filename); + const auto &source = cur.getPluginData()->getSource(); + if (const auto filenameOpt = getPluginFilename(source); filenameOpt) { + newInactivePluginsList.push_back(*filenameOpt); } } } diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp index c0532d2..09a3dc0 100644 --- a/source/utils/utils.cpp +++ b/source/utils/utils.cpp @@ -343,4 +343,48 @@ const char *hookNameToString(const wups_loader_hook_type_t type) { return "WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS"; } return ""; +} + +std::optional getPluginFilename(const std::string &source) { + if (source.starts_with(getPluginPath()) && source.ends_with(".wps")) { + const std::size_t found = source.find_last_of("/\\"); + return source.substr(found + 1); + } + return std::nullopt; +} + +time_t parseBuildDate(const char *s) { + // Expected format: "Apr 16 2026" (ignoring any trailing time) + if (!s || strlen(s) < 11) { + return 0; + } + + static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + tm t = {}; + + // 1. Identify Month (0-2) + t.tm_mon = -1; + for (int i = 0; i < 12; ++i) { + if (strncmp(s, months[i], 3) == 0) { + t.tm_mon = i; + break; + } + } + if (t.tm_mon == -1) return 0; + + // 2. Parse Day (starts at index 4) + t.tm_mday = static_cast(strtol(s + 4, nullptr, 10)); + + // 3. Parse Year (starts at index 7) + // strtol will stop at the space before the time string automatically + t.tm_year = static_cast(strtol(s + 7, nullptr, 10)) - 1900; + + // We only care about the date, so we zero out the time + t.tm_hour = 0; + t.tm_min = 0; + t.tm_sec = 0; + t.tm_isdst = -1; + + return mktime(&t); } \ No newline at end of file diff --git a/source/utils/utils.h b/source/utils/utils.h index 9e76fc1..5b41e5a 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -171,4 +171,12 @@ std::string getModuleAndSymbolName(uint32_t addr); void PrintCapturedStackTrace(std::span trace); -const char *hookNameToString(wups_loader_hook_type_t type); \ No newline at end of file +const char *hookNameToString(wups_loader_hook_type_t type); + +/** +* Helper to extract the filename from the source path. +* Returns nullopt if the source isn't a standard plugin path. +*/ +std::optional getPluginFilename(const std::string &source); + +time_t parseBuildDate(const char *s); \ No newline at end of file