Implement support for a .ignore file in sd:/wiiu/apps

This commit is contained in:
Maschell 2023-03-21 15:08:18 +01:00
parent 84958ee3a3
commit 49e6b50af1
6 changed files with 291 additions and 0 deletions

View File

@ -28,6 +28,24 @@ Via the plugin config menu (press L, DPAD Down and Minus on the gamepad) you can
- Hide all .rpx (Default is false) - Hide all .rpx (Default is false)
- Hides all `.rpx` from the Wii U Menu. - Hides all `.rpx` from the Wii U Menu.
## Hide specific apps
A list of executables to hide can be provided via `sd:/wiiu/apps/.ignore`. This file is a text file, each line contains a file/pattern to exclude.
Every line is considered case-insensitive and relative to `sd:/wiiu/apps/`. Lines starting with "#" will be ignored and can be used for comments. `*` is supported as a wildcard.
Example:
```
# Ignore any .rpx in the my_game sub directory
my_game/*.rpx
# Ignore any executable in the other_game sub directory
other_game/*
# Ignore any executable that starts with "retroarch"
retroarch*
*/retroarch*
# Ignore sd:/wiiu/apps/CoolGame.wuhb
CoolGame.wuhb
```
## 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.

View File

@ -2,11 +2,13 @@
#include "FileInfos.h" #include "FileInfos.h"
#include "SaveRedirection.h" #include "SaveRedirection.h"
#include "filelist.h" #include "filelist.h"
#include "fs/CFile.hpp"
#include "fs/FSUtils.h" #include "fs/FSUtils.h"
#include "fs/FileReader.h" #include "fs/FileReader.h"
#include "fs/FileReaderWUHB.h" #include "fs/FileReaderWUHB.h"
#include "utils/StringTools.h" #include "utils/StringTools.h"
#include "utils/ini.h" #include "utils/ini.h"
#include <algorithm>
#include <content_redirection/redirection.h> #include <content_redirection/redirection.h>
#include <coreinit/cache.h> #include <coreinit/cache.h>
#include <coreinit/debug.h> #include <coreinit/debug.h>
@ -16,6 +18,7 @@
#include <coreinit/systeminfo.h> #include <coreinit/systeminfo.h>
#include <coreinit/title.h> #include <coreinit/title.h>
#include <cstring> #include <cstring>
#include <fnmatch.h>
#include <forward_list> #include <forward_list>
#include <fs/DirList.h> #include <fs/DirList.h>
#include <malloc.h> #include <malloc.h>
@ -67,6 +70,7 @@ WUPS_USE_STORAGE("homebrew_on_menu"); // Use the storage API
#define HIDE_ALL_RPX_STRING "hideAllRPX" #define HIDE_ALL_RPX_STRING "hideAllRPX"
#define HOMEBREW_APPS_DIRECTORY "fs:/vol/external01/wiiu/apps" #define HOMEBREW_APPS_DIRECTORY "fs:/vol/external01/wiiu/apps"
#define IGNORE_FILE_PATH HOMEBREW_APPS_DIRECTORY "/.ignore"
#define HOMEBREW_LAUNCHER_FILENAME "homebrew_launcher.wuhb" #define HOMEBREW_LAUNCHER_FILENAME "homebrew_launcher.wuhb"
#define HOMEBREW_LAUNCHER_OPTIONAL_DIRECTORY "homebrew_launcher" #define HOMEBREW_LAUNCHER_OPTIONAL_DIRECTORY "homebrew_launcher"
@ -82,6 +86,8 @@ bool prevHideAllRPX = false;
bool gHomebrewLauncherExists = false; bool gHomebrewLauncherExists = false;
std::vector<std::string> gIgnorePatterns;
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));
memset((void *) &gLaunchXML, 0, sizeof(gLaunchXML)); memset((void *) &gLaunchXML, 0, sizeof(gLaunchXML));
@ -266,6 +272,32 @@ ON_APPLICATION_START() {
initLogging(); initLogging();
sSDIsMounted = false; sSDIsMounted = false;
CFile file(IGNORE_FILE_PATH, CFile::ReadOnly);
if (file.isOpen()) {
std::string strBuffer;
strBuffer.resize(file.size());
file.read((uint8_t *) &strBuffer[0], strBuffer.size());
file.close();
//! remove all windows crap signs
size_t position;
while (true) {
position = strBuffer.find('\r');
if (position == std::string::npos) {
break;
}
strBuffer.erase(position, 1);
}
gIgnorePatterns = StringTools::StringSplit(strBuffer, "\n");
// Ignore all lines that start with '#'
gIgnorePatterns.erase(std::remove_if(gIgnorePatterns.begin(), gIgnorePatterns.end(), [](auto &line) { return line.starts_with('#'); }), gIgnorePatterns.end());
} else {
DEBUG_FUNCTION_LINE_ERR("No ignore found");
}
if (OSGetTitleID() == 0x0005001010040000L || // Wii U Menu JPN if (OSGetTitleID() == 0x0005001010040000L || // Wii U Menu JPN
OSGetTitleID() == 0x0005001010040100L || // Wii U Menu USA OSGetTitleID() == 0x0005001010040100L || // Wii U Menu USA
OSGetTitleID() == 0x0005001010040200L) { // Wii U Menu EUR OSGetTitleID() == 0x0005001010040200L) { // Wii U Menu EUR
@ -427,6 +459,20 @@ void readCustomTitlesFromSD() {
listOfExecutables.emplace_back(dirList.GetFilepath(i)); listOfExecutables.emplace_back(dirList.GetFilepath(i));
} }
} }
// Remove any executable that matches the ignore pattern.
listOfExecutables.erase(std::remove_if(listOfExecutables.begin(), listOfExecutables.end(), [&](const auto &item) {
auto path = item.substr(strlen(HOMEBREW_APPS_DIRECTORY) + 1);
return std::ranges::any_of(gIgnorePatterns.begin(), gIgnorePatterns.end(),
[&](const auto &pattern) {
if (fnmatch(pattern.c_str(), path.c_str(), FNM_CASEFOLD) == 0) {
DEBUG_FUNCTION_LINE_INFO("Ignore \"%s\" because it matched pattern \"%s\"", path.c_str(), pattern.c_str());
return true;
}
return false;
});
}),
listOfExecutables.end());
} }
for (auto &filePath : listOfExecutables) { for (auto &filePath : listOfExecutables) {

View File

@ -91,3 +91,26 @@ void StringTools::RemoveDoubleSlashs(std::string &str) {
} }
} }
} }
std::vector<std::string> StringTools::StringSplit(const std::string &inValue, const std::string &splitter) {
std::string value = inValue;
std::vector<std::string> result;
while (true) {
unsigned int index = value.find(splitter);
if (index == std::string::npos) {
result.push_back(value);
break;
}
std::string first = value.substr(0, index);
result.push_back(first);
if (index + splitter.size() == value.length()) {
result.push_back("");
break;
}
if (index + splitter.size() > value.length()) {
break;
}
value = value.substr(index + splitter.size(), value.length());
}
return result;
}

View File

@ -49,4 +49,6 @@ public:
static const char *FullpathToFilename(const char *path); static const char *FullpathToFilename(const char *path);
static void RemoveDoubleSlashs(std::string &str); static void RemoveDoubleSlashs(std::string &str);
static std::vector<std::string> StringSplit(const std::string &inValue, const std::string &splitter);
}; };

200
src/utils/fnmatch.c Normal file
View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 1989, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Guido van Rossum.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* From FreeBSD fnmatch.c 1.11
* $Id: fnmatch.c,v 1.3 1997/08/19 02:34:30 jdp Exp $
*/
#define _GNU_SOURCE
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94";
#endif /* LIBC_SCCS and not lint */
/*
* Function fnmatch() as specified in POSIX 1003.2-1992, section B.6.
* Compares a filename or pathname to a pattern.
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "fnmatch.h"
#define EOS '\0'
#define FNM_PREFIX_DIRS 0x20 /* Directory prefixes of pattern match too. */
static const char *rangematch(const char *, char, int);
int fnmatch(const char *pattern, const char *string, int flags) {
const char *stringstart;
char c, test;
for (stringstart = string;;)
switch (c = *pattern++) {
case EOS:
if ((flags & FNM_LEADING_DIR) && *string == '/')
return (0);
return (*string == EOS ? 0 : FNM_NOMATCH);
case '?':
if (*string == EOS)
return (FNM_NOMATCH);
if (*string == '/' && (flags & FNM_PATHNAME))
return (FNM_NOMATCH);
if (*string == '.' && (flags & FNM_PERIOD) &&
(string == stringstart ||
((flags & FNM_PATHNAME) && *(string - 1) == '/')))
return (FNM_NOMATCH);
++string;
break;
case '*':
c = *pattern;
/* Collapse multiple stars. */
while (c == '*')
c = *++pattern;
if (*string == '.' && (flags & FNM_PERIOD) &&
(string == stringstart ||
((flags & FNM_PATHNAME) && *(string - 1) == '/')))
return (FNM_NOMATCH);
/* Optimize for pattern with * at end or before /. */
if (c == EOS)
if (flags & FNM_PATHNAME)
return ((flags & FNM_LEADING_DIR) ||
strchr(string, '/') == NULL
? 0
: FNM_NOMATCH);
else
return (0);
else if (c == '/' && flags & FNM_PATHNAME) {
if ((string = strchr(string, '/')) == NULL)
return (FNM_NOMATCH);
break;
}
/* General case, use recursion. */
while ((test = *string) != EOS) {
if (!fnmatch(pattern, string, flags & ~FNM_PERIOD))
return (0);
if (test == '/' && flags & FNM_PATHNAME)
break;
++string;
}
return (FNM_NOMATCH);
case '[':
if (*string == EOS)
return (FNM_NOMATCH);
if (*string == '/' && flags & FNM_PATHNAME)
return (FNM_NOMATCH);
if ((pattern =
rangematch(pattern, *string, flags)) == NULL)
return (FNM_NOMATCH);
++string;
break;
case '\\':
if (!(flags & FNM_NOESCAPE)) {
if ((c = *pattern++) == EOS) {
c = '\\';
--pattern;
}
}
/* FALLTHROUGH */
default:
if (c == *string)
;
else if ((flags & FNM_CASEFOLD) &&
(tolower((unsigned char) c) ==
tolower((unsigned char) *string)))
;
else if ((flags & FNM_PREFIX_DIRS) && *string == EOS &&
((c == '/' && string != stringstart) ||
(string == stringstart + 1 && *stringstart == '/')))
return (0);
else
return (FNM_NOMATCH);
string++;
break;
}
/* NOTREACHED */
}
static const char *
rangematch(const char *pattern, char test, int flags) {
int negate, ok;
char c, c2;
/*
* A bracket expression starting with an unquoted circumflex
* character produces unspecified results (IEEE 1003.2-1992,
* 3.13.2). This implementation treats it like '!', for
* consistency with the regular expression syntax.
* J.T. Conklin (conklin@ngai.kaleida.com)
*/
if ((negate = (*pattern == '!' || *pattern == '^')))
++pattern;
if (flags & FNM_CASEFOLD)
test = tolower((unsigned char) test);
for (ok = 0; (c = *pattern++) != ']';) {
if (c == '\\' && !(flags & FNM_NOESCAPE))
c = *pattern++;
if (c == EOS)
return (NULL);
if (flags & FNM_CASEFOLD)
c = tolower((unsigned char) c);
if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && c2 != ']') {
pattern += 2;
if (c2 == '\\' && !(flags & FNM_NOESCAPE))
c2 = *pattern++;
if (c2 == EOS)
return (NULL);
if (flags & FNM_CASEFOLD)
c2 = tolower((unsigned char) c2);
if ((unsigned char) c <= (unsigned char) test &&
(unsigned char) test <= (unsigned char) c2)
ok = 1;
} else if (c == test)
ok = 1;
}
return (ok == negate ? NULL : pattern);
}

View File

@ -36,6 +36,7 @@ extern "C" {
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##INFO ## ", "", FMT, ##ARGS)
#else #else
@ -47,6 +48,7 @@ extern "C" {
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(OSReport, "##ERROR## ", "\n", FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(OSReport, "##WARN ## ", "\n", FMT, ##ARGS) #define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(OSReport, "##WARN ## ", "\n", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX(OSReport, "##INFO ## ", "\n", FMT, ##ARGS)
#endif #endif