Add option to hide .rpx if a .wuhb with the same name exists

This commit is contained in:
Maschell 2022-09-21 12:15:54 +02:00
parent fa50152356
commit 3bdde5c1d4
4 changed files with 138 additions and 33 deletions

View File

@ -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) [![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.) (`[ENVIRONMENT]` is a placeholder for the actual environment name.)
1. Copy the file `homebrew_on_menu.wps` into `sd:/wiiu/environments/[ENVIRONMENT]/plugins`. 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`. 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`. 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 ## 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`. 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. 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 ## Buildflags
### Logging ### Logging

View File

@ -19,6 +19,7 @@
#include <forward_list> #include <forward_list>
#include <fs/DirList.h> #include <fs/DirList.h>
#include <malloc.h> #include <malloc.h>
#include <map>
#include <mutex> #include <mutex>
#include <nn/acp.h> #include <nn/acp.h>
#include <optional> #include <optional>
@ -61,8 +62,20 @@ void readCustomTitlesFromSD();
WUPS_USE_WUT_DEVOPTAB(); WUPS_USE_WUT_DEVOPTAB();
WUPS_USE_STORAGE("homebrew_on_menu"); // Use the storage API WUPS_USE_STORAGE("homebrew_on_menu"); // Use the storage API
bool gHideHomebrew = false; #define HIDE_HOMEBREW_STRING "hideHomebrew"
bool prevHideValue = false; #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() { INITIALIZE_PLUGIN() {
memset((void *) &current_launched_title_info, 0, sizeof(current_launched_title_info)); memset((void *) &current_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); DEBUG_FUNCTION_LINE_ERR("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
} else { } else {
// Try to get value from storage // 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. // 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) { if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to store bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); 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 // Close storage
WUPS_CloseStorage(); WUPS_CloseStorage();
@ -118,9 +144,20 @@ INITIALIZE_PLUGIN() {
void hideHomebrewChanged(ConfigItemBoolean *item, bool newValue) { void hideHomebrewChanged(ConfigItemBoolean *item, bool newValue) {
DEBUG_FUNCTION_LINE_VERBOSE("New value in gHideHomebrew: %d", newValue); DEBUG_FUNCTION_LINE_VERBOSE("New value in gHideHomebrew: %d", newValue);
gHideHomebrew = 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) { if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE_ERR("Failed to store bool: %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); DEBUG_FUNCTION_LINE_ERR("Failed to store bool: %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
} }
@ -141,7 +178,8 @@ WUPS_GET_CONFIG() {
WUPSConfigCategoryHandle cat; WUPSConfigCategoryHandle cat;
WUPSConfig_AddCategoryByNameHandled(config, "Features", &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; return config;
} }
@ -157,13 +195,14 @@ WUPS_CONFIG_CLOSED() {
DEBUG_FUNCTION_LINE_ERR("Failed to close storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes); DEBUG_FUNCTION_LINE_ERR("Failed to close storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
} }
if (prevHideValue != gHideHomebrew) { if (prevHideValue != gHideHomebrew || prevPreferWUHBOverRPXValue != gPreferWUHBOverRPX) {
if (!sTitleRebooting) { if (!sTitleRebooting) {
_SYSLaunchTitleWithStdArgsInNoSplash(OSGetTitleID(), nullptr); _SYSLaunchTitleWithStdArgsInNoSplash(OSGetTitleID(), nullptr);
sTitleRebooting = true; sTitleRebooting = true;
} }
} }
prevHideValue = gHideHomebrew; prevHideValue = gHideHomebrew;
prevPreferWUHBOverRPXValue = gPreferWUHBOverRPX;
} }
void Cleanup() { void Cleanup() {
@ -303,42 +342,74 @@ void readCustomTitlesFromSD() {
DEBUG_FUNCTION_LINE_VERBOSE("Using cached value"); DEBUG_FUNCTION_LINE_VERBOSE("Using cached value");
return; 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<std::string> 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<std::string, std::vector<std::string>> 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<std::string>();
}
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 //! skip wiiload temp files
if (gHideHomebrew && strcasecmp(dirList.GetFilename(i), "homebrew_launcher.wuhb") != 0) { if (strcasecmp(filename, "temp.rpx") == 0) {
continue; continue;
} }
//! skip wiiload temp files //! skip wiiload temp files
if (strcasecmp(dirList.GetFilename(i), "temp.rpx") == 0) { if (strcasecmp(filename, "temp.wuhb") == 0) {
continue; continue;
} }
//! skip wiiload temp files //! skip wiiload temp files
if (strcasecmp(dirList.GetFilename(i), "temp.wuhb") == 0) { if (strcasecmp(filename, "temp2.wuhb") == 0) {
continue;
}
//! skip wiiload temp files
if (strcasecmp(dirList.GetFilename(i), "temp2.wuhb") == 0) {
continue; continue;
} }
//! skip hidden linux and mac files //! skip hidden linux and mac files
if (dirList.GetFilename(i)[0] == '.' || dirList.GetFilename(i)[0] == '_') { if (filename[0] == '.' || filename[0] == '_') {
continue; continue;
} }
auto repl = "fs:/vol/external01/"; auto repl = "fs:/vol/external01/";
auto input = dirList.GetFilepath(i); auto input = filePath.c_str();
const char *relativeFilepath; const char *relativeFilepath;
if (std::string_view(input).starts_with(repl)) { if (filePath.starts_with(repl)) {
relativeFilepath = &input[strlen(repl)]; relativeFilepath = &input[strlen(repl)];
} else { } else {
DEBUG_FUNCTION_LINE_ERR("Skip %s, Path doesn't start with %s (This should never happen", input, repl); 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"; const char *indexedDevice = "mlc";
strncpy(cur_title_info->indexedDevice, indexedDevice, sizeof(cur_title_info->indexedDevice) - 1); strncpy(cur_title_info->indexedDevice, indexedDevice, sizeof(cur_title_info->indexedDevice) - 1);
fileInfo->filename = dirList.GetFilename(i); fileInfo->filename = filename;
fileInfo->longname = dirList.GetFilename(i); fileInfo->longname = filename;
fileInfo->shortname = dirList.GetFilename(i); fileInfo->shortname = filename;
fileInfo->author = dirList.GetFilename(i); fileInfo->author = filename;
// System apps don't have a splash screen. // System apps don't have a splash screen.
cur_title_info->appType = MCP_APP_TYPE_SYSTEM_APPS; cur_title_info->appType = MCP_APP_TYPE_SYSTEM_APPS;
@ -376,7 +447,7 @@ void readCustomTitlesFromSD() {
#define TMP_BUNDLE_NAME "romfscheck" #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; fileInfo->isBundle = true;
uint8_t *buffer; uint8_t *buffer;
uint32_t bufferSize; uint32_t bufferSize;
@ -410,7 +481,7 @@ void readCustomTitlesFromSD() {
DEBUG_FUNCTION_LINE_ERR("Failed to unmount \"%s\"", TMP_BUNDLE_NAME); DEBUG_FUNCTION_LINE_ERR("Failed to unmount \"%s\"", TMP_BUNDLE_NAME);
} }
} else { } 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; continue;
} }
} }
@ -425,6 +496,7 @@ void readCustomTitlesFromSD() {
fileInfos.push_front(fileInfo); fileInfos.push_front(fileInfo);
} }
OSMemoryBarrier();
} }
bool CheckFileExistsHelper(const char *path) { bool CheckFileExistsHelper(const char *path) {

View File

@ -30,6 +30,19 @@
#include <utils/StringTools.h> #include <utils/StringTools.h>
#include <wut_types.h> #include <wut_types.h>
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) { int32_t StringTools::strtokcmp(const char *string, const char *compare, const char *separator) {
if (!string || !compare) if (!string || !compare)
return -1; return -1;

View File

@ -40,6 +40,10 @@ std::string string_format(const std::string &format, Args... args) {
class StringTools { class StringTools {
public: 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 int32_t strtokcmp(const char *string, const char *compare, const char *separator);
static const char *FullpathToFilename(const char *path); static const char *FullpathToFilename(const char *path);