diff --git a/.gitignore b/.gitignore index ba57c28..98a796f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build/ cmake-build-debug/ CMakeLists.txt *.wms +*.zip diff --git a/Dockerfile b/Dockerfile index 59759a8..b12ae3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,5 +5,6 @@ COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230719 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libmappedmemory:20230621 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libwupsbackend:20230621 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libnotifications:20230621 /artifacts $DEVKITPRO WORKDIR project diff --git a/Makefile b/Makefile index eb05fbf..cbace7c 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g endif -LIBS := -lwums -lwut -lwups -lfunctionpatcher -lmappedmemory -lz +LIBS := -lwums -lwut -lwups -lfunctionpatcher -lmappedmemory -lz -lnotifications #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level diff --git a/README.md b/README.md index c92d8f0..b540e6e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ This is the backend for the [WiiUPluginSystem](https://github.com/wiiu-env/WiiUP 2. Requires the [WUMSLoader](https://github.com/wiiu-env/WUMSLoader) in `sd:/wiiu/environments/[ENVIRONMENT]/modules/setup`. 3. Requires the [FunctionPatcherModule](https://github.com/wiiu-env/FunctionPatcherModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. 4. Requires the [MemoryMappingModule](https://github.com/wiiu-env/MemoryMappingModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +5. Requires the [NotificationModule](https://github.com/wiiu-env/NotificationModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. Plugins needs to be placed into the following directory: @@ -32,7 +33,7 @@ DEVKITPRO=/opt/devkitpro DEVKITPPC=/opt/devkitpro/devkitPPC ``` -Also make sure to install [wut](https://github.com/decaf-emu/wut), [WiiUPluginSystem](https://github.com/wiiu-env/WiiUPluginSystem), [WiiUModuleSystem](https://github.com/wiiu-env/WiiUModuleSystem), [libfunctionpatcher](https://github.com/wiiu-env/libfunctionpatcher) and [libmappedmemory](https://github.com/wiiu-env/libmappedmemory). +Also make sure to install [wut](https://github.com/decaf-emu/wut), [WiiUPluginSystem](https://github.com/wiiu-env/WiiUPluginSystem), [WiiUModuleSystem](https://github.com/wiiu-env/WiiUModuleSystem), [libfunctionpatcher](https://github.com/wiiu-env/libfunctionpatcher), [libnotifications](https://github.com/wiiu-env/libnotifications) and [libmappedmemory](https://github.com/wiiu-env/libmappedmemory). ## Buildflags diff --git a/source/NotificationsUtils.cpp b/source/NotificationsUtils.cpp new file mode 100644 index 0000000..1d3ede3 --- /dev/null +++ b/source/NotificationsUtils.cpp @@ -0,0 +1,117 @@ +#include "NotificationsUtils.h" +#include "globals.h" +#include "utils/logger.h" +#include +#include +#include +#include +#include +#include + +std::unique_ptr sNotificationsThread; +static bool sShutdownNotificationsThread = false; + +OSMessageQueue sNotificationQueue; +OSMessage sNotificationMessages[0x10]; + +#define NOTIFICATION_QUEUE_COMMAND_STOP 0 +#define NOTIFICATION_QUEUE_COMMAND_ERROR 1 + +struct NotificationMessageWrapper { + NotificationModuleNotificationType type = NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO; + char text[512] = {}; + float duration = 10.0f; +}; + +void NotificationMainLoop() { + bool isOverlayReady = false; + while (!sShutdownNotificationsThread && + NotificationModule_IsOverlayReady(&isOverlayReady) == NOTIFICATION_MODULE_RESULT_SUCCESS && !isOverlayReady) { + OSSleepTicks(OSMillisecondsToTicks(16)); + } + if (sShutdownNotificationsThread || !isOverlayReady) { + return; + } + + OSMessage recv; + while (OSReceiveMessage(&sNotificationQueue, &recv, OS_MESSAGE_FLAGS_BLOCKING)) { + if (recv.args[0] == NOTIFICATION_QUEUE_COMMAND_STOP) { + break; + } + + if (recv.args[0] == NOTIFICATION_QUEUE_COMMAND_ERROR) { + auto *param = (NotificationMessageWrapper *) recv.message; + if (param->type == NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO) { + NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, param->duration); + NotificationModule_AddInfoNotification(param->text); + } else if (param->type == NOTIFICATION_MODULE_NOTIFICATION_TYPE_ERROR) { + NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_ERROR, NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, param->duration); + NotificationModule_AddErrorNotification(param->text); + } else { + DEBUG_FUNCTION_LINE_WARN("Unsupported notification type: %d", param->type); + } + + free(param); + continue; + } + } +} + +bool DisplayNotificationMessage(std::string &text, NotificationModuleNotificationType type, float duration) { + if (!gNotificationModuleLoaded) { + return false; + } + if (type == NOTIFICATION_MODULE_NOTIFICATION_TYPE_DYNAMIC) { + return false; + } + auto *param = (NotificationMessageWrapper *) malloc(sizeof(NotificationMessageWrapper)); + if (!param) { + return false; + } + strncpy(param->text, text.c_str(), sizeof(param->text) - 1); + param->type = type; + param->duration = duration; + + OSMessage send; + send.message = param; + send.args[0] = NOTIFICATION_QUEUE_COMMAND_ERROR; + auto res = OSSendMessage(&sNotificationQueue, &send, OS_MESSAGE_FLAGS_NONE); + if (!res) { + DEBUG_FUNCTION_LINE_ERR("Failed to add Error Notification: Queue full"); + free(param); + return false; + } + return true; +} + +bool DisplayInfoNotificationMessage(std::string &text, float duration) { + return DisplayNotificationMessage(text, NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, duration); +} + +bool DisplayErrorNotificationMessage(std::string &text, float duration) { + return DisplayNotificationMessage(text, NOTIFICATION_MODULE_NOTIFICATION_TYPE_ERROR, duration); +} + +void StartNotificationThread() { + sNotificationsThread.reset(); + sShutdownNotificationsThread = false; + if (!gNotificationModuleLoaded) { + return; + } + + constexpr int32_t messageSize = sizeof(sNotificationMessages) / sizeof(sNotificationMessages[0]); + OSInitMessageQueue(&sNotificationQueue, sNotificationMessages, messageSize); + sNotificationsThread = std::make_unique(NotificationMainLoop); +} + +void StopNotificationThread() { + if (sNotificationsThread != nullptr) { + OSMessage message; + message.args[0] = NOTIFICATION_QUEUE_COMMAND_STOP; + OSSendMessage(&sNotificationQueue, &message, OS_MESSAGE_FLAGS_NONE); + sShutdownNotificationsThread = true; + OSMemoryBarrier(); + sNotificationsThread->join(); + sNotificationsThread.reset(); + } +} \ No newline at end of file diff --git a/source/NotificationsUtils.h b/source/NotificationsUtils.h new file mode 100644 index 0000000..8950ece --- /dev/null +++ b/source/NotificationsUtils.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +void StartNotificationThread(); + +void StopNotificationThread(); + +bool DisplayInfoNotificationMessage(std::string &text, float duration); + +bool DisplayErrorNotificationMessage(std::string &text, float duration); \ No newline at end of file diff --git a/source/PluginManagement.cpp b/source/PluginManagement.cpp index 07da182..dd922fc 100644 --- a/source/PluginManagement.cpp +++ b/source/PluginManagement.cpp @@ -156,7 +156,9 @@ PluginManagement::loadPlugins(const std::forward_listgetName().c_str()); + auto errMsg = string_format("Failed to load plugin: %s", metaInfo.value()->getName().c_str()); + DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); + DisplayErrorNotificationMessage(errMsg, 15.0f); continue; } auto container = make_unique_nothrow(std::move(metaInfo.value()), std::move(info.value()), pluginData); diff --git a/source/globals.cpp b/source/globals.cpp index b28b3aa..709c8be 100644 --- a/source/globals.cpp +++ b/source/globals.cpp @@ -10,4 +10,6 @@ std::forward_list> gLoadedData __attribute__((sectio std::forward_list> gLoadOnNextLaunch __attribute__((section(".data"))); std::mutex gLoadedDataMutex __attribute__((section(".data"))); std::map gUsedRPLs __attribute__((section(".data"))); -std::vector gAllocatedAddresses __attribute__((section(".data"))); \ No newline at end of file +std::vector gAllocatedAddresses __attribute__((section(".data"))); + +bool gNotificationModuleLoaded __attribute__((section(".data"))) = false; \ No newline at end of file diff --git a/source/globals.h b/source/globals.h index 364835c..76adc34 100644 --- a/source/globals.h +++ b/source/globals.h @@ -24,3 +24,5 @@ extern std::forward_list> gLoadOnNextLaunch; extern std::mutex gLoadedDataMutex; extern std::map gUsedRPLs; extern std::vector gAllocatedAddresses; + +extern bool gNotificationModuleLoaded; diff --git a/source/main.cpp b/source/main.cpp index c58b8cb..886d0ed 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,3 +1,4 @@ +#include "NotificationsUtils.h" #include "PluginManagement.h" #include "coreinit/interrupts.h" #include "coreinit/scheduler.h" @@ -7,12 +8,14 @@ #include "plugin/PluginDataFactory.h" #include "utils/utils.h" #include +#include #include WUMS_MODULE_EXPORT_NAME("homebrew_wupsbackend"); WUMS_USE_WUT_DEVOPTAB(); WUMS_DEPENDS_ON(homebrew_functionpatcher); WUMS_DEPENDS_ON(homebrew_memorymapping); +WUMS_DEPENDS_ON(homebrew_notifications); WUMS_INITIALIZE() { initLogging(); @@ -21,6 +24,14 @@ WUMS_INITIALIZE() { OSFatal("homebrew_wupsbackend: FunctionPatcher_InitLibrary failed"); } + NotificationModuleStatus res; + if ((res = NotificationModule_InitLibrary()) != NOTIFICATION_MODULE_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to init NotificationModule"); + gNotificationModuleLoaded = false; + } else { + gNotificationModuleLoaded = true; + } + DEBUG_FUNCTION_LINE("Patching functions"); for (uint32_t i = 0; i < method_hooks_static_size; i++) { if (FunctionPatcher_AddFunctionPatch(&method_hooks_static[i], nullptr, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) { @@ -43,6 +54,7 @@ WUMS_APPLICATION_ENDS() { if (upid != 2 && upid != 15) { return; } + CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_APPLICATION_ENDS); CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_FINI_WUT_SOCKETS); CallHook(gLoadedPlugins, WUPS_LOADER_HOOK_FINI_WUT_DEVOPTAB); @@ -52,6 +64,8 @@ WUMS_APPLICATION_ENDS() { } gUsedRPLs.clear(); + StopNotificationThread(); + deinitLogging(); } @@ -65,6 +79,8 @@ WUMS_APPLICATION_STARTS() { OSReport("Running WiiUPluginLoaderBackend " VERSION_FULL "\n"); + StartNotificationThread(); + gUsedRPLs.clear(); // If an allocated rpl was not released properly (e.g. if something else calls OSDynload_Acquire without releasing it) memory get leaked. diff --git a/source/plugin/PluginDataFactory.cpp b/source/plugin/PluginDataFactory.cpp index bfa06ef..858d1ee 100644 --- a/source/plugin/PluginDataFactory.cpp +++ b/source/plugin/PluginDataFactory.cpp @@ -15,6 +15,7 @@ * along with this program. If not, see . ****************************************************************************/ #include "PluginDataFactory.h" +#include "NotificationsUtils.h" #include "fs/FSUtils.h" #include "utils/StringTools.h" #include "utils/logger.h" @@ -54,7 +55,9 @@ std::forward_list> PluginDataFactory::loadDir(const if (pluginData) { result.push_front(std::move(pluginData.value())); } else { - DEBUG_FUNCTION_LINE_ERR("Failed to load plugin: %s", full_file_path.c_str()); + auto errMsg = string_format("Failed to load plugin: %s", full_file_path.c_str()); + DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str()); + DisplayErrorNotificationMessage(errMsg, 15.0f); } } diff --git a/source/utils/StorageUtils.cpp b/source/utils/StorageUtils.cpp index 26a67ac..0c8b13e 100644 --- a/source/utils/StorageUtils.cpp +++ b/source/utils/StorageUtils.cpp @@ -1,10 +1,12 @@ #include "StorageUtils.h" +#include "NotificationsUtils.h" #include "StringTools.h" #include "fs/CFile.hpp" #include "fs/FSUtils.h" #include "utils.h" #include "utils/json.hpp" #include "utils/logger.h" +#include static void processJson(wups_storage_item_t *items, nlohmann::json json) { if (items == nullptr) { @@ -72,6 +74,8 @@ WUPSStorageError StorageUtils::OpenStorage(const char *plugin_id, wups_storage_i DEBUG_FUNCTION_LINE_ERR("%s", errorMessage.c_str()); remove(filePath.c_str()); + DisplayErrorNotificationMessage(errorMessage, 10.0f); + return WUPS_STORAGE_ERROR_SUCCESS; } } else { // empty or no config exists yet