WiiUPluginLoaderBackend/source/PluginManagement.cpp
2026-04-19 12:03:58 +02:00

295 lines
14 KiB
C++

#include "PluginManagement.h"
#include "NotificationsUtils.h"
#include "hooks.h"
#include "plugin/FunctionData.h"
#include "plugin/HookData.h"
#include "plugin/PluginConfigData.h"
#include "plugin/PluginContainer.h"
#include "plugin/PluginData.h"
#include "plugin/PluginLinkInformationFactory.h"
#include "plugin/PluginLoadWrapper.h"
#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/WUPSBackendSettings.h"
#include "utils/logger.h"
#include <wums/defines/relocation_defines.h>
#include <coreinit/cache.h>
#include <cstring>
#include <ranges>
#include <vector>
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);
// 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());
if (!linkInfo) {
auto errMsg = string_format("Failed to load plugin: %s", pluginDataWrapper.getPluginData()->getSource().c_str());
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());
DisplayErrorNotificationMessage(errMsg, 15.0f);
continue;
}
plugins.emplace_back(std::move(*metaInfo), std::move(*linkInfo), pluginDataWrapper.getPluginData(), pluginDataWrapper.getHeapTrackingOptions());
} else {
addPluginAsStub();
}
} else {
DEBUG_FUNCTION_LINE_INFO("LOAD (INACTIVE) %s", metaInfo->getName().c_str());
addPluginAsStub();
}
}
if (!PluginManagement::DoFunctionPatches(plugins)) {
DEBUG_FUNCTION_LINE_ERR("Failed to patch functions");
OSFatal("WiiUPluginLoaderBackend: Failed to patch functions");
}
WUPSBackendSettings::SaveSettings();
return plugins;
}
bool PluginManagement::doRelocation(const std::vector<RelocationData> &relocData,
std::span<relocation_trampoline_entry_t> trampData,
std::map<std::string, OSDynLoad_Module> &usedRPls,
const IPluginHeapMemoryAllocator &memory_allocator) {
for (auto const &cur : relocData) {
uint32_t functionAddress = 0;
auto &functionName = cur.getName();
if (functionName == "MEMAllocFromDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(memory_allocator.GetAllocFunctionAddress());
} else if (functionName == "MEMAllocFromDefaultHeapEx") {
functionAddress = reinterpret_cast<uint32_t>(memory_allocator.GetAllocExFunctionAddress());
} else if (functionName == "MEMFreeToDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(memory_allocator.GetFreeFunctionAddress());
}
if (functionAddress == 0) {
auto rplName = cur.getImportRPLInformation().getRPLName();
int32_t isData = cur.getImportRPLInformation().isData();
OSDynLoad_Module rplHandle = nullptr;
if (!usedRPls.contains(rplName)) {
DEBUG_FUNCTION_LINE_VERBOSE("Acquire %s", rplName.c_str());
// Always acquire to increase refcount and make sure it won't get unloaded while we're using it.
if (const OSDynLoad_Error err = OSDynLoad_Acquire(rplName.c_str(), &rplHandle); err != OS_DYNLOAD_OK) {
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
usedRPls[rplName] = rplHandle;
} else {
rplHandle = usedRPls[rplName];
}
OSDynLoad_FindExport(rplHandle, static_cast<OSDynLoad_ExportType>(isData), functionName.c_str(), reinterpret_cast<void **>(&functionAddress));
}
if (functionAddress == 0) {
DEBUG_FUNCTION_LINE_ERR("Failed to find export for %s", functionName.c_str());
return false;
} else {
//DEBUG_FUNCTION_LINE("Found export for %s %s", rplName.c_str(), functionName.c_str());
}
if (!ElfUtils::elfLinkOne(cur.getType(), cur.getOffset(), cur.getAddend(), reinterpret_cast<uint32_t>(cur.getDestination()), functionAddress, trampData, RELOC_TYPE_IMPORT)) {
DEBUG_FUNCTION_LINE_ERR("elfLinkOne failed");
return false;
}
}
// Unloading RPLs which you want to use is stupid.
// Never uncomment this again.
/* for (auto &cur : moduleHandleCache) {
// Release handle if they are not from DynLoadPatchModule
if (((uint32_t) cur.second & 0xFFFF0000) != 0x13370000) {
OSDynLoad_Release(cur.second);
}
} */
DCFlushRange((void *) trampData.data(), trampData.size() * sizeof(relocation_trampoline_entry_t));
ICInvalidateRange((void *) trampData.data(), trampData.size() * sizeof(relocation_trampoline_entry_t));
OSMemoryBarrier();
return true;
}
bool PluginManagement::doRelocations(const std::vector<PluginContainer> &plugins,
std::map<std::string, OSDynLoad_Module> &usedRPls) {
OSDynLoadAllocFn prevDynLoadAlloc = nullptr;
OSDynLoadFreeFn prevDynLoadFree = nullptr;
OSDynLoad_GetAllocator(&prevDynLoadAlloc, &prevDynLoadFree);
OSDynLoad_SetAllocator(CustomDynLoadAlloc, CustomDynLoadFree);
for (const auto &pluginContainer : plugins) {
if (!pluginContainer.isLinkedAndLoaded()) {
continue;
}
const auto &trampData = pluginContainer.getPluginLinkInformation().getTrampData();
for (auto &cur : trampData) {
if (cur.status == RELOC_TRAMP_IMPORT_DONE) {
cur.status = RELOC_TRAMP_FREE;
}
}
DEBUG_FUNCTION_LINE_VERBOSE("Doing relocations for plugin: %s", pluginContainer.getMetaInformation().getName().c_str());
if (!PluginManagement::doRelocation(pluginContainer.getPluginLinkInformation().getRelocationDataList(),
trampData,
usedRPls, pluginContainer.getMemoryAllocator())) {
return false;
}
}
OSDynLoad_SetAllocator(prevDynLoadAlloc, prevDynLoadFree);
return true;
}
bool PluginManagement::RestoreFunctionPatches(std::vector<PluginContainer> &plugins) {
for (auto &cur : std::ranges::reverse_view(plugins)) {
if (!cur.isLinkedAndLoaded()) {
continue;
}
for (auto &curFunction : std::ranges::reverse_view(cur.getPluginLinkInformation().getFunctionDataList())) {
if (!curFunction.RemovePatch()) {
DEBUG_FUNCTION_LINE_ERR("Failed to remove function patch for: plugin %s", cur.getMetaInformation().getName().c_str());
return false;
}
}
}
return true;
}
bool PluginManagement::DoFunctionPatches(std::vector<PluginContainer> &plugins) {
for (auto &cur : plugins) {
for (auto &curFunction : cur.getPluginLinkInformation().getFunctionDataList()) {
if (!curFunction.AddPatch()) {
DEBUG_FUNCTION_LINE_ERR("Failed to add function patch for: plugin %s", cur.getMetaInformation().getName().c_str());
return false;
}
}
}
return true;
}
void PluginManagement::callInitHooks(const std::vector<PluginContainer> &plugins, const std::function<bool(const PluginContainer &)> &pred) {
CallHook(plugins, WUPS_LOADER_HOOK_INIT_BUTTON_COMBO, pred);
CallHook(plugins, WUPS_LOADER_HOOK_INIT_CONFIG, pred);
CallHook(plugins, WUPS_LOADER_HOOK_INIT_STORAGE_DEPRECATED, pred);
CallHook(plugins, WUPS_LOADER_HOOK_INIT_STORAGE, pred);
CallHook(plugins, WUPS_LOADER_HOOK_INIT_PLUGIN, pred);
DEBUG_FUNCTION_LINE_VERBOSE("Done calling init hooks");
}