mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Allow opening directories with OpenFileSystem
This commit is contained in:
parent
036e048208
commit
a006816a2e
@ -14,6 +14,8 @@ namespace LibHac.Common
|
||||
public ReadOnlySpan<byte> Value => _buffer;
|
||||
public int Length => _buffer.Length;
|
||||
|
||||
public static U8Span Empty => default;
|
||||
|
||||
public byte this[int i]
|
||||
{
|
||||
get => _buffer[i];
|
||||
|
@ -15,5 +15,9 @@ namespace LibHac.Fs
|
||||
public static readonly U8String SdCardFileSystemMountName = new U8String("@Sdcard");
|
||||
public static readonly U8String HostRootFileSystemMountName = new U8String("@Host");
|
||||
public static readonly U8String RegisteredUpdatePartitionMountName = new U8String("@RegUpdate");
|
||||
|
||||
public const char GameCardFileSystemMountNameUpdateSuffix = 'U';
|
||||
public const char GameCardFileSystemMountNameNormalSuffix = 'N';
|
||||
public const char GameCardFileSystemMountNameSecureSuffix = 'S';
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using static LibHac.Fs.PathTool;
|
||||
|
||||
@ -28,5 +29,53 @@ namespace LibHac.Fs
|
||||
(IsSeparator(path.GetUnsafe(0)) && IsSeparator(path.GetUnsafe(1)) ||
|
||||
IsAltSeparator(path.GetUnsafe(0)) && IsAltSeparator(path.GetUnsafe(1)));
|
||||
}
|
||||
|
||||
public static int GetWindowsPathSkipLength(U8Span path)
|
||||
{
|
||||
if (IsWindowsDrive(path))
|
||||
return 2;
|
||||
|
||||
if (!IsUnc(path))
|
||||
return 0;
|
||||
|
||||
for (int i = 2; i < path.Length && !IsNullTerminator(path[i]); i++)
|
||||
{
|
||||
byte c = path[i];
|
||||
if (c == (byte)'$' || IsDriveSeparator(c))
|
||||
{
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Result VerifyPath(U8Span path, int maxPathLength, int maxNameLength)
|
||||
{
|
||||
Debug.Assert(!path.IsNull());
|
||||
|
||||
int nameLength = 0;
|
||||
|
||||
for (int i = 0; i < path.Length && i <= maxPathLength && nameLength <= maxNameLength; i++)
|
||||
{
|
||||
byte c = path[i];
|
||||
|
||||
if (IsNullTerminator(c))
|
||||
return Result.Success;
|
||||
|
||||
// todo: Compare path based on their Unicode code points
|
||||
|
||||
if (c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|')
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
|
||||
nameLength++;
|
||||
if (c == '\\' || c == '/')
|
||||
{
|
||||
nameLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,5 +6,6 @@ namespace LibHac.FsService.Creators
|
||||
public interface ISubDirectoryFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path);
|
||||
Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path, bool preserveUnc);
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using LibHac.Fs;
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public interface ITargetManagerFileSystemCreator
|
||||
{
|
||||
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
|
||||
Result GetCaseSensitivePath(out bool isSuccess, ref string path);
|
||||
Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path);
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class StorageOnNcaCreator : IStorageOnNcaCreator
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private bool IsEnabledProgramVerification { get; set; }
|
||||
private Keyset Keyset { get; }
|
||||
|
||||
@ -39,7 +40,7 @@ namespace LibHac.FsService.Creators
|
||||
|
||||
storage = storageTemp;
|
||||
fsHeader = nca.Header.GetFsHeader(fsIndex);
|
||||
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
|
@ -7,13 +7,18 @@ namespace LibHac.FsService.Creators
|
||||
public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator
|
||||
{
|
||||
public Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path)
|
||||
{
|
||||
return Create(out subDirFileSystem, baseFileSystem, path, false);
|
||||
}
|
||||
|
||||
public Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path, bool preserveUnc)
|
||||
{
|
||||
subDirFileSystem = default;
|
||||
|
||||
Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String());
|
||||
rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String(), preserveUnc);
|
||||
subDirFileSystem = fs;
|
||||
return rc;
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace LibHac.FsService.Creators
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result GetCaseSensitivePath(out bool isSuccess, ref string path)
|
||||
public Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
@ -41,11 +42,13 @@ namespace LibHac.FsService
|
||||
// Get a reference to the path that will be advanced as each part of the path is parsed
|
||||
U8Span path2 = path.Slice(0, StringUtils.GetLength(path));
|
||||
|
||||
Result rc = OpenFileSystemFromMountName(ref path2, out IFileSystem baseFileSystem, out bool successQQ,
|
||||
// Open the root filesystem based on the path's mount name
|
||||
Result rc = OpenFileSystemFromMountName(ref path2, out IFileSystem baseFileSystem, out bool shouldContinue,
|
||||
out MountNameInfo mountNameInfo);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (!successQQ)
|
||||
// Don't continue if the rest of the path is empty
|
||||
if (!shouldContinue)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard)
|
||||
@ -65,7 +68,19 @@ namespace LibHac.FsService
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (!mountNameInfo.IsHostFs)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
if (type == FileSystemProxyType.Manual)
|
||||
{
|
||||
rc = TryOpenCaseSensitiveContentDirectory(out IFileSystem manualFileSystem, baseFileSystem, path2);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = new ReadOnlyFileSystem(manualFileSystem);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return TryOpenContentDirectory(path2, out fileSystem, baseFileSystem, type, true);
|
||||
}
|
||||
|
||||
rc = TryOpenNsp(ref path2, out IFileSystem nspFileSystem, baseFileSystem);
|
||||
@ -123,23 +138,52 @@ namespace LibHac.FsService
|
||||
public bool CanMountNca;
|
||||
}
|
||||
|
||||
private Result OpenFileSystemFromMountName(ref U8Span path, out IFileSystem fileSystem, out bool successQQ,
|
||||
private Result OpenFileSystemFromMountName(ref U8Span path, out IFileSystem fileSystem, out bool shouldContinue,
|
||||
out MountNameInfo info)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
info = new MountNameInfo();
|
||||
successQQ = true;
|
||||
shouldContinue = true;
|
||||
|
||||
if (StringUtils.Compare(path, CommonMountNames.GameCardFileSystemMountName,
|
||||
CommonMountNames.GameCardFileSystemMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.GameCardFileSystemMountName.Length);
|
||||
|
||||
if (StringUtils.GetLength(path.Value, 9) < 9)
|
||||
return ResultFs.InvalidPath.Log();
|
||||
|
||||
GameCardPartition partition;
|
||||
switch ((char)path[0])
|
||||
{
|
||||
case CommonMountNames.GameCardFileSystemMountNameUpdateSuffix:
|
||||
partition = GameCardPartition.Update;
|
||||
break;
|
||||
case CommonMountNames.GameCardFileSystemMountNameNormalSuffix:
|
||||
partition = GameCardPartition.Normal;
|
||||
break;
|
||||
case CommonMountNames.GameCardFileSystemMountNameSecureSuffix:
|
||||
partition = GameCardPartition.Secure;
|
||||
break;
|
||||
default:
|
||||
return ResultFs.InvalidPath.Log();
|
||||
}
|
||||
|
||||
path = path.Slice(1);
|
||||
bool handleParsed = Utf8Parser.TryParse(path, out int handle, out int bytesConsumed);
|
||||
|
||||
if (!handleParsed || handle == -1 || bytesConsumed != 8)
|
||||
return ResultFs.InvalidPath.Log();
|
||||
|
||||
path = path.Slice(8);
|
||||
|
||||
Result rc = OpenGameCardFileSystem(out fileSystem, new GameCardHandle(handle), partition);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
info.GcHandle = handle;
|
||||
info.IsGameCard = true;
|
||||
info.CanMountNca = true;
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSystemMountName,
|
||||
@ -228,7 +272,8 @@ namespace LibHac.FsService
|
||||
info.IsHostFs = true;
|
||||
info.CanMountNca = true;
|
||||
|
||||
throw new NotImplementedException();
|
||||
Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.RegisteredUpdatePartitionMountName,
|
||||
@ -248,7 +293,7 @@ namespace LibHac.FsService
|
||||
|
||||
if (StringUtils.GetLength(path, FsPath.MaxLength) == 0)
|
||||
{
|
||||
successQQ = false;
|
||||
shouldContinue = false;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
@ -293,14 +338,106 @@ namespace LibHac.FsService
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
private Result TryOpenContentDirectory(U8Span path, out IFileSystem contentFileSystem,
|
||||
IFileSystem baseFileSystem, FileSystemProxyType fsType, bool preserveUnc)
|
||||
{
|
||||
contentFileSystem = default;
|
||||
|
||||
FsPath fullPath;
|
||||
unsafe { _ = &fullPath; } // workaround for CS0165
|
||||
|
||||
Result rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs,
|
||||
baseFileSystem, path, preserveUnc);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return OpenSubDirectoryForFsType(out contentFileSystem, subDirFs, fsType);
|
||||
}
|
||||
|
||||
private Result TryOpenCaseSensitiveContentDirectory(out IFileSystem contentFileSystem,
|
||||
IFileSystem baseFileSystem, U8Span path)
|
||||
{
|
||||
contentFileSystem = default;
|
||||
FsPath fullPath;
|
||||
unsafe { _ = &fullPath; } // workaround for CS0165
|
||||
|
||||
var pathBuilder = new PathBuilder(fullPath.Str);
|
||||
Result rc = pathBuilder.Append(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = pathBuilder.Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' });
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
pathBuilder.Terminate();
|
||||
|
||||
rc = FsCreators.TargetManagerFileSystemCreator.GetCaseSensitivePath(out bool success, fullPath.Str);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
// Reopen the host filesystem as case sensitive
|
||||
if (success)
|
||||
{
|
||||
baseFileSystem.Dispose();
|
||||
|
||||
rc = OpenHostFileSystem(out baseFileSystem, U8Span.Empty, openCaseSensitive: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
return FsCreators.SubDirectoryFileSystemCreator.Create(out contentFileSystem, baseFileSystem, fullPath);
|
||||
}
|
||||
|
||||
private Result OpenSubDirectoryForFsType(out IFileSystem fileSystem, IFileSystem baseFileSystem,
|
||||
FileSystemProxyType fsType)
|
||||
{
|
||||
fileSystem = default;
|
||||
ReadOnlySpan<byte> dirName;
|
||||
|
||||
// Get the name of the subdirectory for the filesystem type
|
||||
switch (fsType)
|
||||
{
|
||||
case FileSystemProxyType.Package:
|
||||
fileSystem = baseFileSystem;
|
||||
return Result.Success;
|
||||
|
||||
case FileSystemProxyType.Code:
|
||||
dirName = new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'d', (byte)'e', (byte)'/' };
|
||||
break;
|
||||
case FileSystemProxyType.Rom:
|
||||
case FileSystemProxyType.Control:
|
||||
case FileSystemProxyType.Manual:
|
||||
case FileSystemProxyType.Meta:
|
||||
// Nintendo doesn't include the Data case in the switch. Maybe an oversight?
|
||||
case FileSystemProxyType.Data:
|
||||
case FileSystemProxyType.RegisteredUpdate:
|
||||
dirName = new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' };
|
||||
break;
|
||||
case FileSystemProxyType.Logo:
|
||||
dirName = new[] { (byte)'/', (byte)'l', (byte)'o', (byte)'g', (byte)'o', (byte)'/' };
|
||||
break;
|
||||
|
||||
default:
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
// Open the subdirectory filesystem
|
||||
Result rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs, baseFileSystem,
|
||||
new U8Span(dirName));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (fsType == FileSystemProxyType.Code)
|
||||
{
|
||||
rc = FsCreators.StorageOnNcaCreator.VerifyAcidSignature(subDirFs, null);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
fileSystem = subDirFs;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result TryOpenNsp(ref U8Span path, out IFileSystem outFileSystem, IFileSystem baseFileSystem)
|
||||
{
|
||||
outFileSystem = default;
|
||||
|
||||
ReadOnlySpan<byte> nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' };
|
||||
|
||||
int searchEnd = path.Length - 4;
|
||||
|
||||
// Search for the end of the nsp part of the path
|
||||
int nspPathLen = 0;
|
||||
|
||||
@ -447,6 +584,7 @@ namespace LibHac.FsService
|
||||
if (Crypto.CryptoUtil.IsSameBytes(rightsId.AsBytes(), zero.AsBytes(), Unsafe.SizeOf<RightsId>()))
|
||||
return Result.Success;
|
||||
|
||||
// ReSharper disable once UnusedVariable
|
||||
Result rc = ExternalKeys.Get(rightsId, out AccessKey accessKey);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
@ -604,7 +742,53 @@ namespace LibHac.FsService
|
||||
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
|
||||
GameCardPartition partitionId)
|
||||
{
|
||||
return FsCreators.GameCardFileSystemCreator.Create(out fileSystem, handle, partitionId);
|
||||
Result rc;
|
||||
int tries = 0;
|
||||
|
||||
do
|
||||
{
|
||||
rc = FsCreators.GameCardFileSystemCreator.Create(out fileSystem, handle, partitionId);
|
||||
|
||||
if (!ResultFs.DataCorrupted.Includes(rc))
|
||||
break;
|
||||
|
||||
tries++;
|
||||
} while (tries < 2);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out IFileSystem fileSystem, U8Span path, bool openCaseSensitive)
|
||||
{
|
||||
fileSystem = default;
|
||||
Result rc;
|
||||
|
||||
if (!path.IsEmpty())
|
||||
{
|
||||
rc = Util.VerifyHostPath(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, openCaseSensitive);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (path.IsEmpty())
|
||||
{
|
||||
rc = hostFs.GetEntryType(out _, "C:/".ToU8Span());
|
||||
|
||||
// Nintendo ignores all results other than this one
|
||||
if (ResultFs.TargetNotFound.Includes(rc))
|
||||
return rc;
|
||||
|
||||
fileSystem = hostFs;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs, hostFs, path, preserveUnc: true);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fileSystem = subDirFs;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey)
|
||||
|
@ -10,12 +10,13 @@ namespace LibHac.FsService
|
||||
bool createPathIfMissing)
|
||||
{
|
||||
subFileSystem = default;
|
||||
Result rc;
|
||||
|
||||
if (!createPathIfMissing)
|
||||
{
|
||||
if (path == null) return ResultFs.NullptrArgument.Log();
|
||||
|
||||
Result rc = baseFileSystem.GetEntryType(out DirectoryEntryType entryType, path.ToU8Span());
|
||||
rc = baseFileSystem.GetEntryType(out DirectoryEntryType entryType, path.ToU8Span());
|
||||
|
||||
if (rc.IsFailure() || entryType != DirectoryEntryType.Directory)
|
||||
{
|
||||
@ -23,7 +24,8 @@ namespace LibHac.FsService
|
||||
}
|
||||
}
|
||||
|
||||
baseFileSystem.EnsureDirectoryExists(path);
|
||||
rc = baseFileSystem.EnsureDirectoryExists(path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
return CreateSubFileSystemImpl(out subFileSystem, baseFileSystem, path);
|
||||
}
|
||||
@ -42,6 +44,29 @@ namespace LibHac.FsService
|
||||
return rc;
|
||||
}
|
||||
|
||||
public static Result VerifyHostPath(U8Span path)
|
||||
{
|
||||
if(path.IsEmpty())
|
||||
return Result.Success;
|
||||
|
||||
if (path[0] != StringTraits.DirectorySeparator)
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
U8Span path2 = path.Slice(1);
|
||||
|
||||
if(path2.IsEmpty())
|
||||
return Result.Success;
|
||||
|
||||
int skipLength = PathUtility.GetWindowsPathSkipLength(path2);
|
||||
int remainingLength = PathTools.MaxPathLength - skipLength;
|
||||
|
||||
Result rc = PathUtility.VerifyPath(path2.Slice(skipLength), remainingLength, remainingLength);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
var normalizer = new PathNormalizer(path, PathNormalizer.Option.PreserveUnc);
|
||||
return normalizer.Result;
|
||||
}
|
||||
|
||||
public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId)
|
||||
{
|
||||
return spaceId == SaveDataSpaceId.System ||
|
||||
|
Loading…
x
Reference in New Issue
Block a user