Add logic to handle the "reent" bug

This commit is contained in:
Maschell 2026-04-17 23:45:49 +02:00
parent 66fd70e759
commit a22da69f2d
11 changed files with 247 additions and 34 deletions

View File

@ -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);
}
}
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;
}

View File

@ -3,5 +3,5 @@
#include <string_view>
bool DisplayInfoNotificationMessage(std::string_view text, float duration);
bool DisplayErrorNotificationMessage(std::string_view text, float duration);
bool DisplayErrorNotificationMessage(std::string_view text, float duration);
bool DisplayWarnNotificationMessage(std::string_view text, float duration);

View File

@ -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 <wums/defines/relocation_defines.h>
#include <coreinit/cache.h>
#include <cstring>
#include <ranges>
#include <vector>
#include <cstdint>
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<std::string> 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<PluginContainer>
PluginManagement::loadPlugins(const std::vector<PluginLoadWrapper> &pluginDataList) {
std::vector<PluginContainer> 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<PluginLoadWrapper> &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<PluginLoadWrapper> &pluginDataLi
OSFatal("WiiUPluginLoaderBackend: Failed to patch functions");
}
WUPSBackendSettings::SaveSettings();
return plugins;
}
@ -94,8 +190,7 @@ bool PluginManagement::doRelocation(const std::vector<RelocationData> &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<RelocationData> &relocData
bool PluginManagement::doRelocations(const std::vector<PluginContainer> &plugins,
std::map<std::string, OSDynLoad_Module> &usedRPls) {
OSDynLoadAllocFn prevDynLoadAlloc = nullptr;
OSDynLoadFreeFn prevDynLoadFree = nullptr;
@ -162,7 +255,6 @@ bool PluginManagement::doRelocations(const std::vector<PluginContainer> &plugins
}
OSDynLoad_SetAllocator(prevDynLoadAlloc, prevDynLoadFree);
return true;
}

View File

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

View File

@ -47,3 +47,11 @@ size_t PluginData::getMemoryFootprint() const {
return totalSize;
}
void PluginData::setAllowWithReentBug() {
mIsReentBugAllowed = true;
}
bool PluginData::isReentBugAllowed() const {
return mIsReentBugAllowed;
}

View File

@ -45,10 +45,15 @@ public:
size_t getMemoryFootprint() const;
void setAllowWithReentBug();
bool isReentBugAllowed() const;
private:
std::vector<uint8_t> mBuffer;
std::string mSource;
std::unique_ptr<uint32_t> mHandle = std::make_unique<uint32_t>();
bool mIsReentBugAllowed = false;
};
struct PluginDataSharedPtrComparator {

View File

@ -5,15 +5,22 @@
#include "utils/logger.h"
#include "utils/utils.h"
#include <set>
#include <string>
#include <vector>
namespace WUPSBackendSettings {
namespace {
std::set<std::string> sInactivePlugins;
}
std::set<std::string> 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<std::string>());
}
}
}
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<std::string>());
}
}
}
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<std::string> &GetInactivePluginFilenames() {
return sInactivePlugins;
}
const std::set<std::string> &GetBrokenReentPluginFilenames() {
return sBrokenReentPlugins;
}
} // namespace WUPSBackendSettings

View File

@ -21,4 +21,8 @@ namespace WUPSBackendSettings {
}
const std::set<std::string> &GetInactivePluginFilenames();
const std::set<std::string> &GetBrokenReentPluginFilenames();
void AddBrokenReentPluginFilename(const std::string &filename);
void RemoveBrokenReentPluginFilename(const std::string &filename);
}; // namespace WUPSBackendSettings

View File

@ -235,11 +235,9 @@ void ConfigUtils::displayMenu() {
std::vector<std::string> 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);
}
}
}

View File

@ -343,4 +343,48 @@ const char *hookNameToString(const wups_loader_hook_type_t type) {
return "WUPS_LOADER_HOOK_INIT_REENT_FUNCTIONS";
}
return "<UNKNOWN>";
}
std::optional<std::string> 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<int>(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<int>(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);
}

View File

@ -171,4 +171,12 @@ std::string getModuleAndSymbolName(uint32_t addr);
void PrintCapturedStackTrace(std::span<const uint32_t> trace);
const char *hookNameToString(wups_loader_hook_type_t type);
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<std::string> getPluginFilename(const std::string &source);
time_t parseBuildDate(const char *s);