mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add a pattern option to EnumerateEntries
This commit is contained in:
parent
defab1a229
commit
828c1f5b54
5 changed files with 427 additions and 9 deletions
13
src/LibHac/Compatibility/Env.cs
Normal file
13
src/LibHac/Compatibility/Env.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LibHac.Compatibility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains variables describing runtime environment info
|
||||||
|
/// needed for compatibility code.
|
||||||
|
/// </summary>
|
||||||
|
internal static class Env
|
||||||
|
{
|
||||||
|
public static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;
|
||||||
|
}
|
||||||
|
}
|
367
src/LibHac/Compatibility/FileSystemName.cs
Normal file
367
src/LibHac/Compatibility/FileSystemName.cs
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
|
||||||
|
// This code was introduced in .NET Core 2.1
|
||||||
|
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace LibHac.Compatibility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides methods for matching file system names.
|
||||||
|
/// </summary>
|
||||||
|
internal static class FileSystemName
|
||||||
|
{
|
||||||
|
private static readonly char[] WildcardChars =
|
||||||
|
{
|
||||||
|
'\"', '<', '>', '*', '?'
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly char[] SimpleWildcardChars =
|
||||||
|
{
|
||||||
|
'*', '?'
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return true if the given expression matches the given name. '*' and '?' are wildcards, '\' escapes.
|
||||||
|
/// </summary>
|
||||||
|
public static bool MatchesSimpleExpression(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase = true)
|
||||||
|
{
|
||||||
|
return MatchPattern(expression, name, ignoreCase, useExtendedWildcards: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matching routine description
|
||||||
|
// ============================
|
||||||
|
// (copied from native impl)
|
||||||
|
//
|
||||||
|
// This routine compares a Dbcs name and an expression and tells the caller
|
||||||
|
// if the name is in the language defined by the expression. The input name
|
||||||
|
// cannot contain wildcards, while the expression may contain wildcards.
|
||||||
|
//
|
||||||
|
// Expression wild cards are evaluated as shown in the nondeterministic
|
||||||
|
// finite automatons below. Note that ~* and ~? are DOS_STAR and DOS_QM.
|
||||||
|
//
|
||||||
|
// ~* is DOS_STAR, ~? is DOS_QM, and ~. is DOS_DOT
|
||||||
|
//
|
||||||
|
// S
|
||||||
|
// <-----<
|
||||||
|
// X | | e Y
|
||||||
|
// X * Y == (0)----->-(1)->-----(2)-----(3)
|
||||||
|
//
|
||||||
|
// S-.
|
||||||
|
// <-----<
|
||||||
|
// X | | e Y
|
||||||
|
// X ~* Y == (0)----->-(1)->-----(2)-----(3)
|
||||||
|
//
|
||||||
|
// X S S Y
|
||||||
|
// X ?? Y == (0)---(1)---(2)---(3)---(4)
|
||||||
|
//
|
||||||
|
// X . . Y
|
||||||
|
// X ~.~. Y == (0)---(1)----(2)------(3)---(4)
|
||||||
|
// | |________|
|
||||||
|
// | ^ |
|
||||||
|
// |_______________|
|
||||||
|
// ^EOF or .^
|
||||||
|
//
|
||||||
|
// X S-. S-. Y
|
||||||
|
// X ~?~? Y == (0)---(1)-----(2)-----(3)---(4)
|
||||||
|
// | |________|
|
||||||
|
// | ^ |
|
||||||
|
// |_______________|
|
||||||
|
// ^EOF or .^
|
||||||
|
//
|
||||||
|
// where S is any single character
|
||||||
|
// S-. is any single character except the final .
|
||||||
|
// e is a null character transition
|
||||||
|
// EOF is the end of the name string
|
||||||
|
//
|
||||||
|
// In words:
|
||||||
|
//
|
||||||
|
// * matches 0 or more characters.
|
||||||
|
// ? matches exactly 1 character.
|
||||||
|
// DOS_STAR matches 0 or more characters until encountering and matching
|
||||||
|
// the final . in the name.
|
||||||
|
// DOS_QM matches any single character, or upon encountering a period or
|
||||||
|
// end of name string, advances the expression to the end of the
|
||||||
|
// set of contiguous DOS_QMs.
|
||||||
|
// DOS_DOT matches either a . or zero characters beyond name string.
|
||||||
|
|
||||||
|
private static bool MatchPattern(ReadOnlySpan<char> expression, ReadOnlySpan<char> name, bool ignoreCase, bool useExtendedWildcards)
|
||||||
|
{
|
||||||
|
// The idea behind the algorithm is pretty simple. We keep track of all possible locations
|
||||||
|
// in the regular expression that are matching the name. When the name has been exhausted,
|
||||||
|
// if one of the locations in the expression is also just exhausted, the name is in the
|
||||||
|
// language defined by the regular expression.
|
||||||
|
|
||||||
|
if (expression.Length == 0 || name.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (expression[0] == '*')
|
||||||
|
{
|
||||||
|
// Just * matches everything
|
||||||
|
if (expression.Length == 1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
ReadOnlySpan<char> expressionEnd = expression.Slice(1);
|
||||||
|
if (expressionEnd.IndexOfAny(useExtendedWildcards ? WildcardChars : SimpleWildcardChars) == -1)
|
||||||
|
{
|
||||||
|
// Handle the special case of a single starting *, which essentially means "ends with"
|
||||||
|
|
||||||
|
// If the name doesn't have enough characters to match the remaining expression, it can't be a match.
|
||||||
|
if (name.Length < expressionEnd.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// See if we end with the expression
|
||||||
|
return name.EndsWith(expressionEnd, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nameOffset = 0;
|
||||||
|
int expressionOffset;
|
||||||
|
|
||||||
|
int priorMatch;
|
||||||
|
int currentMatch;
|
||||||
|
int priorMatchCount;
|
||||||
|
int matchCount = 1;
|
||||||
|
|
||||||
|
char nameChar = '\0';
|
||||||
|
char expressionChar;
|
||||||
|
|
||||||
|
// ReSharper disable once RedundantAssignment
|
||||||
|
Span<int> temp = stackalloc int[0];
|
||||||
|
Span<int> currentMatches = stackalloc int[16];
|
||||||
|
Span<int> priorMatches = stackalloc int[16];
|
||||||
|
priorMatches[0] = 0;
|
||||||
|
|
||||||
|
int maxState = expression.Length * 2;
|
||||||
|
int currentState;
|
||||||
|
bool nameFinished = false;
|
||||||
|
|
||||||
|
// Walk through the name string, picking off characters. We go one
|
||||||
|
// character beyond the end because some wild cards are able to match
|
||||||
|
// zero characters beyond the end of the string.
|
||||||
|
//
|
||||||
|
// With each new name character we determine a new set of states that
|
||||||
|
// match the name so far. We use two arrays that we swap back and forth
|
||||||
|
// for this purpose. One array lists the possible expression states for
|
||||||
|
// all name characters up to but not including the current one, and other
|
||||||
|
// array is used to build up the list of states considering the current
|
||||||
|
// name character as well. The arrays are then switched and the process
|
||||||
|
// repeated.
|
||||||
|
//
|
||||||
|
// There is not a one-to-one correspondence between state number and
|
||||||
|
// offset into the expression. State numbering is not continuous.
|
||||||
|
// This allows a simple conversion between state number and expression
|
||||||
|
// offset. Each character in the expression can represent one or two
|
||||||
|
// states. * and DOS_STAR generate two states: expressionOffset * 2 and
|
||||||
|
// expressionOffset * 2 + 1. All other expression characters can produce
|
||||||
|
// only a single state. Thus expressionOffset = currentState / 2.
|
||||||
|
|
||||||
|
while (!nameFinished)
|
||||||
|
{
|
||||||
|
if (nameOffset < name.Length)
|
||||||
|
{
|
||||||
|
// Not at the end of the name. Grab the current character and move the offset forward.
|
||||||
|
nameChar = name[nameOffset++];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// At the end of the name. If the expression is exhausted, exit.
|
||||||
|
if (priorMatches[matchCount - 1] == maxState)
|
||||||
|
break;
|
||||||
|
|
||||||
|
nameFinished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now, for each of the previous stored expression matches, see what
|
||||||
|
// we can do with this name character.
|
||||||
|
priorMatch = 0;
|
||||||
|
currentMatch = 0;
|
||||||
|
priorMatchCount = 0;
|
||||||
|
|
||||||
|
while (priorMatch < matchCount)
|
||||||
|
{
|
||||||
|
// We have to carry on our expression analysis as far as possible for each
|
||||||
|
// character of name, so we loop here until the expression stops matching.
|
||||||
|
|
||||||
|
expressionOffset = (priorMatches[priorMatch++] + 1) / 2;
|
||||||
|
|
||||||
|
while (expressionOffset < expression.Length)
|
||||||
|
{
|
||||||
|
currentState = expressionOffset * 2;
|
||||||
|
expressionChar = expression[expressionOffset];
|
||||||
|
|
||||||
|
// We may be about to exhaust the local space for matches,
|
||||||
|
// so we have to reallocate if this is the case.
|
||||||
|
if (currentMatch >= currentMatches.Length - 2)
|
||||||
|
{
|
||||||
|
int newSize = currentMatches.Length * 2;
|
||||||
|
temp = new int[newSize];
|
||||||
|
currentMatches.CopyTo(temp);
|
||||||
|
currentMatches = temp;
|
||||||
|
|
||||||
|
temp = new int[newSize];
|
||||||
|
priorMatches.CopyTo(temp);
|
||||||
|
priorMatches = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expressionChar == '*')
|
||||||
|
{
|
||||||
|
// '*' matches any character zero or more times.
|
||||||
|
// ReSharper disable once RedundantJumpStatement
|
||||||
|
goto MatchZeroOrMore;
|
||||||
|
}
|
||||||
|
else if (useExtendedWildcards && expressionChar == '<')
|
||||||
|
{
|
||||||
|
// '<' (DOS_STAR) matches any character except '.' zero or more times.
|
||||||
|
|
||||||
|
// If we are at a period, determine if we are allowed to
|
||||||
|
// consume it, i.e. make sure it is not the last one.
|
||||||
|
|
||||||
|
bool notLastPeriod = false;
|
||||||
|
if (!nameFinished && nameChar == '.')
|
||||||
|
{
|
||||||
|
for (int offset = nameOffset; offset < name.Length; offset++)
|
||||||
|
{
|
||||||
|
if (name[offset] == '.')
|
||||||
|
{
|
||||||
|
notLastPeriod = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nameFinished || nameChar != '.' || notLastPeriod)
|
||||||
|
{
|
||||||
|
// ReSharper disable once RedundantJumpStatement
|
||||||
|
goto MatchZeroOrMore;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We are at a period. We can only match zero
|
||||||
|
// characters (i.e. the epsilon transition).
|
||||||
|
goto MatchZero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The remaining expression characters all match by consuming a character,
|
||||||
|
// so we need to force the expression and state forward.
|
||||||
|
currentState += 2;
|
||||||
|
|
||||||
|
if (useExtendedWildcards && expressionChar == '>')
|
||||||
|
{
|
||||||
|
// '>' (DOS_QM) is the most complicated. If the name is finished,
|
||||||
|
// we can match zero characters. If this name is a '.', we
|
||||||
|
// don't match, but look at the next expression. Otherwise
|
||||||
|
// we match a single character.
|
||||||
|
if (nameFinished || nameChar == '.')
|
||||||
|
goto NextExpressionCharacter;
|
||||||
|
|
||||||
|
currentMatches[currentMatch++] = currentState;
|
||||||
|
goto ExpressionFinished;
|
||||||
|
}
|
||||||
|
else if (useExtendedWildcards && expressionChar == '"')
|
||||||
|
{
|
||||||
|
// A '"' (DOS_DOT) can match either a period, or zero characters
|
||||||
|
// beyond the end of name.
|
||||||
|
if (nameFinished)
|
||||||
|
{
|
||||||
|
goto NextExpressionCharacter;
|
||||||
|
}
|
||||||
|
else if (nameChar == '.')
|
||||||
|
{
|
||||||
|
currentMatches[currentMatch++] = currentState;
|
||||||
|
}
|
||||||
|
goto ExpressionFinished;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (expressionChar == '\\')
|
||||||
|
{
|
||||||
|
// Escape character, try to move the expression forward again and match literally.
|
||||||
|
if (++expressionOffset == expression.Length)
|
||||||
|
{
|
||||||
|
currentMatches[currentMatch++] = maxState;
|
||||||
|
goto ExpressionFinished;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentState = expressionOffset * 2 + 2;
|
||||||
|
expressionChar = expression[expressionOffset];
|
||||||
|
}
|
||||||
|
|
||||||
|
// From this point on a name character is required to even
|
||||||
|
// continue, let alone make a match.
|
||||||
|
if (nameFinished) goto ExpressionFinished;
|
||||||
|
|
||||||
|
if (expressionChar == '?')
|
||||||
|
{
|
||||||
|
// If this expression was a '?' we can match it once.
|
||||||
|
currentMatches[currentMatch++] = currentState;
|
||||||
|
}
|
||||||
|
else if (ignoreCase
|
||||||
|
? char.ToUpperInvariant(expressionChar) == char.ToUpperInvariant(nameChar)
|
||||||
|
: expressionChar == nameChar)
|
||||||
|
{
|
||||||
|
// Matched a non-wildcard character
|
||||||
|
currentMatches[currentMatch++] = currentState;
|
||||||
|
}
|
||||||
|
|
||||||
|
goto ExpressionFinished;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchZeroOrMore:
|
||||||
|
currentMatches[currentMatch++] = currentState;
|
||||||
|
MatchZero:
|
||||||
|
currentMatches[currentMatch++] = currentState + 1;
|
||||||
|
NextExpressionCharacter:
|
||||||
|
if (++expressionOffset == expression.Length)
|
||||||
|
currentMatches[currentMatch++] = maxState;
|
||||||
|
} // while (expressionOffset < expression.Length)
|
||||||
|
|
||||||
|
ExpressionFinished:
|
||||||
|
|
||||||
|
// Prevent duplication in the destination array.
|
||||||
|
//
|
||||||
|
// Each of the arrays is monotonically increasing and non-duplicating, thus we skip
|
||||||
|
// over any source element in the source array if we just added the same element to
|
||||||
|
// the destination array. This guarantees non-duplication in the destination array.
|
||||||
|
|
||||||
|
if ((priorMatch < matchCount) && (priorMatchCount < currentMatch))
|
||||||
|
{
|
||||||
|
while (priorMatchCount < currentMatch)
|
||||||
|
{
|
||||||
|
int previousLength = priorMatches.Length;
|
||||||
|
while ((priorMatch < previousLength) && (priorMatches[priorMatch] < currentMatches[priorMatchCount]))
|
||||||
|
{
|
||||||
|
priorMatch++;
|
||||||
|
}
|
||||||
|
priorMatchCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // while (sourceCount < matchesCount)
|
||||||
|
|
||||||
|
// If we found no matches in the just finished iteration it's time to bail.
|
||||||
|
if (currentMatch == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Swap the meaning the two arrays
|
||||||
|
temp = priorMatches;
|
||||||
|
priorMatches = currentMatches;
|
||||||
|
currentMatches = temp;
|
||||||
|
|
||||||
|
matchCount = currentMatch;
|
||||||
|
} // while (!nameFinished)
|
||||||
|
|
||||||
|
currentState = priorMatches[matchCount - 1];
|
||||||
|
|
||||||
|
return currentState == maxState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -4,12 +4,10 @@ using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace LibHac
|
namespace LibHac.Compatibility
|
||||||
{
|
{
|
||||||
internal class Compat
|
internal static class Rsa
|
||||||
{
|
{
|
||||||
public static bool IsMono { get; } = Type.GetType("Mono.Runtime") != null;
|
|
||||||
|
|
||||||
public static bool Rsa2048PssVerifyMono(byte[] data, byte[] signature, byte[] modulus)
|
public static bool Rsa2048PssVerifyMono(byte[] data, byte[] signature, byte[] modulus)
|
||||||
{
|
{
|
||||||
const int rsaLen = 0x100;
|
const int rsaLen = 0x100;
|
|
@ -157,9 +157,9 @@ namespace LibHac
|
||||||
public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus)
|
public static Validity Rsa2048PssVerify(byte[] data, byte[] signature, byte[] modulus)
|
||||||
{
|
{
|
||||||
#if NETFRAMEWORK
|
#if NETFRAMEWORK
|
||||||
if (Compat.IsMono)
|
if (Compatibility.Env.IsMono)
|
||||||
{
|
{
|
||||||
return Compat.Rsa2048PssVerifyMono(data, signature, modulus)
|
return Compatibility.Rsa.Rsa2048PssVerifyMono(data, signature, modulus)
|
||||||
? Validity.Valid
|
? Validity.Valid
|
||||||
: Validity.Invalid;
|
: Validity.Invalid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,10 @@ using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
#if !NETFRAMEWORK
|
||||||
|
using System.IO.Enumeration;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace LibHac.IO
|
namespace LibHac.IO
|
||||||
{
|
{
|
||||||
public static class FileSystemExtensions
|
public static class FileSystemExtensions
|
||||||
|
@ -55,18 +59,35 @@ namespace LibHac.IO
|
||||||
source.CopyFileSystem(destFs, logger);
|
source.CopyFileSystem(destFs, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
return fileSystem.OpenDirectory("/", OpenDirectoryMode.All).EnumerateEntries();
|
||||||
|
}
|
||||||
|
|
||||||
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory)
|
||||||
{
|
{
|
||||||
|
return directory.EnumerateEntries("*", SearchOptions.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<DirectoryEntry> EnumerateEntries(this IDirectory directory, string searchPattern, SearchOptions searchOptions)
|
||||||
|
{
|
||||||
|
bool ignoreCase = searchOptions.HasFlag(SearchOptions.CaseInsensitive);
|
||||||
|
bool recurse = searchOptions.HasFlag(SearchOptions.RecurseSubdirectories);
|
||||||
|
|
||||||
IFileSystem fs = directory.ParentFileSystem;
|
IFileSystem fs = directory.ParentFileSystem;
|
||||||
|
|
||||||
foreach (DirectoryEntry entry in directory.Read())
|
foreach (DirectoryEntry entry in directory.Read())
|
||||||
{
|
{
|
||||||
yield return entry;
|
if (MatchesPattern(searchPattern, entry.Name, ignoreCase))
|
||||||
if (entry.Type != DirectoryEntryType.Directory) continue;
|
{
|
||||||
|
yield return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.Type != DirectoryEntryType.Directory || !recurse) continue;
|
||||||
|
|
||||||
IDirectory subDir = fs.OpenDirectory(directory.FullPath + '/' + entry.Name, OpenDirectoryMode.All);
|
IDirectory subDir = fs.OpenDirectory(directory.FullPath + '/' + entry.Name, OpenDirectoryMode.All);
|
||||||
|
|
||||||
foreach (DirectoryEntry subEntry in subDir.EnumerateEntries())
|
foreach (DirectoryEntry subEntry in subDir.EnumerateEntries(searchPattern, searchOptions))
|
||||||
{
|
{
|
||||||
yield return subEntry;
|
yield return subEntry;
|
||||||
}
|
}
|
||||||
|
@ -123,5 +144,24 @@ namespace LibHac.IO
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool MatchesPattern(string searchPattern, string name, bool ignoreCase)
|
||||||
|
{
|
||||||
|
#if NETFRAMEWORK
|
||||||
|
return Compatibility.FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(),
|
||||||
|
name.AsSpan(), ignoreCase);
|
||||||
|
#else
|
||||||
|
return FileSystemName.MatchesSimpleExpression(searchPattern.AsSpan(),
|
||||||
|
name.AsSpan(), ignoreCase);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum SearchOptions
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
RecurseSubdirectories = 1 << 0,
|
||||||
|
CaseInsensitive = 1 << 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue