diff --git a/README.md b/README.md index fea0b67..26ec3c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ [![CI-Release](https://github.com/wiiu-env/homebrew_on_menu_plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/wiiu-env/homebrew_on_menu_plugin/actions/workflows/ci.yml) -## Usage +# Homebrew on menu + +This plugin allows you to boot homebrew directly from your Wii U Menu. + +## Installation (`[ENVIRONMENT]` is a placeholder for the actual environment name.) 1. Copy the file `homebrew_on_menu.wps` into `sd:/wiiu/environments/[ENVIRONMENT]/plugins`. @@ -10,10 +14,22 @@ 5. Requires the [ContentRedirectionModule](https://github.com/wiiu-env/ContentRedirectionModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. 6. Requires the [SDHotSwapModule](https://github.com/wiiu-env/SDHotSwapModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +## Usage + +Place your homebrew (`.rpx` or `.wuhb`) in `sd:/wiiu/apps` or any subdirectory inside `sd:/wiiu/apps`. + +Via the plugin config menu (press L, DPAD Down and Minus on the gamepad) you can configure the plugin. The available options are the following: +- **Features**: + - Hide all homebrew except Homebrew Launcher: (Default is false) + - Hides all homebrew from the Wii U Menu except the `sd:/wiiu/apps/homebrew_launcher.wuhb` and `sd:/wiiu/apps/homebrew_launcher/homebrew_launcher.wuhb` + - Prefer .wuhb over .rpx (Default is true) + - Hides a `.rpx` from the Wii U Menu if a `.wuhb` with the same name exists in the same directory. ## Save data redirection In order to preserve the order of homebrew apps even when you run the Wii U Menu without this plugin, this plugin will redirect the Wii U Menu save data to `sd:/wiiu/homebrew_on_menu_plugin`. When no save data is found on the sd card, the current save data is copied from the console, but after that it's never updated. +**If the plugin is configured to hide any homebrew except a Homebrew Launcher, the redirection is disabled.** + ## Buildflags ### Logging diff --git a/src/main.cpp b/src/main.cpp index 077710a..acc421b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -61,8 +62,20 @@ void readCustomTitlesFromSD(); WUPS_USE_WUT_DEVOPTAB(); WUPS_USE_STORAGE("homebrew_on_menu"); // Use the storage API -bool gHideHomebrew = false; -bool prevHideValue = false; +#define HIDE_HOMEBREW_STRING "hideHomebrew" +#define PREFER_WUHB_OVER_RPX_STRING "hideRPXIfWUHBExists" + +#define HOMEBREW_APPS_DIRECTORY "fs:/vol/external01/wiiu/apps" +#define HOMEBREW_LAUNCHER_FILENAME "homebrew_launcher.wuhb" +#define HOMEBREW_LAUNCHER_OPTIONAL_DIRECTORY "homebrew_launcher" + +#define HOMEBREW_LAUNCHER_PATH HOMEBREW_APPS_DIRECTORY "/" HOMEBREW_LAUNCHER_FILENAME +#define HOMEBREW_LAUNCHER_PATH2 HOMEBREW_APPS_DIRECTORY "/" HOMEBREW_LAUNCHER_OPTIONAL_DIRECTORY "/" HOMEBREW_LAUNCHER_FILENAME + +bool gHideHomebrew = false; +bool gPreferWUHBOverRPX = true; +bool prevHideValue = false; +bool prevPreferWUHBOverRPXValue = false; INITIALIZE_PLUGIN() { memset((void *) ¤t_launched_title_info, 0, sizeof(current_launched_title_info)); @@ -96,9 +109,9 @@ INITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE_ERR("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); } else { // Try to get value from storage - if ((storageRes = WUPS_GetBool(nullptr, "hideHomebrew", &gHideHomebrew)) == WUPS_STORAGE_ERROR_NOT_FOUND) { + if ((storageRes = WUPS_GetBool(nullptr, HIDE_HOMEBREW_STRING, &gHideHomebrew)) == WUPS_STORAGE_ERROR_NOT_FOUND) { // Add the value to the storage if it's missing. - storageRes = WUPS_StoreBool(nullptr, "hideHomebrew", gHideHomebrew); + storageRes = WUPS_StoreBool(nullptr, HIDE_HOMEBREW_STRING, gHideHomebrew); if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to store bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); } @@ -108,7 +121,20 @@ INITIALIZE_PLUGIN() { } } - prevHideValue = gHideHomebrew; + if ((storageRes = WUPS_GetBool(nullptr, PREFER_WUHB_OVER_RPX_STRING, &gPreferWUHBOverRPX)) == WUPS_STORAGE_ERROR_NOT_FOUND) { + // Add the value to the storage if it's missing. + storageRes = WUPS_StoreBool(nullptr, PREFER_WUHB_OVER_RPX_STRING, gPreferWUHBOverRPX); + if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to store bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); + } + } else { + if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); + } + } + + prevHideValue = gHideHomebrew; + prevPreferWUHBOverRPXValue = gPreferWUHBOverRPX; // Close storage WUPS_CloseStorage(); @@ -118,9 +144,20 @@ INITIALIZE_PLUGIN() { void hideHomebrewChanged(ConfigItemBoolean *item, bool newValue) { DEBUG_FUNCTION_LINE_VERBOSE("New value in gHideHomebrew: %d", newValue); gHideHomebrew = newValue; - // If the value has changed, we store it in the storage. - WUPSStorageError storageRes = WUPS_StoreBool(nullptr, "hideHomebrew", gHideHomebrew); + // If the value has changed, we store it in the storage. + WUPSStorageError storageRes = WUPS_StoreBool(nullptr, HIDE_HOMEBREW_STRING, gHideHomebrew); + if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Failed to store bool: %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); + } +} + +void preferWUHBOverRPXChanged(ConfigItemBoolean *item, bool newValue) { + DEBUG_FUNCTION_LINE_VERBOSE("New value in gPreferWUHBOverRPX: %d", newValue); + gPreferWUHBOverRPX = newValue; + + // If the value has changed, we store it in the storage. + WUPSStorageError storageRes = WUPS_StoreBool(nullptr, PREFER_WUHB_OVER_RPX_STRING, gPreferWUHBOverRPX); if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) { DEBUG_FUNCTION_LINE_ERR("Failed to store bool: %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); } @@ -141,7 +178,8 @@ WUPS_GET_CONFIG() { WUPSConfigCategoryHandle cat; WUPSConfig_AddCategoryByNameHandled(config, "Features", &cat); - WUPSConfigItemBoolean_AddToCategoryHandled(config, cat, "hideHomebrew", "Hide all homebrew except Homebrew Launcher", gHideHomebrew, &hideHomebrewChanged); + WUPSConfigItemBoolean_AddToCategoryHandled(config, cat, HIDE_HOMEBREW_STRING, "Hide all homebrew except Homebrew Launcher", gHideHomebrew, &hideHomebrewChanged); + WUPSConfigItemBoolean_AddToCategoryHandled(config, cat, PREFER_WUHB_OVER_RPX_STRING, "Prefer .wuhb over .rpx", gPreferWUHBOverRPX, &preferWUHBOverRPXChanged); return config; } @@ -157,13 +195,14 @@ WUPS_CONFIG_CLOSED() { DEBUG_FUNCTION_LINE_ERR("Failed to close storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); } - if (prevHideValue != gHideHomebrew) { + if (prevHideValue != gHideHomebrew || prevPreferWUHBOverRPXValue != gPreferWUHBOverRPX) { if (!sTitleRebooting) { _SYSLaunchTitleWithStdArgsInNoSplash(OSGetTitleID(), nullptr); sTitleRebooting = true; } } - prevHideValue = gHideHomebrew; + prevHideValue = gHideHomebrew; + prevPreferWUHBOverRPXValue = gPreferWUHBOverRPX; } void Cleanup() { @@ -303,42 +342,74 @@ void readCustomTitlesFromSD() { DEBUG_FUNCTION_LINE_VERBOSE("Using cached value"); return; } - // Reset current infos - DirList dirList("fs:/vol/external01/wiiu/apps", ".rpx,.wuhb", DirList::Files | DirList::CheckSubfolders, 1); - dirList.SortList(); - for (int i = 0; i < dirList.GetFilecount(); i++) { + std::vector listOfExecutables; + + if (gHideHomebrew) { + struct stat st {}; + if (stat(HOMEBREW_LAUNCHER_PATH, &st) >= 0) { + listOfExecutables.emplace_back(HOMEBREW_LAUNCHER_PATH); + } else if (stat(HOMEBREW_LAUNCHER_PATH2, &st) >= 0) { + listOfExecutables.emplace_back(HOMEBREW_LAUNCHER_PATH2); + } + } else { + // Reset current infos + DirList dirList(HOMEBREW_APPS_DIRECTORY, ".rpx,.wuhb", DirList::Files | DirList::CheckSubfolders, 1); + dirList.SortList(); + + if (gPreferWUHBOverRPX) { + // map<[path without extension], vector<[extension]>> + std::map> pathWithoutExtensionMap; + for (int i = 0; i < dirList.GetFilecount(); i++) { + std::string pathNoExtension = StringTools::remove_extension(dirList.GetFilepath(i)); + if (pathWithoutExtensionMap.count(pathNoExtension) == 0) { + pathWithoutExtensionMap[pathNoExtension] = std::vector(); + } + pathWithoutExtensionMap[pathNoExtension].push_back(StringTools::get_extension(dirList.GetFilename(i))); + } + + for (auto &l : pathWithoutExtensionMap) { + if (l.second.size() == 1 && l.second.at(0) == ".rpx") { + listOfExecutables.push_back(l.first + ".rpx"); + } else { + listOfExecutables.push_back(l.first + ".wuhb"); + } + } + } else { + for (int i = 0; i < dirList.GetFilecount(); i++) { + listOfExecutables.emplace_back(dirList.GetFilepath(i)); + } + } + } + + for (auto &filePath : listOfExecutables) { + auto filename = StringTools::FullpathToFilename(filePath.c_str()); + //! skip wiiload temp files - if (gHideHomebrew && strcasecmp(dirList.GetFilename(i), "homebrew_launcher.wuhb") != 0) { + if (strcasecmp(filename, "temp.rpx") == 0) { continue; } //! skip wiiload temp files - if (strcasecmp(dirList.GetFilename(i), "temp.rpx") == 0) { + if (strcasecmp(filename, "temp.wuhb") == 0) { continue; } //! skip wiiload temp files - if (strcasecmp(dirList.GetFilename(i), "temp.wuhb") == 0) { - continue; - } - - //! skip wiiload temp files - if (strcasecmp(dirList.GetFilename(i), "temp2.wuhb") == 0) { + if (strcasecmp(filename, "temp2.wuhb") == 0) { continue; } //! skip hidden linux and mac files - if (dirList.GetFilename(i)[0] == '.' || dirList.GetFilename(i)[0] == '_') { + if (filename[0] == '.' || filename[0] == '_') { continue; } - auto repl = "fs:/vol/external01/"; - auto input = dirList.GetFilepath(i); + auto input = filePath.c_str(); const char *relativeFilepath; - if (std::string_view(input).starts_with(repl)) { + if (filePath.starts_with(repl)) { relativeFilepath = &input[strlen(repl)]; } else { DEBUG_FUNCTION_LINE_ERR("Skip %s, Path doesn't start with %s (This should never happen", input, repl); @@ -360,10 +431,10 @@ void readCustomTitlesFromSD() { const char *indexedDevice = "mlc"; strncpy(cur_title_info->indexedDevice, indexedDevice, sizeof(cur_title_info->indexedDevice) - 1); - fileInfo->filename = dirList.GetFilename(i); - fileInfo->longname = dirList.GetFilename(i); - fileInfo->shortname = dirList.GetFilename(i); - fileInfo->author = dirList.GetFilename(i); + fileInfo->filename = filename; + fileInfo->longname = filename; + fileInfo->shortname = filename; + fileInfo->author = filename; // System apps don't have a splash screen. cur_title_info->appType = MCP_APP_TYPE_SYSTEM_APPS; @@ -376,7 +447,7 @@ void readCustomTitlesFromSD() { #define TMP_BUNDLE_NAME "romfscheck" - if (WUHBUtils_MountBundle(TMP_BUNDLE_NAME, dirList.GetFilepath(i), BundleSource_FileDescriptor, &result) == WUHB_UTILS_RESULT_SUCCESS && result >= 0) { + if (WUHBUtils_MountBundle(TMP_BUNDLE_NAME, filePath.c_str(), BundleSource_FileDescriptor, &result) == WUHB_UTILS_RESULT_SUCCESS && result >= 0) { fileInfo->isBundle = true; uint8_t *buffer; uint32_t bufferSize; @@ -410,7 +481,7 @@ void readCustomTitlesFromSD() { DEBUG_FUNCTION_LINE_ERR("Failed to unmount \"%s\"", TMP_BUNDLE_NAME); } } else { - DEBUG_FUNCTION_LINE_ERR("%s is not a valid .wuhb file: %d", dirList.GetFilepath(i), result); + DEBUG_FUNCTION_LINE_ERR("%s is not a valid .wuhb file: %d", filePath.c_str(), result); continue; } } @@ -425,6 +496,7 @@ void readCustomTitlesFromSD() { fileInfos.push_front(fileInfo); } + OSMemoryBarrier(); } bool CheckFileExistsHelper(const char *path) { diff --git a/src/utils/StringTools.cpp b/src/utils/StringTools.cpp index 38cd47b..13fcc9a 100644 --- a/src/utils/StringTools.cpp +++ b/src/utils/StringTools.cpp @@ -30,6 +30,19 @@ #include #include +std::string StringTools::remove_extension(const std::string &filename) { + size_t lastdot = filename.find_last_of('.'); + if (lastdot == std::string::npos) return filename; + return filename.substr(0, lastdot); +} + +std::string StringTools::get_extension(const std::string &filename) { + size_t lastdot = filename.find_last_of('.'); + if (lastdot == std::string::npos) return ""; + return filename.substr(lastdot); +} + + int32_t StringTools::strtokcmp(const char *string, const char *compare, const char *separator) { if (!string || !compare) return -1; diff --git a/src/utils/StringTools.h b/src/utils/StringTools.h index 5a96490..96bfc14 100644 --- a/src/utils/StringTools.h +++ b/src/utils/StringTools.h @@ -40,6 +40,10 @@ std::string string_format(const std::string &format, Args... args) { class StringTools { public: + static std::string remove_extension(const std::string &filename); + + static std::string get_extension(const std::string &filename); + static int32_t strtokcmp(const char *string, const char *compare, const char *separator); static const char *FullpathToFilename(const char *path);