mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Merge pull request #121 from Thealexbarney/open-filesystem
- Add OpenFileSystem in FileSystemProxy - Add OpenHostFileSystem - Add FS client shims for OpenHostFileSystem
This commit is contained in:
commit
cd5d46c81b
@ -81,6 +81,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
||||
2,4464,,AllocationTableIteratedRangeEntry,
|
||||
|
||||
2,4501,4599,NcaCorrupted,
|
||||
2,4512,,InvalidNcaFsType,
|
||||
2,4527,,InvalidNcaProgramId,
|
||||
|
||||
2,4601,4639,IntegrityVerificationStorageCorrupted,
|
||||
2,4602,,InvalidIvfcMagic,
|
||||
|
|
@ -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];
|
||||
|
@ -12,6 +12,7 @@ namespace LibHac.Common
|
||||
private int _length;
|
||||
|
||||
public bool Overflowed { get; private set; }
|
||||
public int Length => _length;
|
||||
public int Capacity => _buffer.Length - NullTerminatorLength;
|
||||
|
||||
public U8StringBuilder(Span<byte> buffer)
|
||||
|
@ -4,6 +4,8 @@ namespace LibHac.Fs
|
||||
{
|
||||
internal static class CommonMountNames
|
||||
{
|
||||
public const char ReservedMountNamePrefixCharacter = '@';
|
||||
|
||||
public static readonly U8String GameCardFileSystemMountName = new U8String("@Gc");
|
||||
public static readonly U8String ContentStorageSystemMountName = new U8String("@SystemContent");
|
||||
public static readonly U8String ContentStorageUserMountName = new U8String("@UserContent");
|
||||
@ -15,5 +17,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';
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ namespace LibHac.Fs
|
||||
{
|
||||
public class FileStorage2 : StorageBase
|
||||
{
|
||||
private const long InvalidSize = -1;
|
||||
protected const long InvalidSize = -1;
|
||||
|
||||
private IFile BaseFile { get; set; }
|
||||
private long FileSize { get; set; }
|
||||
protected long FileSize { get; set; }
|
||||
|
||||
public FileStorage2(IFile baseFile)
|
||||
{
|
||||
|
@ -9,7 +9,10 @@ namespace LibHac.Fs
|
||||
private IFileSystem BaseFileSystem { get; set; }
|
||||
private IFile BaseFile { get; set; }
|
||||
|
||||
private FileStorageBasedFileSystem() { }
|
||||
private FileStorageBasedFileSystem()
|
||||
{
|
||||
FileSize = InvalidSize;
|
||||
}
|
||||
|
||||
public static Result CreateNew(out FileStorageBasedFileSystem created, IFileSystem baseFileSystem, U8Span path,
|
||||
OpenMode mode)
|
||||
|
@ -150,6 +150,30 @@ namespace LibHac.Fs
|
||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
|
||||
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (result.IsFailure())
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, 0, message, caller);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, FileHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (result.IsFailure())
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OutputAccessLogUnlessResultSuccess(Result result, TimeSpan startTime, TimeSpan endTime, DirectoryHandle handle, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (result.IsFailure())
|
||||
{
|
||||
OutputAccessLogImpl(result, startTime, endTime, handle.GetId(), message, caller);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OutputAccessLogImpl(Result result, TimeSpan startTime, TimeSpan endTime, int handleId,
|
||||
string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
@ -208,6 +232,27 @@ namespace LibHac.Fs
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
public Result RunOperationWithAccessLogOnFailure(AccessLogTarget logTarget, Func<Result> operation,
|
||||
Func<string> textGenerator, [CallerMemberName] string caller = "")
|
||||
{
|
||||
Result rc;
|
||||
|
||||
if (IsEnabledAccessLog(logTarget))
|
||||
{
|
||||
TimeSpan startTime = Time.GetCurrent();
|
||||
rc = operation();
|
||||
TimeSpan endTime = Time.GetCurrent();
|
||||
|
||||
OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, textGenerator(), caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = operation();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
@ -187,4 +187,11 @@ namespace LibHac.Fs
|
||||
Nand = 1,
|
||||
SdCard = 2
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum MountHostOption
|
||||
{
|
||||
None = 0,
|
||||
PseudoCaseSensitive = 1
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using LibHac.Common;
|
||||
using static LibHac.Fs.CommonMountNames;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
@ -23,6 +24,11 @@ namespace LibHac.Fs
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static bool IsReservedMountName(U8Span name)
|
||||
{
|
||||
return (uint)name.Length > 0 && name[0] == ReservedMountNamePrefixCharacter;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private static bool CheckMountNameImpl(U8Span name)
|
||||
{
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -171,6 +171,10 @@ namespace LibHac.Fs
|
||||
|
||||
/// <summary>Error code: 2002-4501; Range: 4501-4599; Inner value: 0x232a02</summary>
|
||||
public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4501, 4599); }
|
||||
/// <summary>Error code: 2002-4512; Inner value: 0x234002</summary>
|
||||
public static Result.Base InvalidNcaFsType => new Result.Base(ModuleFs, 4512);
|
||||
/// <summary>Error code: 2002-4527; Inner value: 0x235e02</summary>
|
||||
public static Result.Base InvalidNcaProgramId => new Result.Base(ModuleFs, 4527);
|
||||
|
||||
/// <summary>Error code: 2002-4601; Range: 4601-4639; Inner value: 0x23f202</summary>
|
||||
public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4601, 4639); }
|
||||
|
374
src/LibHac/Fs/Shim/Host.cs
Normal file
374
src/LibHac/Fs/Shim/Host.cs
Normal file
@ -0,0 +1,374 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.FsService;
|
||||
using LibHac.FsSystem;
|
||||
using static LibHac.Fs.CommonMountNames;
|
||||
|
||||
namespace LibHac.Fs.Shim
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains functions for mounting file systems from a host computer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All functions in this file are based on SDK 9.3
|
||||
/// </remarks>
|
||||
public static class Host
|
||||
{
|
||||
private static ReadOnlySpan<byte> HostRootFileSystemPath => new[]
|
||||
{(byte) '@', (byte) 'H', (byte) 'o', (byte) 's', (byte) 't', (byte) ':', (byte) '/'};
|
||||
|
||||
private const int HostRootFileSystemPathLength = 8;
|
||||
|
||||
private class HostCommonMountNameGenerator : ICommonMountNameGenerator
|
||||
{
|
||||
private FsPath _path;
|
||||
|
||||
public HostCommonMountNameGenerator(U8Span path)
|
||||
{
|
||||
StringUtils.Copy(_path.Str, path);
|
||||
|
||||
int pathLength = StringUtils.GetLength(_path.Str);
|
||||
if (pathLength != 0 && _path.Str[pathLength - 1] == StringTraits.DirectorySeparator)
|
||||
{
|
||||
_path.Str[pathLength - 1] = StringTraits.NullTerminator;
|
||||
}
|
||||
}
|
||||
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
int requiredNameBufferSize = StringUtils.GetLength(_path.Str, FsPath.MaxLength) + HostRootFileSystemPathLength;
|
||||
|
||||
if (nameBuffer.Length < requiredNameBufferSize)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
int size = new U8StringBuilder(nameBuffer).Append(HostRootFileSystemPath).Append(_path.Str).Length;
|
||||
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
private class HostRootCommonMountNameGenerator : ICommonMountNameGenerator
|
||||
{
|
||||
public Result GenerateCommonMountName(Span<byte> nameBuffer)
|
||||
{
|
||||
const int requiredNameBufferSize = HostRootFileSystemPathLength;
|
||||
|
||||
Debug.Assert(nameBuffer.Length >= requiredNameBufferSize);
|
||||
|
||||
int size = StringUtils.Copy(nameBuffer, HostRootFileSystemPath);
|
||||
Debug.Assert(size == requiredNameBufferSize - 1);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts the C:\ drive of a host Windows computer at @Host:/
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public static Result MountHostRoot(this FileSystemClient fs)
|
||||
{
|
||||
IFileSystem hostFileSystem = default;
|
||||
var path = new FsPath();
|
||||
path.Str[0] = 0;
|
||||
|
||||
static string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}\"";
|
||||
|
||||
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, MountHostOption.None);
|
||||
|
||||
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
|
||||
new HostRootCommonMountNameGenerator());
|
||||
|
||||
// Open the host file system
|
||||
Result result =
|
||||
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
// Mount the host file system
|
||||
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts the C:\ drive of a host Windows computer at @Host:/
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="option">Options for mounting the host file system.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option)
|
||||
{
|
||||
IFileSystem hostFileSystem = default;
|
||||
var path = new FsPath();
|
||||
path.Str[0] = 0;
|
||||
|
||||
string LogMessageGenerator() =>
|
||||
$", name: \"{HostRootFileSystemMountName.ToString()}, mount_host_option: {option}\"";
|
||||
|
||||
Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, option);
|
||||
|
||||
Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem,
|
||||
new HostRootCommonMountNameGenerator());
|
||||
|
||||
// Open the host file system
|
||||
Result result =
|
||||
fs.RunOperationWithAccessLogOnFailure(AccessLogTarget.Application, OpenHostFs, LogMessageGenerator);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
// Mount the host file system
|
||||
result = fs.RunOperationWithAccessLog(AccessLogTarget.Application, MountHostFs, LogMessageGenerator);
|
||||
if (result.IsFailure()) return result;
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
fs.EnableFileSystemAccessorAccessLog(HostRootFileSystemMountName);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unmounts the file system at @Host:/
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
public static void UnmountHostRoot(this FileSystemClient fs)
|
||||
{
|
||||
fs.Unmount(HostRootFileSystemMountName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a directory on a host Windows computer at the specified mount point.
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
|
||||
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path)
|
||||
{
|
||||
return MountHostImpl(fs, mountName, path, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a directory on a host Windows computer at the specified mount point.
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
|
||||
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
|
||||
/// <param name="option">Options for mounting the host file system.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
public static Result MountHost(this FileSystemClient fs, U8Span mountName, U8Span path, MountHostOption option)
|
||||
{
|
||||
return MountHostImpl(fs, mountName, path, option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a directory on a host Windows computer at the specified mount point.
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
|
||||
/// <param name="path">The path on the host computer to mount. e.g. C:\Windows\System32</param>
|
||||
/// <param name="optionalOption">Options for mounting the host file system. Specifying this parameter is optional.</param>
|
||||
/// <param name="caller">The caller of this function.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private static Result MountHostImpl(this FileSystemClient fs, U8Span mountName, U8Span path,
|
||||
MountHostOption? optionalOption, [CallerMemberName] string caller = "")
|
||||
{
|
||||
Result rc;
|
||||
ICommonMountNameGenerator nameGenerator;
|
||||
|
||||
string logMessage = null;
|
||||
var option = MountHostOption.None;
|
||||
|
||||
// Set the mount option if it was specified
|
||||
if (optionalOption.HasValue)
|
||||
{
|
||||
option = optionalOption.Value;
|
||||
}
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
if (optionalOption.HasValue)
|
||||
{
|
||||
logMessage = $", name: \"{mountName.ToString()}\", mount_host_option: {option}";
|
||||
}
|
||||
else
|
||||
{
|
||||
logMessage = $", name: \"{mountName.ToString()}\"";
|
||||
}
|
||||
|
||||
TimeSpan startTime = fs.Time.GetCurrent();
|
||||
rc = PreMountHost(out nameGenerator, mountName, path);
|
||||
TimeSpan endTime = fs.Time.GetCurrent();
|
||||
|
||||
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = PreMountHost(out nameGenerator, mountName, path);
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
IFileSystem hostFileSystem;
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
TimeSpan startTime = fs.Time.GetCurrent();
|
||||
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
|
||||
TimeSpan endTime = fs.Time.GetCurrent();
|
||||
|
||||
fs.OutputAccessLogUnlessResultSuccess(rc, startTime, endTime, logMessage, caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = OpenHostFileSystem(fs, out hostFileSystem, mountName, path, option);
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
TimeSpan startTime = fs.Time.GetCurrent();
|
||||
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
|
||||
TimeSpan endTime = fs.Time.GetCurrent();
|
||||
|
||||
fs.OutputAccessLog(rc, startTime, endTime, logMessage, caller);
|
||||
}
|
||||
else
|
||||
{
|
||||
rc = fs.Register(mountName, hostFileSystem, nameGenerator);
|
||||
}
|
||||
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (fs.IsEnabledAccessLog(AccessLogTarget.Application))
|
||||
{
|
||||
fs.EnableFileSystemAccessorAccessLog(mountName);
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="ICommonMountNameGenerator"/> based on the <paramref name="mountName"/> and
|
||||
/// <paramref name="path"/>, and verifies the <paramref name="mountName"/>.
|
||||
/// </summary>
|
||||
/// <param name="nameGenerator">If successful, the created <see cref="ICommonMountNameGenerator"/>.</param>
|
||||
/// <param name="mountName">The mount name at which the file system will be mounted.</param>
|
||||
/// <param name="path">The path that will be opened on the host computer. e.g. C:\Windows\System32</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private static Result PreMountHost(out ICommonMountNameGenerator nameGenerator, U8Span mountName, U8Span path)
|
||||
{
|
||||
nameGenerator = default;
|
||||
|
||||
Result rc = MountHelpers.CheckMountName(mountName);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
nameGenerator = new HostCommonMountNameGenerator(path);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies parameters and opens a host file system.
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="fileSystem">If successful, the opened host file system.</param>
|
||||
/// <param name="mountName">The mount name to be verified.</param>
|
||||
/// <param name="path">The path on the host computer to open. e.g. C:\Windows\System32</param>
|
||||
/// <param name="option">Options for opening the host file system.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private static Result OpenHostFileSystem(FileSystemClient fs, out IFileSystem fileSystem, U8Span mountName,
|
||||
U8Span path, MountHostOption option)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
if (mountName.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (path.IsNull())
|
||||
return ResultFs.NullptrArgument.Log();
|
||||
|
||||
if (PathUtility.IsWindowsDrive(mountName))
|
||||
return ResultFs.InvalidMountName.Log();
|
||||
|
||||
if (MountHelpers.IsReservedMountName(mountName))
|
||||
return ResultFs.InvalidMountName.Log();
|
||||
|
||||
bool needsTrailingSeparator = false;
|
||||
int pathLength = StringUtils.GetLength(path, PathTools.MaxPathLength + 1);
|
||||
|
||||
if (pathLength != 0 && PathTool.IsSeparator(path[pathLength - 1]))
|
||||
{
|
||||
needsTrailingSeparator = true;
|
||||
pathLength++;
|
||||
}
|
||||
|
||||
if (pathLength + 1 > PathTools.MaxPathLength)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
FsPath fullPath;
|
||||
unsafe { _ = &fullPath; } // workaround for CS0165
|
||||
|
||||
var sb = new U8StringBuilder(fullPath.Str);
|
||||
sb.Append(StringTraits.DirectorySeparator).Append(path);
|
||||
|
||||
if (needsTrailingSeparator)
|
||||
{
|
||||
sb.Append(StringTraits.DirectorySeparator);
|
||||
}
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
// If the input path begins with "//", change any leading '/' characters to '\'
|
||||
if (PathTool.IsSeparator(fullPath.Str[1]) && PathTool.IsSeparator(fullPath.Str[2]))
|
||||
{
|
||||
for (int i = 1; PathTool.IsSeparator(fullPath.Str[i]); i++)
|
||||
{
|
||||
fullPath.Str[i] = StringTraits.AltDirectorySeparator;
|
||||
}
|
||||
}
|
||||
|
||||
return OpenHostFileSystemImpl(fs, out fileSystem, ref fullPath, option);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens a host file system via <see cref="IFileSystemProxy"/>.
|
||||
/// </summary>
|
||||
/// <param name="fs">The <see cref="FileSystemClient"/> to use.</param>
|
||||
/// <param name="fileSystem">If successful, the opened host file system.</param>
|
||||
/// <param name="path">The path on the host computer to open. e.g. /C:\Windows\System32/</param>
|
||||
/// <param name="option">Options for opening the host file system.</param>
|
||||
/// <returns>The <see cref="Result"/> of the operation.</returns>
|
||||
private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
|
||||
IFileSystem hostFs;
|
||||
|
||||
if (option == MountHostOption.None)
|
||||
{
|
||||
Result rc = fsProxy.OpenHostFileSystem(out hostFs, ref path);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
else
|
||||
{
|
||||
Result rc = fsProxy.OpenHostFileSystemWithOption(out hostFs, ref path, option);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
fileSystem = hostFs;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem.RomFs;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class RomFileSystemCreator : IRomFileSystemCreator
|
||||
{
|
||||
// todo: Implement properly
|
||||
public Result Create(out IFileSystem fileSystem, IStorage romFsStorage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
fileSystem = new RomFsFileSystem(romFsStorage);
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,47 @@
|
||||
using System;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsSystem.Detail;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
|
||||
namespace LibHac.FsService.Creators
|
||||
{
|
||||
public class StorageOnNcaCreator : IStorageOnNcaCreator
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private bool IsEnabledProgramVerification { get; set; }
|
||||
private Keyset Keyset { get; }
|
||||
|
||||
public StorageOnNcaCreator(Keyset keyset)
|
||||
{
|
||||
Keyset = keyset;
|
||||
}
|
||||
|
||||
// todo: Implement NcaReader and other Nca classes
|
||||
public Result Create(out IStorage storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
storage = default;
|
||||
fsHeader = default;
|
||||
|
||||
Result rc = OpenStorage(out IStorage storageTemp, nca, fsIndex);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (isCodeFs)
|
||||
{
|
||||
using (var codeFs = new PartitionFileSystemCore<StandardEntry>())
|
||||
{
|
||||
rc = codeFs.Initialize(storageTemp);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = VerifyAcidSignature(codeFs, nca);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
}
|
||||
|
||||
storage = storageTemp;
|
||||
fsHeader = nca.Header.GetFsHeader(fsIndex);
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result CreateWithPatch(out IStorage storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs)
|
||||
@ -18,12 +51,25 @@ namespace LibHac.FsService.Creators
|
||||
|
||||
public Result OpenNca(out Nca nca, IStorage ncaStorage)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
nca = new Nca(Keyset, ncaStorage);
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// todo
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result OpenStorage(out IStorage storage, Nca nca, int fsIndex)
|
||||
{
|
||||
storage = default;
|
||||
|
||||
if (!nca.SectionExists(fsIndex))
|
||||
return ResultFs.PartitionNotFound.Log();
|
||||
|
||||
storage = nca.OpenStorage(fsIndex, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
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();
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace LibHac.FsService
|
||||
|
||||
creators.RomFileSystemCreator = new RomFileSystemCreator();
|
||||
creators.PartitionFileSystemCreator = new PartitionFileSystemCreator();
|
||||
creators.StorageOnNcaCreator = new StorageOnNcaCreator();
|
||||
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keyset);
|
||||
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
|
||||
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
|
||||
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(keyset);
|
||||
|
@ -34,7 +34,28 @@ namespace LibHac.FsService
|
||||
|
||||
public Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, TitleId titleId, FileSystemProxyType type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
fileSystem = default;
|
||||
|
||||
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
|
||||
|
||||
bool canMountSystemDataPrivate = false;
|
||||
|
||||
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
|
||||
if (normalizer.Result.IsFailure()) return normalizer.Result;
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, titleId);
|
||||
}
|
||||
|
||||
private PathNormalizer.Option GetPathNormalizerOptions(U8Span path)
|
||||
{
|
||||
int hostMountLength = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName,
|
||||
PathTools.MountNameLengthMax);
|
||||
|
||||
bool isHostPath = StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountLength) == 0;
|
||||
|
||||
PathNormalizer.Option hostOption = isHostPath ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None;
|
||||
return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTailSeparator | hostOption;
|
||||
}
|
||||
|
||||
public Result OpenFileSystemWithPatch(out IFileSystem fileSystem, TitleId titleId, FileSystemProxyType type)
|
||||
@ -661,9 +682,16 @@ namespace LibHac.FsService
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath)
|
||||
public Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// Missing permission check
|
||||
|
||||
return FsProxyCore.OpenHostFileSystem(out fileSystem, new U8Span(path.Str), option.HasFlag(MountHostOption.PseudoCaseSensitive));
|
||||
}
|
||||
|
||||
public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path)
|
||||
{
|
||||
return OpenHostFileSystemWithOption(out fileSystem, ref path, MountHostOption.None);
|
||||
}
|
||||
|
||||
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
|
||||
|
@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Buffers.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Shim;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.FsService.Creators;
|
||||
using LibHac.FsSystem.NcaUtils;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Spl;
|
||||
using RightsId = LibHac.Fs.RightsId;
|
||||
|
||||
@ -22,7 +26,7 @@ namespace LibHac.FsService
|
||||
|
||||
private GlobalAccessLogMode LogMode { get; set; }
|
||||
public bool IsSdCardAccessible { get; set; }
|
||||
|
||||
|
||||
public FileSystemProxyCore(FileSystemCreators fsCreators, ExternalKeySet externalKeys, IDeviceOperator deviceOperator)
|
||||
{
|
||||
FsCreators = fsCreators;
|
||||
@ -30,6 +34,587 @@ namespace LibHac.FsService
|
||||
DeviceOperator = deviceOperator;
|
||||
}
|
||||
|
||||
public Result OpenFileSystem(out IFileSystem fileSystem, U8Span path, FileSystemProxyType type,
|
||||
bool canMountSystemDataPrivate, TitleId titleId)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
// 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));
|
||||
|
||||
// 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;
|
||||
|
||||
// Don't continue if the rest of the path is empty
|
||||
if (!shouldContinue)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard)
|
||||
{
|
||||
rc = OpenGameCardFileSystem(out fileSystem, new GameCardHandle(mountNameInfo.GcHandle),
|
||||
GameCardPartition.Logo);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
return Result.Success;
|
||||
|
||||
if (!ResultFs.PartitionNotFound.Includes(rc))
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = IsContentPathDir(ref path2, out bool isDirectory);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
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);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
// Must be the end of the path to open Application Package FS type
|
||||
if (path2.Length == 0 || path2[0] == 0)
|
||||
{
|
||||
if (type == FileSystemProxyType.Package)
|
||||
{
|
||||
fileSystem = nspFileSystem;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
baseFileSystem = nspFileSystem;
|
||||
}
|
||||
|
||||
if (!mountNameInfo.CanMountNca)
|
||||
{
|
||||
return ResultFs.InvalidNcaMountPoint.Log();
|
||||
}
|
||||
|
||||
TitleId openTitleId = mountNameInfo.IsHostFs ? new TitleId(ulong.MaxValue) : titleId;
|
||||
|
||||
rc = TryOpenNca(ref path2, out Nca nca, baseFileSystem, openTitleId);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = OpenNcaStorage(out IStorage ncaSectionStorage, nca, out NcaFormatType fsType, type,
|
||||
mountNameInfo.IsGameCard, canMountSystemDataPrivate);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
switch (fsType)
|
||||
{
|
||||
case NcaFormatType.Romfs:
|
||||
return FsCreators.RomFileSystemCreator.Create(out fileSystem, ncaSectionStorage);
|
||||
case NcaFormatType.Pfs0:
|
||||
return FsCreators.PartitionFileSystemCreator.Create(out fileSystem, ncaSectionStorage);
|
||||
default:
|
||||
return ResultFs.InvalidNcaFsType.Log();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores info obtained by parsing a common mount name.
|
||||
/// </summary>
|
||||
private struct MountNameInfo
|
||||
{
|
||||
public bool IsGameCard;
|
||||
public int GcHandle;
|
||||
public bool IsHostFs;
|
||||
public bool CanMountNca;
|
||||
}
|
||||
|
||||
private Result OpenFileSystemFromMountName(ref U8Span path, out IFileSystem fileSystem, out bool shouldContinue,
|
||||
out MountNameInfo info)
|
||||
{
|
||||
fileSystem = default;
|
||||
|
||||
info = new MountNameInfo();
|
||||
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;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSystemMountName,
|
||||
CommonMountNames.ContentStorageSystemMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.ContentStorageSystemMountName.Length);
|
||||
|
||||
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.System);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
info.CanMountNca = true;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageUserMountName,
|
||||
CommonMountNames.ContentStorageUserMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.ContentStorageUserMountName.Length);
|
||||
|
||||
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
info.CanMountNca = true;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSdCardMountName,
|
||||
CommonMountNames.ContentStorageSdCardMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.ContentStorageSdCardMountName.Length);
|
||||
|
||||
Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.SdCard);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
info.CanMountNca = true;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.BisCalibrationFilePartitionMountName,
|
||||
CommonMountNames.BisCalibrationFilePartitionMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.BisCalibrationFilePartitionMountName.Length);
|
||||
|
||||
Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.CalibrationFile);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.BisSafeModePartitionMountName,
|
||||
CommonMountNames.BisSafeModePartitionMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.BisSafeModePartitionMountName.Length);
|
||||
|
||||
Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.SafeMode);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.BisUserPartitionMountName,
|
||||
CommonMountNames.BisUserPartitionMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.BisUserPartitionMountName.Length);
|
||||
|
||||
Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.User);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.BisSystemPartitionMountName,
|
||||
CommonMountNames.BisSystemPartitionMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.BisSystemPartitionMountName.Length);
|
||||
|
||||
Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.System);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.SdCardFileSystemMountName,
|
||||
CommonMountNames.SdCardFileSystemMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.SdCardFileSystemMountName.Length);
|
||||
|
||||
Result rc = OpenSdCardFileSystem(out fileSystem);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName,
|
||||
CommonMountNames.HostRootFileSystemMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.HostRootFileSystemMountName.Length);
|
||||
|
||||
info.IsHostFs = true;
|
||||
info.CanMountNca = true;
|
||||
|
||||
Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false);
|
||||
if (rc.IsFailure()) return rc;
|
||||
}
|
||||
|
||||
else if (StringUtils.Compare(path, CommonMountNames.RegisteredUpdatePartitionMountName,
|
||||
CommonMountNames.RegisteredUpdatePartitionMountName.Length) == 0)
|
||||
{
|
||||
path = path.Slice(CommonMountNames.RegisteredUpdatePartitionMountName.Length);
|
||||
|
||||
info.CanMountNca = true;
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
if (StringUtils.GetLength(path, FsPath.MaxLength) == 0)
|
||||
{
|
||||
shouldContinue = false;
|
||||
}
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result IsContentPathDir(ref U8Span path, out bool isDirectory)
|
||||
{
|
||||
isDirectory = default;
|
||||
|
||||
ReadOnlySpan<byte> mountSeparator = new[] { (byte)':', (byte)'/' };
|
||||
|
||||
if (StringUtils.Compare(mountSeparator, path, mountSeparator.Length) != 0)
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
path = path.Slice(1);
|
||||
int pathLen = StringUtils.GetLength(path);
|
||||
|
||||
if (path[pathLen - 1] == '/')
|
||||
{
|
||||
isDirectory = true;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// Now make sure the path has a content file extension
|
||||
if (pathLen < 5)
|
||||
return ResultFs.PathNotFound.Log();
|
||||
|
||||
ReadOnlySpan<byte> fileExtension = path.Value.Slice(pathLen - 4);
|
||||
|
||||
ReadOnlySpan<byte> ncaExtension = new[] { (byte)'.', (byte)'n', (byte)'c', (byte)'a' };
|
||||
ReadOnlySpan<byte> nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' };
|
||||
|
||||
if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0 ||
|
||||
StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0)
|
||||
{
|
||||
isDirectory = false;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
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 sb = new U8StringBuilder(fullPath.Str);
|
||||
sb.Append(path)
|
||||
.Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' });
|
||||
|
||||
if (sb.Overflowed)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Result 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' };
|
||||
|
||||
// Search for the end of the nsp part of the path
|
||||
int nspPathLen = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
U8Span currentSpan;
|
||||
|
||||
while (true)
|
||||
{
|
||||
currentSpan = path.Slice(nspPathLen);
|
||||
if (StringUtils.CompareCaseInsensitive(nspExtension, currentSpan, 4) == 0)
|
||||
break;
|
||||
|
||||
if (currentSpan.Length == 0 || currentSpan[0] == 0)
|
||||
{
|
||||
return ResultFs.PathNotFound.Log();
|
||||
}
|
||||
|
||||
nspPathLen++;
|
||||
}
|
||||
|
||||
// The nsp filename must be the end of the entire path or the end of a path segment
|
||||
if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/')
|
||||
break;
|
||||
|
||||
nspPathLen += 4;
|
||||
}
|
||||
|
||||
nspPathLen += 4;
|
||||
|
||||
if (nspPathLen > FsPath.MaxLength + 1)
|
||||
return ResultFs.TooLongPath.Log();
|
||||
|
||||
Result rc = FsPath.FromSpan(out FsPath nspPath, path.Slice(0, nspPathLen));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FileStorageBasedFileSystem.CreateNew(out FileStorageBasedFileSystem nspFileStorage, baseFileSystem,
|
||||
new U8Span(nspPath.Str), OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.PartitionFileSystemCreator.Create(out outFileSystem, nspFileStorage);
|
||||
|
||||
if (rc.IsSuccess())
|
||||
{
|
||||
path = path.Slice(nspPathLen);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
private Result TryOpenNca(ref U8Span path, out Nca nca, IFileSystem baseFileSystem, TitleId programId)
|
||||
{
|
||||
nca = default;
|
||||
|
||||
Result rc = FileStorageBasedFileSystem.CreateNew(out FileStorageBasedFileSystem ncaFileStorage,
|
||||
baseFileSystem, path, OpenMode.Read);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (programId.Value == ulong.MaxValue)
|
||||
{
|
||||
ulong ncaProgramId = ncaTemp.Header.TitleId;
|
||||
|
||||
if (ncaProgramId != ulong.MaxValue && programId.Value != ncaProgramId)
|
||||
{
|
||||
return ResultFs.InvalidNcaProgramId.Log();
|
||||
}
|
||||
}
|
||||
|
||||
nca = ncaTemp;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result OpenNcaStorage(out IStorage ncaStorage, Nca nca, out NcaFormatType fsType,
|
||||
FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate)
|
||||
{
|
||||
ncaStorage = default;
|
||||
fsType = default;
|
||||
|
||||
NcaContentType contentType = nca.Header.ContentType;
|
||||
|
||||
switch (fsProxyType)
|
||||
{
|
||||
case FileSystemProxyType.Code:
|
||||
case FileSystemProxyType.Rom:
|
||||
case FileSystemProxyType.Logo:
|
||||
case FileSystemProxyType.RegisteredUpdate:
|
||||
if (contentType != NcaContentType.Program)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
break;
|
||||
|
||||
case FileSystemProxyType.Control:
|
||||
if (contentType != NcaContentType.Control)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
break;
|
||||
case FileSystemProxyType.Manual:
|
||||
if (contentType != NcaContentType.Manual)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
break;
|
||||
case FileSystemProxyType.Meta:
|
||||
if (contentType != NcaContentType.Meta)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
break;
|
||||
case FileSystemProxyType.Data:
|
||||
if (contentType != NcaContentType.Data && contentType != NcaContentType.PublicData)
|
||||
return ResultFs.PreconditionViolation.Log();
|
||||
|
||||
if (contentType == NcaContentType.Data && !canMountSystemDataPrivate)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
break;
|
||||
default:
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
|
||||
if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard)
|
||||
return ResultFs.PermissionDenied.Log();
|
||||
|
||||
Result rc = SetNcaExternalKey(nca);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = GetNcaSectionIndex(out int sectionIndex, fsProxyType);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = FsCreators.StorageOnNcaCreator.Create(out ncaStorage, out NcaFsHeader fsHeader, nca,
|
||||
sectionIndex, fsProxyType == FileSystemProxyType.Code);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
fsType = fsHeader.FormatType;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result SetNcaExternalKey(Nca nca)
|
||||
{
|
||||
var rightsId = new RightsId(nca.Header.RightsId);
|
||||
var zero = new RightsId(0, 0);
|
||||
|
||||
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;
|
||||
|
||||
// todo: Set key in nca reader
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result GetNcaSectionIndex(out int index, FileSystemProxyType fspType)
|
||||
{
|
||||
switch (fspType)
|
||||
{
|
||||
case FileSystemProxyType.Code:
|
||||
case FileSystemProxyType.Control:
|
||||
case FileSystemProxyType.Manual:
|
||||
case FileSystemProxyType.Meta:
|
||||
case FileSystemProxyType.Data:
|
||||
index = 0;
|
||||
return Result.Success;
|
||||
case FileSystemProxyType.Rom:
|
||||
case FileSystemProxyType.RegisteredUpdate:
|
||||
index = 1;
|
||||
return Result.Success;
|
||||
case FileSystemProxyType.Logo:
|
||||
index = 2;
|
||||
return Result.Success;
|
||||
default:
|
||||
index = default;
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
}
|
||||
}
|
||||
|
||||
public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId)
|
||||
{
|
||||
return FsCreators.BuiltInStorageFileSystemCreator.Create(out fileSystem, rootPath, partitionId);
|
||||
@ -155,7 +740,54 @@ 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())
|
||||
{
|
||||
ReadOnlySpan<byte> rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' };
|
||||
rc = hostFs.GetEntryType(out _, new U8Span(rootHostPath));
|
||||
|
||||
// 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)
|
||||
|
@ -17,7 +17,8 @@ namespace LibHac.FsService
|
||||
Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId);
|
||||
Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId);
|
||||
Result InvalidateBisCache();
|
||||
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath subPath);
|
||||
Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option);
|
||||
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path);
|
||||
Result OpenSdCardFileSystem(out IFileSystem fileSystem);
|
||||
Result FormatSdCardFileSystem();
|
||||
Result DeleteSaveDataFileSystem(ulong saveDataId);
|
||||
|
@ -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 ||
|
||||
|
@ -497,200 +497,6 @@ namespace LibHac.FsSystem
|
||||
|
||||
private static bool IsValidMountNameChar(byte c) => IsValidMountNameChar((char)c);
|
||||
|
||||
private static Result GetPathRoot(out ReadOnlySpan<byte> afterRootPath, Span<byte> pathRootBuffer, out int outRootLength, ReadOnlySpan<byte> path)
|
||||
{
|
||||
outRootLength = 0;
|
||||
afterRootPath = path;
|
||||
|
||||
if (path.Length == 0) return Result.Success;
|
||||
|
||||
int mountNameStart;
|
||||
if (IsDirectorySeparator(path[0]))
|
||||
{
|
||||
mountNameStart = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
mountNameStart = 0;
|
||||
}
|
||||
|
||||
int rootLength = 0;
|
||||
|
||||
for (int i = mountNameStart; i < mountNameStart + MountNameLengthMax; i++)
|
||||
{
|
||||
if (i >= path.Length || path[i] == 0) break;
|
||||
|
||||
// Set the length to 0 if there's no mount name
|
||||
if (IsDirectorySeparator(path[i]))
|
||||
{
|
||||
outRootLength = 0;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
if (path[i] == MountSeparator)
|
||||
{
|
||||
rootLength = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mountNameStart >= rootLength - 1 || path[rootLength - 1] != MountSeparator)
|
||||
{
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
}
|
||||
|
||||
if (mountNameStart < rootLength)
|
||||
{
|
||||
for (int i = mountNameStart; i < rootLength; i++)
|
||||
{
|
||||
if (path[i] == '.')
|
||||
{
|
||||
return ResultFs.InvalidCharacter.Log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pathRootBuffer.IsEmpty)
|
||||
{
|
||||
if (rootLength > pathRootBuffer.Length)
|
||||
{
|
||||
return ResultFs.TooLongPath.Log();
|
||||
}
|
||||
|
||||
path.Slice(0, rootLength).CopyTo(pathRootBuffer);
|
||||
}
|
||||
|
||||
afterRootPath = path.Slice(rootLength);
|
||||
outRootLength = rootLength;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public static Result Normalize(Span<byte> normalizedPath, out int normalizedLength, ReadOnlySpan<byte> path, bool hasMountName)
|
||||
{
|
||||
normalizedLength = 0;
|
||||
|
||||
int rootLength = 0;
|
||||
ReadOnlySpan<byte> mainPath = path;
|
||||
|
||||
if (hasMountName)
|
||||
{
|
||||
Result pathRootRc = GetPathRoot(out mainPath, normalizedPath, out rootLength, path);
|
||||
if (pathRootRc.IsFailure()) return pathRootRc;
|
||||
}
|
||||
|
||||
var sb = new PathBuilder(normalizedPath.Slice(rootLength));
|
||||
|
||||
var state = NormalizeState.Initial;
|
||||
|
||||
for (int i = 0; i < mainPath.Length; i++)
|
||||
{
|
||||
Result rc = Result.Success;
|
||||
byte c = mainPath[i];
|
||||
|
||||
// Read input strings as null-terminated
|
||||
if (c == 0) break;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NormalizeState.Initial when IsDirectorySeparator(c):
|
||||
state = NormalizeState.Delimiter;
|
||||
break;
|
||||
|
||||
case NormalizeState.Initial:
|
||||
return ResultFs.InvalidPathFormat.Log();
|
||||
|
||||
case NormalizeState.Normal when IsDirectorySeparator(c):
|
||||
state = NormalizeState.Delimiter;
|
||||
break;
|
||||
|
||||
case NormalizeState.Normal:
|
||||
rc = sb.Append(c);
|
||||
break;
|
||||
|
||||
case NormalizeState.Delimiter when IsDirectorySeparator(c):
|
||||
break;
|
||||
|
||||
case NormalizeState.Delimiter when c == '.':
|
||||
state = NormalizeState.Dot;
|
||||
rc = sb.AppendWithPrecedingSeparator(c);
|
||||
break;
|
||||
|
||||
case NormalizeState.Delimiter:
|
||||
state = NormalizeState.Normal;
|
||||
rc = sb.AppendWithPrecedingSeparator(c);
|
||||
break;
|
||||
|
||||
case NormalizeState.Dot when IsDirectorySeparator(c):
|
||||
state = NormalizeState.Delimiter;
|
||||
rc = sb.GoUpLevels(1);
|
||||
break;
|
||||
|
||||
case NormalizeState.Dot when c == '.':
|
||||
state = NormalizeState.DoubleDot;
|
||||
rc = sb.Append(c);
|
||||
break;
|
||||
|
||||
case NormalizeState.Dot:
|
||||
state = NormalizeState.Normal;
|
||||
rc = sb.Append(c);
|
||||
break;
|
||||
|
||||
case NormalizeState.DoubleDot when IsDirectorySeparator(c):
|
||||
state = NormalizeState.Delimiter;
|
||||
rc = sb.GoUpLevels(2);
|
||||
break;
|
||||
|
||||
case NormalizeState.DoubleDot:
|
||||
state = NormalizeState.Normal;
|
||||
break;
|
||||
}
|
||||
|
||||
if (rc.IsFailure())
|
||||
{
|
||||
if (ResultFs.TooLongPath.Includes(rc))
|
||||
{
|
||||
// Make sure pending delimiters are added to the string if possible
|
||||
if (state == NormalizeState.Delimiter)
|
||||
{
|
||||
sb.Append((byte)DirectorySeparator);
|
||||
}
|
||||
}
|
||||
|
||||
normalizedLength = sb.Length;
|
||||
sb.Terminate();
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
Result finalRc = Result.Success;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case NormalizeState.Dot:
|
||||
state = NormalizeState.Delimiter;
|
||||
finalRc = sb.GoUpLevels(1);
|
||||
break;
|
||||
|
||||
case NormalizeState.DoubleDot:
|
||||
state = NormalizeState.Delimiter;
|
||||
finalRc = sb.GoUpLevels(2);
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the pending delimiter if the path is empty
|
||||
// or if the path has only a mount name with no trailing delimiter
|
||||
if (state == NormalizeState.Delimiter && sb.Length == 0 ||
|
||||
rootLength > 0 && sb.Length == 0)
|
||||
{
|
||||
finalRc = sb.Append((byte)'/');
|
||||
}
|
||||
|
||||
normalizedLength = sb.Length;
|
||||
sb.Terminate();
|
||||
|
||||
return finalRc;
|
||||
}
|
||||
|
||||
private enum NormalizeState
|
||||
{
|
||||
Initial,
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using Xunit;
|
||||
|
||||
@ -172,110 +171,6 @@ namespace LibHac.Tests
|
||||
return paths.Select(x => new object[] { x }).ToArray();
|
||||
}
|
||||
|
||||
public static object[][] NormalizedPathTestItemsU8NoMountName =
|
||||
{
|
||||
new object[] {"/", "/", Result.Success},
|
||||
new object[] {"/.", "/", Result.Success},
|
||||
new object[] {"/..", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"/abc", "/abc", Result.Success},
|
||||
new object[] {"/a/..", "/", Result.Success},
|
||||
new object[] {"/a/b/c", "/a/b/c", Result.Success},
|
||||
new object[] {"/a/b/../c", "/a/c", Result.Success},
|
||||
new object[] {"/a/b/c/..", "/a/b", Result.Success},
|
||||
new object[] {"/a/b/c/.", "/a/b/c", Result.Success},
|
||||
new object[] {"/a/../../..", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"/a/../../../a/b/c", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"//a/b//.//c", "/a/b/c", Result.Success},
|
||||
new object[] {"/../a/b/c/.", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"/./aaa/bbb/ccc/.", "/aaa/bbb/ccc", Result.Success},
|
||||
|
||||
new object[] {"/a/b/c/", "/a/b/c", Result.Success},
|
||||
new object[] {"/aa/./bb/../cc/", "/aa/cc", Result.Success},
|
||||
new object[] {"/./b/../c/", "/c", Result.Success},
|
||||
new object[] {"/a/../../../", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"//a/b//.//c/", "/a/b/c", Result.Success},
|
||||
new object[] {"/tmp/../", "/", Result.Success},
|
||||
new object[] {"abc", "", ResultFs.InvalidPathFormat.Value }
|
||||
};
|
||||
|
||||
public static object[][] NormalizedPathTestItemsU8MountName =
|
||||
{
|
||||
new object[] {"mount:/a/b/../c", "mount:/a/c", Result.Success},
|
||||
new object[] {"a:/a/b/c", "a:/a/b/c", Result.Success},
|
||||
new object[] {"mount:/a/b/../c", "mount:/a/c", Result.Success},
|
||||
new object[] {"mount:", "mount:/", Result.Success},
|
||||
new object[] {"abc:/a/../../../a/b/c", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"abc:/./b/../c/", "abc:/c", Result.Success},
|
||||
new object[] {"abc:/.", "abc:/", Result.Success},
|
||||
new object[] {"abc:/..", "", ResultFs.DirectoryUnobtainable.Value},
|
||||
new object[] {"abc:/", "abc:/", Result.Success},
|
||||
new object[] {"abc://a/b//.//c", "abc:/a/b/c", Result.Success},
|
||||
new object[] {"abc:/././/././a/b//.//c", "abc:/a/b/c", Result.Success},
|
||||
new object[] {"mount:/d./aa", "mount:/d./aa", Result.Success},
|
||||
new object[] {"mount:/d/..", "mount:/", Result.Success}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NormalizedPathTestItemsU8NoMountName))]
|
||||
public static void NormalizePathU8NoMountName(string path, string expected, Result expectedResult)
|
||||
{
|
||||
var u8Path = path.ToU8String();
|
||||
Span<byte> buffer = stackalloc byte[0x301];
|
||||
|
||||
Result rc = PathTools.Normalize(buffer, out _, u8Path, false);
|
||||
|
||||
string actual = StringUtils.Utf8ZToString(buffer);
|
||||
|
||||
Assert.Equal(expectedResult, rc);
|
||||
if (expectedResult == Result.Success)
|
||||
{
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NormalizedPathTestItemsU8MountName))]
|
||||
public static void NormalizePathU8MountName(string path, string expected, Result expectedResult)
|
||||
{
|
||||
var u8Path = path.ToU8String();
|
||||
Span<byte> buffer = stackalloc byte[0x301];
|
||||
|
||||
Result rc = PathTools.Normalize(buffer, out _, u8Path, true);
|
||||
|
||||
string actual = StringUtils.Utf8ZToString(buffer);
|
||||
|
||||
Assert.Equal(expectedResult, rc);
|
||||
if (expectedResult == Result.Success)
|
||||
{
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
public static object[][] NormalizedPathTestItemsU8TooShort =
|
||||
{
|
||||
new object[] {"/a/b/c", "", 0},
|
||||
new object[] {"/a/b/c", "/a/", 4},
|
||||
new object[] {"/a/b/c", "/a/b", 5},
|
||||
new object[] {"/a/b/c", "/a/b/", 6}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(NormalizedPathTestItemsU8TooShort))]
|
||||
public static void NormalizePathU8TooShortDest(string path, string expected, int destSize)
|
||||
{
|
||||
var u8Path = path.ToU8String();
|
||||
|
||||
Span<byte> buffer = stackalloc byte[destSize];
|
||||
|
||||
Result rc = PathTools.Normalize(buffer, out int normalizedLength, u8Path, false);
|
||||
|
||||
string actual = StringUtils.Utf8ZToString(buffer);
|
||||
|
||||
Assert.Equal(ResultFs.TooLongPath.Value, rc);
|
||||
Assert.Equal(Math.Max(0, destSize - 1), normalizedLength);
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
public static object[][] GetFileNameTestItems =
|
||||
{
|
||||
new object[] {"/a/bb/ccc", "ccc"},
|
||||
|
Loading…
x
Reference in New Issue
Block a user