Fully implement LR client and LocationResolverSet

This commit is contained in:
Alex Barney 2020-09-07 00:32:28 -07:00
parent 4113bc2d36
commit 5dc7c57851
66 changed files with 2731 additions and 914 deletions

View File

@ -1,6 +1,7 @@
Name,Index
Svc,1
Fs,2
Lr,8
Loader,9
Sf,10
Kvdb,20

1 Name Index
2 Svc 1
3 Fs 2
4 Lr 8
5 Loader 9
6 Sf 10
7 Kvdb 20

View File

@ -1,6 +1,7 @@
Name,Namespace,Path
Svc,LibHac.Svc,LibHac/Svc/ResultSvc.cs
Fs,LibHac.Fs,LibHac/Fs/ResultFs.cs
Lr,LibHac.Lr,LibHac/Lr/ResultLr.cs
Loader,LibHac.Loader,LibHac/Loader/ResultLoader.cs
Sf,LibHac.Sf,LibHac/Sf/ResultSf.cs
Kvdb,LibHac.Kvdb,LibHac/Kvdb/ResultKvdb.cs

1 Name Namespace Path
2 Svc LibHac.Svc LibHac/Svc/ResultSvc.cs
3 Fs LibHac.Fs LibHac/Fs/ResultFs.cs
4 Lr LibHac.Lr LibHac/Lr/ResultLr.cs
5 Loader LibHac.Loader LibHac/Loader/ResultLoader.cs
6 Sf LibHac.Sf LibHac/Sf/ResultSf.cs
7 Kvdb LibHac.Kvdb LibHac/Kvdb/ResultKvdb.cs

View File

@ -89,12 +89,28 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,3005,,OutOfRange,
2,3200,3499,AllocationMemoryFailed,
2,3256,,AllocationFailureInNcaFileSystemServiceImplA,In ParseNsp allocating FileStorageBasedFileSystem
2,3257,,AllocationFailureInNcaFileSystemServiceImplB,In ParseNca allocating FileStorageBasedFileSystem
2,3258,,AllocationFailureInProgramRegistryManagerA,In RegisterProgram allocating ProgramInfoNode
2,3264,,AllocationFailureFatFileSystemA,In Initialize allocating ProgramInfoNode
2,3280,,AllocationFailureInPartitionFileSystemCreatorA,In Create allocating PartitionFileSystemCore
2,3312,,AllocationFailureInAesXtsFileA,In Initialize allocating FileStorage
2,3313,,AllocationFailureInAesXtsFileB,In Initialize allocating AesXtsStorage
2,3314,,AllocationFailureInAesXtsFileC,In Initialize allocating AlignmentMatchingStoragePooledBuffer
2,3315,,AllocationFailureInAesXtsFileD,In Initialize allocating StorageFile
2,3383,,AllocationFailureInAesXtsFileE,In Initialize allocating SubStorage
2,3347,,AllocationFailureInPartitionFileSystemA,In Initialize allocating PartitionFileSystemMetaCore
2,3348,,AllocationFailureInPartitionFileSystemB,In DoOpenFile allocating PartitionFile
2,3349,,AllocationFailureInPartitionFileSystemC,In DoOpenDirectory allocating PartitionDirectory
2,3350,,AllocationFailureInPartitionFileSystemMetaA,In Initialize allocating metadata buffer
2,3351,,AllocationFailureInPartitionFileSystemMetaB,In Sha256 Initialize allocating metadata buffer
2,3355,,AllocationFailureInSubdirectoryFileSystemA,In Initialize allocating RootPathBuffer
2,3383,,AllocationFailureInAesXtsFileE,In Initialize
2,3394,,AllocationFailureInEncryptedFileSystemCreatorA,In Create allocating AesXtsFileSystem
2,3420,,AllocationFailureInNew,
2,3421,,AllocationFailureInCreateShared,
2,3422,,AllocationFailureInMakeUnique,
2,3423,,AllocationFailureInAllocateShared,
2,3424,,AllocationFailurePooledBufferNotEnoughSize,
2,3500,3999,MmcAccessFailed,
@ -148,8 +164,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,4464,,AllocationTableIteratedRangeEntry,
2,4501,4599,NcaCorrupted,
2,4512,,InvalidNcaFsType,
2,4527,,InvalidNcaProgramId,
2,4512,,InvalidNcaFileSystemType,
2,4527,,InvalidNcaId,
2,4601,4639,IntegrityVerificationStorageCorrupted,
2,4602,,InvalidIvfcMagic,
@ -303,6 +319,17 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
2,6905,,NotMounted,
2,6906,,SaveDataIsExtending,
8,2,,ProgramNotFound,
8,3,,DataNotFound,
8,4,,UnknownStorageId,
8,5,,LocationResolverNotFound,
8,6,,HtmlDocumentNotFound,
8,7,,AddOnContentNotFound,
8,8,,ControlNotFound,
8,9,,LegalInformationNotFound,
8,10,,DebugProgramNotFound,
8,90,,TooManyRegisteredPaths,
9,1,,TooLongArgument,
9,2,,TooManyArguments,
9,3,,TooLargeMeta,

Can't render this file because it has a wrong number of fields in line 219.

View File

@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.Fat
{
[StructLayout(LayoutKind.Explicit, Size = 0x20)]
public struct FatError
{
private const int FunctionNameLength = 0x10;
[FieldOffset(0x00)] public int Error;
[FieldOffset(0x04)] public int ExtraError;
[FieldOffset(0x08)] public int DriveId;
[FieldOffset(0x0C)] private byte _functionName;
public U8SpanMutable ErrorName =>
new U8SpanMutable(SpanHelpers.CreateSpan(ref _functionName, FunctionNameLength));
}
}

View File

@ -11,7 +11,7 @@ namespace LibHac.Fs
{
private readonly Key128 Key;
public ReadOnlySpan<byte> Value => SpanHelpers.AsByteSpan(ref this);
public readonly ReadOnlySpan<byte> Value => SpanHelpers.AsReadOnlyByteSpan(in this);
public EncryptionSeed(ReadOnlySpan<byte> bytes)
{

View File

@ -6,35 +6,17 @@ namespace LibHac.Fs
public class FileStorageBasedFileSystem : FileStorage2
{
// ReSharper disable once UnusedAutoPropertyAccessor.Local
// FS keeps a shared pointer to the base filesystem
private IFileSystem BaseFileSystem { get; set; }
private ReferenceCountedDisposable<IFileSystem> BaseFileSystem { get; set; }
private IFile BaseFile { get; set; }
private FileStorageBasedFileSystem()
public FileStorageBasedFileSystem()
{
FileSize = SizeNotInitialized;
}
public static Result CreateNew(out FileStorageBasedFileSystem created, IFileSystem baseFileSystem, U8Span path,
OpenMode mode)
public Result Initialize(ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, OpenMode mode)
{
var obj = new FileStorageBasedFileSystem();
Result rc = obj.Initialize(baseFileSystem, path, mode);
if (rc.IsSuccess())
{
created = obj;
return Result.Success;
}
obj.Dispose();
created = default;
return rc;
}
private Result Initialize(IFileSystem baseFileSystem, U8Span path, OpenMode mode)
{
Result rc = baseFileSystem.OpenFile(out IFile file, path, mode);
Result rc = baseFileSystem.Target.OpenFile(out IFile file, path, mode);
if (rc.IsFailure()) return rc;
SetFile(file);
@ -49,6 +31,7 @@ namespace LibHac.Fs
if (disposing)
{
BaseFile?.Dispose();
BaseFileSystem?.Dispose();
}
base.Dispose(disposing);

View File

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
using LibHac.Fat;
namespace LibHac.Fs
{
[StructLayout(LayoutKind.Explicit, Size = 0x80)]
public struct FileSystemProxyErrorInfo
{
[FieldOffset(0x00)] public int RomFsRemountForDataCorruptionCount;
[FieldOffset(0x04)] public int RomFsUnrecoverableDataCorruptionByRemountCount;
[FieldOffset(0x08)] public FatError FatError;
[FieldOffset(0x28)] public int RomFsRecoveredByInvalidateCacheCount;
[FieldOffset(0x2C)] public int SaveDataIndexCount;
}
}

View File

@ -96,8 +96,16 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-3200; Range: 3200-3499; Inner value: 0x190002</summary>
public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); }
/// <summary>In ParseNsp allocating FileStorageBasedFileSystem<br/>Error code: 2002-3256; Inner value: 0x197002</summary>
public static Result.Base AllocationFailureInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 3256);
/// <summary>In ParseNca allocating FileStorageBasedFileSystem<br/>Error code: 2002-3257; Inner value: 0x197202</summary>
public static Result.Base AllocationFailureInNcaFileSystemServiceImplB => new Result.Base(ModuleFs, 3257);
/// <summary>In RegisterProgram allocating ProgramInfoNode<br/>Error code: 2002-3258; Inner value: 0x197402</summary>
public static Result.Base AllocationFailureInProgramRegistryManagerA => new Result.Base(ModuleFs, 3258);
/// <summary>In Initialize allocating ProgramInfoNode<br/>Error code: 2002-3264; Inner value: 0x198002</summary>
public static Result.Base AllocationFailureFatFileSystemA => new Result.Base(ModuleFs, 3264);
/// <summary>In Create allocating PartitionFileSystemCore<br/>Error code: 2002-3280; Inner value: 0x19a002</summary>
public static Result.Base AllocationFailureInPartitionFileSystemCreatorA => new Result.Base(ModuleFs, 3280);
/// <summary>In Initialize allocating FileStorage<br/>Error code: 2002-3312; Inner value: 0x19e002</summary>
public static Result.Base AllocationFailureInAesXtsFileA => new Result.Base(ModuleFs, 3312);
/// <summary>In Initialize allocating AesXtsStorage<br/>Error code: 2002-3313; Inner value: 0x19e202</summary>
@ -106,8 +114,32 @@ namespace LibHac.Fs
public static Result.Base AllocationFailureInAesXtsFileC => new Result.Base(ModuleFs, 3314);
/// <summary>In Initialize allocating StorageFile<br/>Error code: 2002-3315; Inner value: 0x19e602</summary>
public static Result.Base AllocationFailureInAesXtsFileD => new Result.Base(ModuleFs, 3315);
/// <summary>In Initialize allocating SubStorage<br/>Error code: 2002-3383; Inner value: 0x1a6e02</summary>
/// <summary>In Initialize allocating PartitionFileSystemMetaCore<br/>Error code: 2002-3347; Inner value: 0x1a2602</summary>
public static Result.Base AllocationFailureInPartitionFileSystemA => new Result.Base(ModuleFs, 3347);
/// <summary>In DoOpenFile allocating PartitionFile<br/>Error code: 2002-3348; Inner value: 0x1a2802</summary>
public static Result.Base AllocationFailureInPartitionFileSystemB => new Result.Base(ModuleFs, 3348);
/// <summary>In DoOpenDirectory allocating PartitionDirectory<br/>Error code: 2002-3349; Inner value: 0x1a2a02</summary>
public static Result.Base AllocationFailureInPartitionFileSystemC => new Result.Base(ModuleFs, 3349);
/// <summary>In Initialize allocating metadata buffer<br/>Error code: 2002-3350; Inner value: 0x1a2c02</summary>
public static Result.Base AllocationFailureInPartitionFileSystemMetaA => new Result.Base(ModuleFs, 3350);
/// <summary>In Sha256 Initialize allocating metadata buffer<br/>Error code: 2002-3351; Inner value: 0x1a2e02</summary>
public static Result.Base AllocationFailureInPartitionFileSystemMetaB => new Result.Base(ModuleFs, 3351);
/// <summary>In Initialize allocating RootPathBuffer<br/>Error code: 2002-3355; Inner value: 0x1a3602</summary>
public static Result.Base AllocationFailureInSubdirectoryFileSystemA => new Result.Base(ModuleFs, 3355);
/// <summary>In Initialize<br/>Error code: 2002-3383; Inner value: 0x1a6e02</summary>
public static Result.Base AllocationFailureInAesXtsFileE => new Result.Base(ModuleFs, 3383);
/// <summary>In Create allocating AesXtsFileSystem<br/>Error code: 2002-3394; Inner value: 0x1a8402</summary>
public static Result.Base AllocationFailureInEncryptedFileSystemCreatorA => new Result.Base(ModuleFs, 3394);
/// <summary>Error code: 2002-3420; Inner value: 0x1ab802</summary>
public static Result.Base AllocationFailureInNew => new Result.Base(ModuleFs, 3420);
/// <summary>Error code: 2002-3421; Inner value: 0x1aba02</summary>
public static Result.Base AllocationFailureInCreateShared => new Result.Base(ModuleFs, 3421);
/// <summary>Error code: 2002-3422; Inner value: 0x1abc02</summary>
public static Result.Base AllocationFailureInMakeUnique => new Result.Base(ModuleFs, 3422);
/// <summary>Error code: 2002-3423; Inner value: 0x1abe02</summary>
public static Result.Base AllocationFailureInAllocateShared => new Result.Base(ModuleFs, 3423);
/// <summary>Error code: 2002-3424; Inner value: 0x1ac002</summary>
public static Result.Base AllocationFailurePooledBufferNotEnoughSize => new Result.Base(ModuleFs, 3424);
/// <summary>Error code: 2002-3500; Range: 3500-3999; Inner value: 0x1b5802</summary>
public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3500, 3999); }
@ -205,9 +237,9 @@ 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);
public static Result.Base InvalidNcaFileSystemType => new Result.Base(ModuleFs, 4512);
/// <summary>Error code: 2002-4527; Inner value: 0x235e02</summary>
public static Result.Base InvalidNcaProgramId => new Result.Base(ModuleFs, 4527);
public static Result.Base InvalidNcaId => 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); }

View File

@ -2,7 +2,7 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.FsSrv.Sf;
namespace LibHac.Fs.Shim
{
@ -40,14 +40,15 @@ namespace LibHac.Fs.Shim
Result rc = MountHelpers.CheckMountName(mountName);
if (rc.IsFailure()) return rc;
FsPath.FromSpan(out FsPath fsPath, path);
FspPath.FromSpan(out FspPath sfPath, path);
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenFileSystemWithId(out IFileSystem fileSystem, ref fsPath, default, FileSystemProxyType.Package);
rc = fsProxy.OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystem> fileSystem, in sfPath,
default, FileSystemProxyType.Package);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
return fs.Register(mountName, fileSystem.Target);
}
}
}

View File

@ -91,12 +91,13 @@ namespace LibHac.Fs.Shim
// Nintendo doesn't use the provided rootPath
FspPath.CreateEmpty(out FspPath sfPath);
rc = fsProxy.OpenBisFileSystem(out IFileSystem fileSystem, in sfPath, partitionId);
rc = fsProxy.OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in sfPath,
partitionId);
if (rc.IsFailure()) return rc;
var nameGenerator = new BisCommonMountNameGenerator(partitionId);
return fs.Register(mountName, fileSystem, nameGenerator);
return fs.Register(mountName, fileSystem.Target, nameGenerator);
}
public static U8Span GetBisMountName(BisPartitionId partitionId)

View File

@ -49,10 +49,11 @@ namespace LibHac.Fs.Shim
IFileSystemProxyForLoader fsProxy = fs.GetFileSystemProxyForLoaderServiceObject();
rc = fsProxy.OpenCodeFileSystem(out IFileSystem codeFs, out verificationData, in fsPath, programId);
rc = fsProxy.OpenCodeFileSystem(out ReferenceCountedDisposable<IFileSystem> codeFs, out verificationData,
in fsPath, programId);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, codeFs);
return fs.Register(mountName, codeFs.Target);
}
}
}

View File

@ -2,7 +2,7 @@
using LibHac.Common;
using LibHac.Fs.Fsa;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
namespace LibHac.Fs.Shim
@ -27,10 +27,11 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenFileSystemWithPatch(out IFileSystem fileSystem, programId, fspType);
rc = fsProxy.OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem, programId,
fspType);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
return fs.Register(mountName, fileSystem.Target);
}
public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId, ContentType type)
@ -55,14 +56,15 @@ namespace LibHac.Fs.Shim
private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id, FileSystemProxyType type)
{
FsPath.FromSpan(out FsPath fsPath, path);
FspPath.FromSpan(out FspPath fsPath, path);
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.OpenFileSystemWithId(out IFileSystem fileSystem, ref fsPath, id, type);
Result rc = fsProxy.OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystem> fileSystem, in fsPath,
id, type);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
return fs.Register(mountName, fileSystem.Target);
}
private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type) => type switch

View File

@ -20,12 +20,12 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId);
rc = fsProxy.OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> contentFs, storageId);
if (rc.IsFailure()) return rc;
var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId);
return fs.Register(mountName, contentFs, mountNameGenerator);
return fs.Register(mountName, contentFs.Target, mountNameGenerator);
}
public static U8String GetContentStorageMountName(ContentStorageId storageId)

View File

@ -48,12 +48,12 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenGameCardFileSystem(out IFileSystem cardFs, handle, partitionId);
rc = fsProxy.OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> cardFs, handle, partitionId);
if (rc.IsFailure()) return rc;
var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId);
return fs.Register(mountName, cardFs, mountNameGenerator);
return fs.Register(mountName, cardFs.Target, mountNameGenerator);
}
private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator

View File

@ -1,6 +1,6 @@
using LibHac.Common;
using LibHac.FsSrv;
using LibHac.FsSystem;
using LibHac.FsSrv.Sf;
using LibHac.Ncm;
using LibHac.Spl;
using FsRightsId = LibHac.Fs.RightsId;
@ -23,10 +23,10 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = FsPath.FromSpan(out FsPath fsPath, path);
Result rc = FspPath.FromSpan(out FspPath sfPath, path);
if (rc.IsFailure()) return rc;
return fsProxy.GetRightsIdByPath(out rightsId, ref fsPath);
return fsProxy.GetRightsIdByPath(out rightsId, in sfPath);
}
public static Result GetRightsId(this FileSystemClient fs, out FsRightsId rightsId, out byte keyGeneration, U8Span path)
@ -36,24 +36,24 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = FsPath.FromSpan(out FsPath fsPath, path);
Result rc = FspPath.FromSpan(out FspPath sfPath, path);
if (rc.IsFailure()) return rc;
return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, ref fsPath);
return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in sfPath);
}
public static Result RegisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId, ref AccessKey key)
public static Result RegisterExternalKey(this FileSystemClient fs, in FsRightsId rightsId, in AccessKey key)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.RegisterExternalKey(ref rightsId, ref key);
return fsProxy.RegisterExternalKey(in rightsId, in key);
}
public static Result UnregisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
return fsProxy.UnregisterExternalKey(ref rightsId);
return fsProxy.UnregisterExternalKey(in rightsId);
}
public static Result UnregisterAllExternalKey(this FileSystemClient fs)

View File

@ -41,10 +41,10 @@ namespace LibHac.Fs.Shim
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
rc = fsProxy.OpenSdCardFileSystem(out IFileSystem fileSystem);
rc = fsProxy.OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem);
if (rc.IsFailure()) return rc;
return fs.Register(mountName, fileSystem);
return fs.Register(mountName, fileSystem.Target);
}
}
@ -61,11 +61,11 @@ namespace LibHac.Fs.Shim
return isInserted;
}
public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, ref EncryptionSeed seed)
public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, in EncryptionSeed seed)
{
IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject();
Result rc = fsProxy.SetSdCardEncryptionSeed(ref seed);
Result rc = fsProxy.SetSdCardEncryptionSeed(in seed);
if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort");
return Result.Success;

View File

@ -17,7 +17,8 @@ namespace LibHac.FsSrv
_processId = processId;
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, in FspPath rootPath, BisPartitionId partitionId)
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath rootPath,
BisPartitionId partitionId)
{
fileSystem = default;
@ -49,7 +50,8 @@ namespace LibHac.FsSrv
var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty);
if (normalizer.Result.IsFailure()) return normalizer.Result;
rc = _serviceImpl.OpenBisFileSystem(out IFileSystem bisFs, normalizer.Path, partitionId);
rc = _serviceImpl.OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> bisFs, normalizer.Path,
partitionId);
if (rc.IsFailure()) return rc;
fileSystem = bisFs;
@ -84,7 +86,7 @@ namespace LibHac.FsSrv
return _serviceImpl.DeleteAllPaddingFiles();
}
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
public Result OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, GameCardHandle handle,
GameCardPartition partitionId)
{
fileSystem = default;
@ -95,14 +97,14 @@ namespace LibHac.FsSrv
if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead)
return ResultFs.PermissionDenied.Log();
rc = _serviceImpl.OpenGameCardFileSystem(out IFileSystem gcFs, handle, partitionId);
rc = _serviceImpl.OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> gcFs, handle, partitionId);
if (rc.IsFailure()) return rc;
fileSystem = gcFs;
return Result.Success;
}
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
public Result OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
fileSystem = default;
@ -114,7 +116,7 @@ namespace LibHac.FsSrv
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
rc = _serviceImpl.OpenSdCardProxyFileSystem(out IFileSystem sdCardFs);
rc = _serviceImpl.OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> sdCardFs);
if (rc.IsFailure()) return rc;
fileSystem = sdCardFs;
@ -148,7 +150,8 @@ namespace LibHac.FsSrv
return Result.Success;
}
public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId directoryId)
public Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ImageDirectoryId directoryId)
{
fileSystem = default;
@ -166,13 +169,17 @@ namespace LibHac.FsSrv
int id;
switch (directoryId)
{
case ImageDirectoryId.Nand: id = 0; break;
case ImageDirectoryId.SdCard: id = 1; break;
case ImageDirectoryId.Nand:
id = 0;
break;
case ImageDirectoryId.SdCard:
id = 1;
break;
default:
return ResultFs.InvalidArgument.Log();
}
rc = _serviceImpl.OpenBaseFileSystem(out IFileSystem imageFs, id);
rc = _serviceImpl.OpenBaseFileSystem(out ReferenceCountedDisposable<IFileSystem> imageFs, id);
if (rc.IsFailure()) return rc;
fileSystem = imageFs;

View File

@ -35,14 +35,20 @@ namespace LibHac.FsSrv
public ProgramRegistryImpl ProgramRegistry;
}
public Result OpenBaseFileSystem(out IFileSystem fileSystem, int fileSystemId)
public Result OpenBaseFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, int fileSystemId)
{
throw new NotImplementedException();
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, U8Span rootPath, BisPartitionId partitionId)
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId)
{
return _config.BisFileSystemCreator.Create(out fileSystem, rootPath.ToString(), partitionId);
fileSystem = default;
Result rc = _config.BisFileSystemCreator.Create(out IFileSystem fs, rootPath.ToString(), partitionId);
if (rc.IsFailure()) return rc;
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;
}
public Result CreatePaddingFile(long size)
@ -55,7 +61,7 @@ namespace LibHac.FsSrv
throw new NotImplementedException();
}
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
public Result OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, GameCardHandle handle,
GameCardPartition partitionId)
{
Result rc;
@ -74,14 +80,21 @@ namespace LibHac.FsSrv
return rc;
}
public Result OpenSdCardProxyFileSystem(out IFileSystem fileSystem)
public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
return OpenSdCardProxyFileSystem(out fileSystem, false);
}
public Result OpenSdCardProxyFileSystem(out IFileSystem fileSystem, bool isCaseSensitive)
public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool isCaseSensitive)
{
return _config.SdCardFileSystemCreator.Create(out fileSystem, isCaseSensitive);
fileSystem = default;
// Todo: Shared
Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, isCaseSensitive);
if (rc.IsFailure()) return rc;
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;
}
public Result FormatSdCardProxyFileSystem()

View File

@ -1,6 +1,8 @@
using System.Diagnostics;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
namespace LibHac.FsSrv.Creators
{
@ -85,6 +87,43 @@ namespace LibHac.FsSrv.Creators
return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath);
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId)
{
fileSystem = default;
if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log();
if (rootPath.IsNull()) return ResultFs.NullptrArgument.Log();
if (Config.TryGetFileSystem(out IFileSystem fs, partitionId))
{
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;
}
if (Config.RootFileSystem == null)
{
return ResultFs.PreconditionViolation.Log();
}
var partitionPath = GetPartitionPath(partitionId).ToU8String();
// Todo: Store shared file systems
using var sharedRootFs = new ReferenceCountedDisposable<IFileSystem>(Config.RootFileSystem);
Result rc = Utility.WrapSubDirectory(out ReferenceCountedDisposable<IFileSystem> partitionFileSystem,
sharedRootFs, partitionPath, true);
if (rc.IsFailure()) return rc;
if (rootPath.IsEmpty())
{
fileSystem = partitionFileSystem.AddReference();
return Result.Success;
}
return Utility.CreateSubDirectoryFileSystem(out fileSystem, partitionFileSystem, rootPath);
}
public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId)
{
fileSystem = default;

View File

@ -14,6 +14,7 @@ namespace LibHac.FsSrv.Creators
StorageCreator = storageCreator;
GameCard = gameCard;
}
public Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType)
{
// Use the old xci code temporarily
@ -31,5 +32,24 @@ namespace LibHac.FsSrv.Creators
fileSystem = xci.OpenPartition((XciPartitionType)partitionType);
return Result.Success;
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, GameCardHandle handle, GameCardPartition partitionType)
{
// Use the old xci code temporarily
fileSystem = default;
Result rc = GameCard.GetXci(out Xci xci, handle);
if (rc.IsFailure()) return rc;
if (!xci.HasPartition((XciPartitionType)partitionType))
{
return ResultFs.PartitionNotFound.Log();
}
XciPartition fs = xci.OpenPartition((XciPartitionType)partitionType);
fileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;
}
}
}

View File

@ -29,7 +29,27 @@ namespace LibHac.FsSrv.Creators
KeySet.SetSdSeed(encryptionSeed.ToArray());
encryptedFileSystem = new AesXtsFileSystem(baseFileSystem,
KeySet.SdCardEncryptionKeys[(int) keyId].DataRo.ToArray(), 0x4000);
KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000);
return Result.Success;
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem, ReferenceCountedDisposable<IFileSystem> baseFileSystem,
EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed)
{
encryptedFileSystem = default;
if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage)
{
return ResultFs.InvalidArgument.Log();
}
// todo: "proper" key generation instead of a lazy hack
KeySet.SetSdSeed(encryptionSeed.Value);
// Todo: pass ReferenceCountedDisposable to AesXtsFileSystem
var fs = new AesXtsFileSystem(baseFileSystem.AddReference().Target, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000);
encryptedFileSystem = new ReferenceCountedDisposable<IFileSystem>(fs);
return Result.Success;
}

View File

@ -1,11 +1,14 @@
using LibHac.Fs;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.Creators
{
public interface IBuiltInStorageFileSystemCreator
{
// Todo: Remove raw IFileSystem overload
Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span rootPath, BisPartitionId partitionId);
Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId);
Result SetBisRootForHost(BisPartitionId partitionId, string rootPath);
}

View File

@ -1,12 +1,18 @@
using System;
using LibHac.Fs;
using LibHac.Fs.Fsa;
namespace LibHac.FsSrv.Creators
{
public interface IEncryptedFileSystemCreator
{
// Todo: remove the function using raw IFileSystems
Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId,
ReadOnlySpan<byte> encryptionSeed);
Result Create(out ReferenceCountedDisposable<IFileSystem> encryptedFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, EncryptedFsKeyId keyId,
in EncryptionSeed encryptionSeed);
}
public enum EncryptedFsKeyId

View File

@ -6,5 +6,6 @@ namespace LibHac.FsSrv.Creators
public interface IGameCardFileSystemCreator
{
Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, GameCardHandle handle, GameCardPartition partitionType);
}
}

View File

@ -5,6 +5,8 @@ namespace LibHac.FsSrv.Creators
{
public interface IPartitionFileSystemCreator
{
// Todo: Remove non-shared overload
Result Create(out IFileSystem fileSystem, IStorage pFsStorage);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, ReferenceCountedDisposable<IStorage> pFsStorage);
}
}

View File

@ -5,6 +5,6 @@ namespace LibHac.FsSrv.Creators
{
public interface IRomFileSystemCreator
{
Result Create(out IFileSystem fileSystem, IStorage romFsStorage);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, ReferenceCountedDisposable<IStorage> romFsStorage);
}
}

View File

@ -6,8 +6,8 @@ namespace LibHac.FsSrv.Creators
{
public interface IStorageOnNcaCreator
{
Result Create(out IStorage storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs);
Result CreateWithPatch(out IStorage storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs);
Result Create(out ReferenceCountedDisposable<IStorage> storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs);
Result CreateWithPatch(out ReferenceCountedDisposable<IStorage> storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs);
Result OpenNca(out Nca nca, IStorage ncaStorage);
Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca);
}

View File

@ -5,7 +5,11 @@ namespace LibHac.FsSrv.Creators
{
public interface ISubDirectoryFileSystemCreator
{
// Todo: Remove the raw IFileSystem overloads
Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path);
Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path, bool preserveUnc);
Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem, ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path);
Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem, ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc);
}
}

View File

@ -5,7 +5,9 @@ namespace LibHac.FsSrv.Creators
{
public interface ITargetManagerFileSystemCreator
{
// Todo: Remove raw IFilesystem function
Result Create(out IFileSystem fileSystem, bool openCaseSensitive);
Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive);
Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path);
}
}

View File

@ -21,5 +21,20 @@ namespace LibHac.FsSrv.Creators
fileSystem = partitionFs;
return Result.Success;
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, ReferenceCountedDisposable<IStorage> pFsStorage)
{
var partitionFs = new PartitionFileSystemCore<StandardEntry>();
Result rc = partitionFs.Initialize(pFsStorage);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
fileSystem = new ReferenceCountedDisposable<IFileSystem>(partitionFs);
return Result.Success;
}
}
}

View File

@ -7,9 +7,10 @@ namespace LibHac.FsSrv.Creators
public class RomFileSystemCreator : IRomFileSystemCreator
{
// todo: Implement properly
public Result Create(out IFileSystem fileSystem, IStorage romFsStorage)
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, ReferenceCountedDisposable<IStorage> romFsStorage)
{
fileSystem = new RomFsFileSystem(romFsStorage);
// Todo: Properly use shared references
fileSystem = new ReferenceCountedDisposable<IFileSystem>(new RomFsFileSystem(romFsStorage.AddReference().Target));
return Result.Success;
}
}

View File

@ -20,7 +20,8 @@ namespace LibHac.FsSrv.Creators
}
// todo: Implement NcaReader and other Nca classes
public Result Create(out IStorage storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs)
public Result Create(out ReferenceCountedDisposable<IStorage> storage, out NcaFsHeader fsHeader, Nca nca,
int fsIndex, bool isCodeFs)
{
storage = default;
fsHeader = default;
@ -40,13 +41,14 @@ namespace LibHac.FsSrv.Creators
}
}
storage = storageTemp;
storage = new ReferenceCountedDisposable<IStorage>(storageTemp);
fsHeader = nca.GetFsHeader(fsIndex);
return Result.Success;
}
public Result CreateWithPatch(out IStorage storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs)
public Result CreateWithPatch(out ReferenceCountedDisposable<IStorage> storage, out NcaFsHeader fsHeader,
Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs)
{
throw new NotImplementedException();
}

View File

@ -22,5 +22,31 @@ namespace LibHac.FsSrv.Creators
subDirFileSystem = fs;
return rc;
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path)
{
return Create(out subDirFileSystem, baseFileSystem, path, false);
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc)
{
subDirFileSystem = default;
// Verify the sub-path exists
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc;
// Initialize the SubdirectoryFileSystem
var subDir = new SubdirectoryFileSystem(baseFileSystem, preserveUnc);
using var subDirShared = new ReferenceCountedDisposable<SubdirectoryFileSystem>(subDir);
rc = subDirShared.Target.Initialize(path);
if (rc.IsFailure()) return rc;
subDirFileSystem = subDirShared.AddReference<IFileSystem>();
return Result.Success;
}
}
}

View File

@ -10,6 +10,11 @@ namespace LibHac.FsSrv.Creators
throw new NotImplementedException();
}
public Result Create(out ReferenceCountedDisposable<IFileSystem> fileSystem, bool openCaseSensitive)
{
throw new NotImplementedException();
}
public Result GetCaseSensitivePath(out bool isSuccess, Span<byte> path)
{
throw new NotImplementedException();

View File

@ -5,7 +5,8 @@ namespace LibHac.FsSrv
public class FileSystemProxyConfiguration
{
public FileSystemCreators FsCreatorInterfaces { get; set; }
public BaseFileSystemServiceImpl BaseFileSystemServiceImpl { get; set; }
public ProgramRegistryServiceImpl ProgramRegistryServiceImpl { get; set; }
public BaseFileSystemServiceImpl BaseFileSystemService { get; set; }
public NcaFileSystemServiceImpl NcaFileSystemService { get; set; }
public ProgramRegistryServiceImpl ProgramRegistryService { get; set; }
}
}

View File

@ -1,16 +1,10 @@
using System;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSrv.Creators;
using LibHac.FsSystem.NcaUtils;
using LibHac.Spl;
using LibHac.Util;
using RightsId = LibHac.Fs.RightsId;
namespace LibHac.FsSrv
{
@ -26,7 +20,6 @@ namespace LibHac.FsSrv
private byte[] SdEncryptionSeed { get; } = new byte[0x10];
private const string NintendoDirectoryName = "Nintendo";
private const string ContentDirectoryName = "Contents";
private GlobalAccessLogMode LogMode { get; set; }
public bool IsSdCardAccessible { get; set; }
@ -36,590 +29,11 @@ namespace LibHac.FsSrv
public FileSystemProxyCoreImpl(FileSystemProxyConfiguration config, ExternalKeySet externalKeys, IDeviceOperator deviceOperator)
{
Config = config;
ProgramRegistry = new ProgramRegistryImpl(Config.ProgramRegistryServiceImpl);
ProgramRegistry = new ProgramRegistryImpl(Config.ProgramRegistryService);
ExternalKeys = externalKeys ?? new ExternalKeySet();
DeviceOperator = deviceOperator;
}
public Result OpenFileSystem(out IFileSystem fileSystem, U8Span path, FileSystemProxyType type,
bool canMountSystemDataPrivate, ulong programId)
{
fileSystem = default;
// Get a reference to the path that will be advanced as each part of the path is parsed
U8Span currentPath = path.Slice(0, StringUtils.GetLength(path));
// Open the root filesystem based on the path's mount name
Result rc = OpenFileSystemFromMountName(ref currentPath, 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 currentPath, 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, currentPath);
if (rc.IsFailure()) return rc;
fileSystem = new ReadOnlyFileSystem(manualFileSystem);
return Result.Success;
}
return TryOpenContentDirectory(currentPath, out fileSystem, baseFileSystem, type, true);
}
rc = TryOpenNsp(ref currentPath, out IFileSystem nspFileSystem, baseFileSystem);
if (rc.IsSuccess())
{
// Must be the end of the path to open Application Package FS type
if (currentPath.Length == 0 || currentPath[0] == 0)
{
if (type == FileSystemProxyType.Package)
{
fileSystem = nspFileSystem;
return Result.Success;
}
return ResultFs.InvalidArgument.Log();
}
baseFileSystem = nspFileSystem;
}
if (!mountNameInfo.CanMountNca)
{
return ResultFs.InvalidNcaMountPoint.Log();
}
ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : programId;
rc = TryOpenNca(ref currentPath, out Nca nca, baseFileSystem, openProgramId);
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:
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, ulong ncaId)
{
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 (ncaId == ulong.MaxValue)
{
ulong ncaProgramId = ncaTemp.Header.TitleId;
if (ncaProgramId != ulong.MaxValue && ncaId != 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);
@ -651,54 +65,6 @@ namespace LibHac.FsSrv
return Result.Success;
}
public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId)
{
fileSystem = default;
U8String contentDirPath = default;
IFileSystem baseFileSystem = default;
bool isEncrypted = false;
Result rc;
switch (storageId)
{
case ContentStorageId.System:
rc = OpenBisFileSystem(out baseFileSystem, string.Empty, BisPartitionId.System);
contentDirPath = $"/{ContentDirectoryName}".ToU8String();
break;
case ContentStorageId.User:
rc = OpenBisFileSystem(out baseFileSystem, string.Empty, BisPartitionId.User);
contentDirPath = $"/{ContentDirectoryName}".ToU8String();
break;
case ContentStorageId.SdCard:
rc = OpenSdCardFileSystem(out baseFileSystem);
contentDirPath = $"/{NintendoDirectoryName}/{ContentDirectoryName}".ToU8String();
isEncrypted = true;
break;
default:
rc = ResultFs.InvalidArgument.Log();
break;
}
if (rc.IsFailure()) return rc;
rc = baseFileSystem.EnsureDirectoryExists(contentDirPath.ToString());
if (rc.IsFailure()) return rc;
rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFileSystem,
baseFileSystem, contentDirPath);
if (rc.IsFailure()) return rc;
if (!isEncrypted)
{
fileSystem = subDirFileSystem;
return Result.Success;
}
return FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, subDirFileSystem,
EncryptedFsKeyId.Content, SdEncryptionSeed);
}
public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId)
{
fileSystem = default;
@ -743,25 +109,6 @@ namespace LibHac.FsSrv
}
}
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
GameCardPartition 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;
@ -796,26 +143,7 @@ namespace LibHac.FsSrv
return Result.Success;
}
public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey)
{
return ExternalKeys.Add(rightsId, externalKey);
}
public Result UnregisterExternalKey(ref RightsId rightsId)
{
ExternalKeys.Remove(rightsId);
return Result.Success;
}
public Result UnregisterAllExternalKey()
{
ExternalKeys.Clear();
return Result.Success;
}
public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed)
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
{
seed.Value.CopyTo(SdEncryptionSeed);
// todo: FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed);

View File

@ -18,6 +18,7 @@ namespace LibHac.FsSrv
public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader
{
private FileSystemProxyCoreImpl FsProxyCore { get; }
private ReferenceCountedDisposable<NcaFileSystemService> NcaFileSystemService { get; set; }
internal HorizonClient Hos { get; }
public ulong CurrentProcess { get; private set; }
@ -40,98 +41,70 @@ namespace LibHac.FsSrv
private ProgramRegistryService GetProgramRegistryService()
{
return new ProgramRegistryService(FsProxyCore.Config.ProgramRegistryServiceImpl, CurrentProcess);
return new ProgramRegistryService(FsProxyCore.Config.ProgramRegistryService, CurrentProcess);
}
public Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, ulong id, FileSystemProxyType type)
public Result OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath path,
ulong id, FileSystemProxyType fsType)
{
fileSystem = default;
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
AccessControl ac = programInfo.AccessControl;
switch (type)
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
case FileSystemProxyType.Logo:
if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Control:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Manual:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Meta:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Data:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Package:
if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead)
return ResultFs.PermissionDenied.Log();
break;
default:
return ResultFs.InvalidArgument.Log();
fileSystem = default;
return rc;
}
if (type == FileSystemProxyType.Meta)
return ncaFsService.OpenFileSystemWithId(out fileSystem, in path, id, fsType);
}
public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ProgramId programId, FileSystemProxyType fsType)
{
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
id = ulong.MaxValue;
}
else if (id == ulong.MaxValue)
{
return ResultFs.InvalidArgument.Log();
fileSystem = default;
return rc;
}
bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead;
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
if (normalizer.Result.IsFailure()) return normalizer.Result;
return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, id);
// Missing speed emulation storage type wrapper, async wrapper, and FileSystemInterfaceAdapter
return ncaFsService.OpenFileSystemWithPatch(out fileSystem, programId, fsType);
}
private PathNormalizer.Option GetPathNormalizerOptions(U8Span path)
public Result OpenCodeFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out CodeVerificationData verificationData, in FspPath path, ProgramId programId)
{
int hostMountLength = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName,
PathTools.MountNameLengthMax);
Unsafe.SkipInit(out verificationData);
bool isHostPath = StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountLength) == 0;
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
PathNormalizer.Option hostOption = isHostPath ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None;
return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTailSeparator | hostOption;
}
public Result OpenFileSystemWithPatch(out IFileSystem fileSystem, ProgramId programId, FileSystemProxyType type)
{
throw new NotImplementedException();
}
public Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData, in FspPath path,
ProgramId programId)
{
throw new NotImplementedException();
return ncaFsService.OpenCodeFileSystem(out fileSystem, out verificationData, in path, programId);
}
public Result IsArchivedProgram(out bool isArchived, ulong processId)
{
throw new NotImplementedException();
Unsafe.SkipInit(out isArchived);
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.IsArchivedProgram(out isArchived, processId);
}
public Result SetCurrentProcess(ulong processId)
{
CurrentProcess = processId;
// Initialize the NCA file system service
var ncaService = new NcaFileSystemService(FsProxyCore.Config.NcaFileSystemService, processId);
NcaFileSystemService = new ReferenceCountedDisposable<NcaFileSystemService>(ncaService);
NcaFileSystemService.Target.SetSelfReference(NcaFileSystemService);
return Result.Success;
}
@ -140,44 +113,96 @@ namespace LibHac.FsSrv
throw new NotImplementedException();
}
public Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem)
public Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
return ncaFsService.OpenDataFileSystemByCurrentProcess(out fileSystem);
}
public Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, ProgramId programId)
public Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ProgramId programId)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
return ncaFsService.OpenDataFileSystemByProgramId(out fileSystem, programId);
}
public Result OpenDataStorageByCurrentProcess(out IStorage storage)
public Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable<IStorage> storage)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
storage = default;
return rc;
}
return ncaFsService.OpenDataStorageByCurrentProcess(out storage);
}
public Result OpenDataStorageByProgramId(out IStorage storage, ProgramId programId)
public Result OpenDataStorageByProgramId(out ReferenceCountedDisposable<IStorage> storage, ProgramId programId)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
storage = default;
return rc;
}
return ncaFsService.OpenDataStorageByProgramId(out storage, programId);
}
public Result OpenDataStorageByDataId(out IStorage storage, DataId dataId, StorageId storageId)
public Result OpenDataStorageByDataId(out ReferenceCountedDisposable<IStorage> storage, DataId dataId, StorageId storageId)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
storage = default;
return rc;
}
return ncaFsService.OpenDataStorageByDataId(out storage, dataId, storageId);
}
public Result OpenPatchDataStorageByCurrentProcess(out IStorage storage)
{
throw new NotImplementedException();
storage = default;
return ResultFs.TargetNotFound.Log();
}
public Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex)
public Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable<IFileSystem> fileSystem,
byte programIndex)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
return ncaFsService.OpenDataFileSystemWithProgramIndex(out fileSystem, programIndex);
}
public Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex)
public Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable<IStorage> storage, byte programIndex)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
storage = default;
return rc;
}
return ncaFsService.OpenDataStorageWithProgramIndex(out storage, programIndex);
}
public Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan<ulong> saveDataIds)
@ -719,7 +744,8 @@ namespace LibHac.FsSrv
throw new NotImplementedException();
}
public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId directoryId)
public Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ImageDirectoryId directoryId)
{
return GetBaseFileSystemService().OpenImageDirectoryFileSystem(out fileSystem, directoryId);
}
@ -734,7 +760,8 @@ namespace LibHac.FsSrv
throw new NotImplementedException();
}
public Result OpenBisFileSystem(out IFileSystem fileSystem, in FspPath rootPath, BisPartitionId partitionId)
public Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath rootPath,
BisPartitionId partitionId)
{
return GetBaseFileSystemService().OpenBisFileSystem(out fileSystem, in rootPath, partitionId);
}
@ -761,7 +788,7 @@ namespace LibHac.FsSrv
return OpenHostFileSystemWithOption(out fileSystem, ref path, MountHostOption.None);
}
public Result OpenSdCardFileSystem(out IFileSystem fileSystem)
public Result OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
return GetBaseFileSystemService().OpenSdCardFileSystem(out fileSystem);
}
@ -795,6 +822,26 @@ namespace LibHac.FsSrv
return FsProxyCore.OpenDeviceOperator(out deviceOperator);
}
public Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable<IEventNotifier> eventNotifier)
{
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
eventNotifier = null;
return rc;
}
return ncaFsService.OpenSystemDataUpdateEventNotifier(out eventNotifier);
}
public Result NotifySystemDataUpdateEvent()
{
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.NotifySystemDataUpdateEvent();
}
public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable<ISaveDataInfoReader> infoReader)
{
infoReader = default;
@ -1015,11 +1062,16 @@ namespace LibHac.FsSrv
return Result.Success;
}
public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId)
public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, ContentStorageId storageId)
{
// Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = null;
return rc;
}
return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId);
return ncaFsService.OpenContentStorageFileSystem(out fileSystem, storageId);
}
public Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId)
@ -1034,8 +1086,8 @@ namespace LibHac.FsSrv
return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId);
}
public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle,
GameCardPartition partitionId)
public Result OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
GameCardHandle handle, GameCardPartition partitionId)
{
return GetBaseFileSystemService().OpenGameCardFileSystem(out fileSystem, handle, partitionId);
}
@ -1056,48 +1108,69 @@ namespace LibHac.FsSrv
public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId)
{
throw new NotImplementedException();
Unsafe.SkipInit(out rightsId);
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.GetRightsId(out rightsId, programId, storageId);
}
public Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path)
public Result GetRightsIdByPath(out RightsId rightsId, in FspPath path)
{
throw new NotImplementedException();
return GetRightsIdAndKeyGenerationByPath(out rightsId, out _, in path);
}
public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path)
public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path)
{
throw new NotImplementedException();
Unsafe.SkipInit(out rightsId);
Unsafe.SkipInit(out keyGeneration);
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in path);
}
public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey)
public Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey)
{
// Missing permission check
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return FsProxyCore.RegisterExternalKey(ref rightsId, ref externalKey);
return ncaFsService.RegisterExternalKey(in rightsId, in externalKey);
}
public Result UnregisterExternalKey(ref RightsId rightsId)
public Result UnregisterExternalKey(in RightsId rightsId)
{
// Missing permission check
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return FsProxyCore.UnregisterExternalKey(ref rightsId);
return ncaFsService.UnregisterExternalKey(in rightsId);
}
public Result UnregisterAllExternalKey()
{
// Missing permission check
return FsProxyCore.UnregisterAllExternalKey();
}
public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed)
{
// Missing permission check
Result rc = FsProxyCore.SetSdCardEncryptionSeed(ref seed);
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return Result.Success;
return ncaFsService.UnregisterAllExternalKey();
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed seed)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed))
return ResultFs.PermissionDenied.Log();
rc = FsProxyCore.SetSdCardEncryptionSeed(in seed);
if (rc.IsFailure()) return rc;
rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.SetSdCardEncryptionSeed(in seed);
}
public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span<byte> readBuffer)
@ -1166,12 +1239,22 @@ namespace LibHac.FsSrv
public Result RegisterUpdatePartition()
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure()) return rc;
return ncaFsService.RegisterUpdatePartition();
}
public Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem)
public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
throw new NotImplementedException();
Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService);
if (rc.IsFailure())
{
fileSystem = default;
return rc;
}
return ncaFsService.OpenRegisteredUpdatePartition(out fileSystem);
}
public Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan<byte> key)
@ -1269,7 +1352,19 @@ namespace LibHac.FsSrv
private BaseFileSystemService GetBaseFileSystemService()
{
return new BaseFileSystemService(FsProxyCore.Config.BaseFileSystemServiceImpl, CurrentProcess);
return new BaseFileSystemService(FsProxyCore.Config.BaseFileSystemService, CurrentProcess);
}
private Result GetNcaFileSystemService(out NcaFileSystemService ncaFsService)
{
if (NcaFileSystemService is null)
{
ncaFsService = null;
return ResultFs.PreconditionViolation.Log();
}
ncaFsService = NcaFileSystemService.Target;
return Result.Success;
}
internal bool IsCurrentProcess(ulong processId)

View File

@ -12,6 +12,9 @@ namespace LibHac.FsSrv
{
internal const ulong SaveIndexerId = 0x8000000000000000;
private const ulong SpeedEmulationProgramIdMinimum = 0x100000000000000;
private const ulong SpeedEmulationProgramIdMaximum = 0x100000000001FFF;
private FileSystemProxyCoreImpl FsProxyCore { get; }
/// <summary>The client instance to be used for internal operations like save indexer access.</summary>
@ -91,11 +94,30 @@ namespace LibHac.FsSrv
baseFsServiceConfig.ProgramRegistry = programRegistry;
var baseFsService = new BaseFileSystemServiceImpl(in baseFsServiceConfig);
var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration();
ncaFsServiceConfig.BaseFsService = baseFsService;
ncaFsServiceConfig.HostFsCreator = config.FsCreators.HostFileSystemCreator;
ncaFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator;
ncaFsServiceConfig.PartitionFsCreator = config.FsCreators.PartitionFileSystemCreator;
ncaFsServiceConfig.RomFsCreator = config.FsCreators.RomFileSystemCreator;
ncaFsServiceConfig.StorageOnNcaCreator = config.FsCreators.StorageOnNcaCreator;
ncaFsServiceConfig.SubDirectoryFsCreator = config.FsCreators.SubDirectoryFileSystemCreator;
ncaFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator;
ncaFsServiceConfig.ProgramRegistryService = programRegistryService;
ncaFsServiceConfig.HorizonClient = Hos;
ncaFsServiceConfig.ProgramRegistry = programRegistry;
ncaFsServiceConfig.SpeedEmulationRange =
new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdMinimum,
SpeedEmulationProgramIdMaximum);
var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig, config.ExternalKeySet);
var fspConfig = new FileSystemProxyConfiguration
{
FsCreatorInterfaces = config.FsCreators,
BaseFileSystemServiceImpl = baseFsService,
ProgramRegistryServiceImpl = programRegistryService
BaseFileSystemService = baseFsService,
NcaFileSystemService = ncaFsService,
ProgramRegistryService = programRegistryService
};
return fspConfig;
@ -113,7 +135,7 @@ namespace LibHac.FsSrv
private ProgramRegistryImpl GetProgramRegistryServiceObject()
{
return new ProgramRegistryImpl(FsProxyCore.Config.ProgramRegistryServiceImpl);
return new ProgramRegistryImpl(FsProxyCore.Config.ProgramRegistryService);
}
private class FileSystemProxyService : IServiceObject

View File

@ -13,16 +13,16 @@ namespace LibHac.FsSrv
public interface IFileSystemProxy
{
Result SetCurrentProcess(ulong processId);
Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem);
Result OpenFileSystemWithPatch(out IFileSystem fileSystem, ProgramId programId, FileSystemProxyType type);
Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, ulong id, FileSystemProxyType type);
Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, ProgramId programId);
Result OpenBisFileSystem(out IFileSystem fileSystem, in FspPath rootPath, BisPartitionId partitionId);
Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable<IFileSystem> fileSystem);
Result OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem, ProgramId programId, FileSystemProxyType fsType);
Result OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath path, ulong id, FileSystemProxyType fsType);
Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable<IFileSystem> fileSystem, ProgramId programId);
Result OpenBisFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath rootPath, BisPartitionId partitionId);
Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId);
Result InvalidateBisCache();
Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option);
Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path);
Result OpenSdCardFileSystem(out IFileSystem fileSystem);
Result OpenSdCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem);
Result FormatSdCardFileSystem();
Result DeleteSaveDataFileSystem(ulong saveDataId);
Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, ref SaveMetaCreateInfo metaCreateInfo);
@ -33,7 +33,7 @@ namespace LibHac.FsSrv
Result IsExFatSupported(out bool isSupported);
Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute);
Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId);
Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionId);
Result OpenGameCardFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, GameCardHandle handle, GameCardPartition partitionId);
Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize);
Result DeleteCacheStorage(short index);
Result GetCacheStorageSize(out long dataSize, out long journalSize, short index);
@ -57,36 +57,39 @@ namespace LibHac.FsSrv
Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, SaveDataMetaType type);
Result ListAccessibleSaveDataOwnerId(out int readCount, Span<Ncm.ApplicationId> idBuffer, ProgramId programId, int startIndex, int bufferIdCount);
Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId directoryId);
Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId);
Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, ImageDirectoryId directoryId);
Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, ContentStorageId storageId);
Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId);
Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId);
Result OpenDataStorageByCurrentProcess(out IStorage storage);
Result OpenDataStorageByProgramId(out IStorage storage, ProgramId programId);
Result OpenDataStorageByDataId(out IStorage storage, DataId dataId, StorageId storageId);
Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable<IStorage> storage);
Result OpenDataStorageByProgramId(out ReferenceCountedDisposable<IStorage> storage, ProgramId programId);
Result OpenDataStorageByDataId(out ReferenceCountedDisposable<IStorage> storage, DataId dataId, StorageId storageId);
Result OpenPatchDataStorageByCurrentProcess(out IStorage storage);
Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex);
Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex);
Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable<IFileSystem> fileSystem, byte programIndex);
Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable<IStorage> storage, byte programIndex);
Result OpenDeviceOperator(out IDeviceOperator deviceOperator);
Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable<IEventNotifier> eventNotifier);
Result NotifySystemDataUpdateEvent();
Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize);
Result VerifySaveDataFileSystem(ulong saveDataId, Span<byte> readBuffer);
Result CorruptSaveDataFileSystem(ulong saveDataId);
Result CreatePaddingFile(long size);
Result DeleteAllPaddingFiles();
Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId);
Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey);
Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey);
Result UnregisterAllExternalKey();
Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path);
Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path);
Result GetRightsIdByPath(out RightsId rightsId, in FspPath path);
Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path);
Result SetCurrentPosixTimeWithTimeDifference(long time, int difference);
Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId);
Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span<byte> readBuffer);
Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId);
Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId);
Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId);
Result UnregisterExternalKey(ref RightsId rightsId);
Result SetSdCardEncryptionSeed(ref EncryptionSeed seed);
Result UnregisterExternalKey(in RightsId rightsId);
Result SetSdCardEncryptionSeed(in EncryptionSeed seed);
Result SetSdCardAccessibility(bool isAccessible);
Result IsSdCardAccessible(out bool isAccessible);
@ -99,7 +102,7 @@ namespace LibHac.FsSrv
Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode);
Result OutputAccessLogToSdCard(U8Span logString);
Result RegisterUpdatePartition();
Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem);
Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable<IFileSystem> fileSystem);
Result GetProgramIndexForAccessLog(out int programIndex, out int programCount);
Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan<byte> key);

View File

@ -0,0 +1,17 @@
using LibHac.Fs;
using LibHac.FsSystem;
using LibHac.Ncm;
namespace LibHac.FsSrv
{
public interface IRomFileSystemAccessFailureManager
{
Result OpenDataStorageCore(out ReferenceCountedDisposable<IStorage> storage, out Hash ncaHeaderDigest, ulong id,
StorageId storageId);
Result HandleResolubleAccessFailure(out bool wasDeferred, in Result nonDeferredResult);
void IncrementRomFsRemountForDataCorruptionCount();
void IncrementRomFsUnrecoverableDataCorruptionByRemountCount();
void IncrementRomFsRecoveredByInvalidateCacheCount();
}
}

View File

@ -0,0 +1,222 @@
using System;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Lr;
using LibHac.Ncm;
namespace LibHac.FsSrv.Impl
{
internal class LocationResolverSet : IDisposable
{
private const int LocationResolverCount = 5;
private LocationResolver[] _resolvers;
private AddOnContentLocationResolver _aocResolver;
private object _locker;
private HorizonClient _hos;
public LocationResolverSet(HorizonClient horizonClient)
{
_resolvers = new LocationResolver[LocationResolverCount];
_locker = new object();
_hos = horizonClient;
}
private Result GetLocationResolver(out LocationResolver resolver, StorageId storageId)
{
resolver = default;
if (!IsValidStorageId(storageId))
return ResultLr.LocationResolverNotFound.Log();
lock (_locker)
{
int index = GetResolverIndexFromStorageId(storageId);
ref LocationResolver lr = ref _resolvers[index];
// Open the location resolver if it hasn't been already
if (lr is null && _hos.Lr.OpenLocationResolver(out lr, storageId).IsFailure())
return ResultLr.LocationResolverNotFound.Log();
resolver = lr;
return Result.Success;
}
}
private Result GetRegisteredLocationResolver(out RegisteredLocationResolver resolver)
{
Result rc = _hos.Lr.OpenRegisteredLocationResolver(out RegisteredLocationResolver lr);
if (rc.IsFailure())
{
lr?.Dispose();
resolver = default;
return rc;
}
resolver = lr;
return Result.Success;
}
private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver)
{
lock (_locker)
{
if (_aocResolver is null)
{
Result rc = _hos.Lr.OpenAddOnContentLocationResolver(out AddOnContentLocationResolver lr);
if (rc.IsFailure())
{
resolver = default;
return rc;
}
_aocResolver = lr;
}
resolver = _aocResolver;
return Result.Success;
}
}
public Result ResolveApplicationControlPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId)
{
Path.InitEmpty(out path);
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
if (rc.IsFailure()) return rc;
rc = resolver.ResolveApplicationControlPath(out path, applicationId);
if (rc.IsFailure()) return rc;
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
return Result.Success;
}
public Result ResolveApplicationHtmlDocumentPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId)
{
Path.InitEmpty(out path);
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
if (rc.IsFailure()) return rc;
rc = resolver.ResolveApplicationHtmlDocumentPath(out path, applicationId);
if (rc.IsFailure()) return rc;
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
return Result.Success;
}
public Result ResolveProgramPath(out Path path, ProgramId programId, StorageId storageId)
{
Path.InitEmpty(out path);
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
if (rc.IsFailure()) return rc;
rc = resolver.ResolveProgramPath(out path, programId);
if (rc.IsFailure()) return rc;
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
return Result.Success;
}
public Result ResolveRomPath(out Path path, ProgramId programId, StorageId storageId)
{
Path.InitEmpty(out path);
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
if (rc.IsFailure()) return rc;
rc = resolver.ResolveProgramPath(out path, programId);
if (rc.IsFailure()) return rc;
PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/');
return Result.Success;
}
public Result ResolveAddOnContentPath(out Path path, DataId dataId)
{
Path.InitEmpty(out path);
Result rc = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver);
if (rc.IsFailure()) return rc;
return resolver.ResolveAddOnContentPath(out path, dataId);
}
public Result ResolveDataPath(out Path path, DataId dataId, StorageId storageId)
{
Path.InitEmpty(out path);
if (storageId == StorageId.None)
return ResultFs.InvalidAlignment.Log();
Result rc = GetLocationResolver(out LocationResolver resolver, storageId);
if (rc.IsFailure()) return rc;
return resolver.ResolveDataPath(out path, dataId);
}
public Result ResolveRegisteredProgramPath(out Path path, ProgramId programId)
{
Path.InitEmpty(out path);
Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver);
if (rc.IsFailure()) return rc;
using (resolver)
{
return resolver.ResolveProgramPath(out path, programId);
}
}
public Result ResolveRegisteredHtmlDocumentPath(out Path path, ProgramId programId)
{
Path.InitEmpty(out path);
Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver);
if (rc.IsFailure()) return rc;
using (resolver)
{
return resolver.ResolveHtmlDocumentPath(out path, programId);
}
}
private static bool IsValidStorageId(StorageId id)
{
return id == StorageId.Host ||
id == StorageId.GameCard ||
id == StorageId.BuiltInSystem ||
id == StorageId.BuiltInUser ||
id == StorageId.SdCard;
}
private static int GetResolverIndexFromStorageId(StorageId id)
{
Assert.AssertTrue(IsValidStorageId(id));
return id switch
{
StorageId.Host => 2,
StorageId.GameCard => 4,
StorageId.BuiltInSystem => 0,
StorageId.BuiltInUser => 1,
StorageId.SdCard => 3,
_ => -1
};
}
public void Dispose()
{
foreach (LocationResolver resolver in _resolvers)
{
resolver?.Dispose();
}
_aocResolver?.Dispose();
}
}
}

View File

@ -0,0 +1,174 @@
using System;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem;
using LibHac.Util;
namespace LibHac.FsSrv.Impl
{
internal static class Utility
{
public static Result EnsureDirectory(IFileSystem fileSystem, U8Span path)
{
FsPath.FromSpan(out FsPath pathBuffer, ReadOnlySpan<byte>.Empty).IgnoreResult();
int pathLength = StringUtils.GetLength(path);
if (pathLength > 0)
{
// Remove any trailing directory separators
while (pathLength > 0 && path[pathLength - 1] == StringTraits.DirectorySeparator)
{
pathLength--;
}
// Copy the path to a mutable buffer
path.Value.Slice(0, pathLength).CopyTo(pathBuffer.Str);
}
pathBuffer.Str[pathLength] = StringTraits.NullTerminator;
return EnsureDirectoryImpl(fileSystem, pathBuffer.Str.Slice(0, pathLength));
}
private static Result EnsureDirectoryImpl(IFileSystem fileSystem, Span<byte> path)
{
// Double check the trailing separators have been trimmed
Assert.AssertTrue(path.Length == 0 || path[path.Length - 1] != StringTraits.DirectorySeparator);
// Use the root path if the input path is empty
var pathToCheck = new U8Span(path.IsEmpty ? FileSystemRootPath : path);
// Check if the path exists
Result rc = fileSystem.GetEntryType(out DirectoryEntryType entryType, pathToCheck);
if (rc.IsFailure())
{
// Something went wrong if we get a result other than PathNotFound
if (!ResultFs.PathNotFound.Includes(rc))
return rc;
if (path.Length <= 0)
{
// The file system either reported that the root directory doesn't exist,
// or the input path had a negative length
return ResultFs.PathNotFound.Log();
}
// The path does not exist. Ensure its parent directory exists
rc = EnsureParentDirectoryImpl(fileSystem, path);
if (rc.IsFailure()) return rc;
// The parent directory exists, we can now create a directory at the input path
rc = fileSystem.CreateDirectory(new U8Span(path));
if (rc.IsSuccess())
{
// The directory was successfully created
return Result.Success;
}
if (!ResultFs.PathAlreadyExists.Includes(rc))
return rc;
// Someone else created a file system entry at the input path after we checked
// if the path existed. Get the entry type to check if it's a directory.
rc = fileSystem.GetEntryType(out entryType, new U8Span(path));
if (rc.IsFailure()) return rc;
}
// We want the entry that exists at the input path to be a directory
// Return PathAlreadyExists if it's a file
if (entryType == DirectoryEntryType.File)
return ResultFs.PathAlreadyExists.Log();
// A directory exists at the input path. Success
return Result.Success;
}
private static Result EnsureParentDirectoryImpl(IFileSystem fileSystem, Span<byte> path)
{
// The path should not be empty or have a trailing directory separator
Assert.AssertTrue(path.Length > 0);
Assert.NotEqual(StringTraits.DirectorySeparator, path[path.Length - 1]);
// Make sure the path's not too long
if (path.Length > PathTool.EntryNameLengthMax)
return ResultFs.TooLongPath.Log();
// Iterate until we run out of path or find the next separator
int length = path.Length;
while (length > 0 && path[length - 1] != StringTraits.DirectorySeparator)
{
length--;
}
if (length == 0)
{
// We hit the beginning of the path. Ensure the root directory exists and return
return EnsureDirectoryImpl(fileSystem, Span<byte>.Empty);
}
// We found the length of the parent directory. Ensure it exists
path[length - 1] = StringTraits.NullTerminator;
Result rc = EnsureDirectoryImpl(fileSystem, path.Slice(0, length));
if (rc.IsFailure()) return rc;
// Restore the separator
path[length - 1] = StringTraits.DirectorySeparator;
return Result.Success;
}
public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable<IFileSystem> subDirFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool preserveUnc = false)
{
subDirFileSystem = default;
if (path.IsNull())
return ResultFs.NullptrArgument.Log();
// Check if the directory exists
Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, path, OpenDirectoryMode.Directory);
if (rc.IsFailure()) return rc;
dir.Dispose();
var fs = new SubdirectoryFileSystem(baseFileSystem, preserveUnc);
using (var subDirFs = new ReferenceCountedDisposable<SubdirectoryFileSystem>(fs))
{
rc = subDirFs.Target.Initialize(path);
if (rc.IsFailure()) return rc;
subDirFileSystem = subDirFs.AddReference<IFileSystem>();
return Result.Success;
}
}
public static Result WrapSubDirectory(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path, bool createIfMissing)
{
fileSystem = default;
// The path must already exist if we're not automatically creating it
if (!createIfMissing)
{
Result result = baseFileSystem.Target.GetEntryType(out _, path);
if (result.IsFailure()) return result;
}
// Ensure the path exists or check if it's a directory
Result rc = EnsureDirectory(baseFileSystem.Target, path);
if (rc.IsFailure()) return rc;
return CreateSubDirectoryFileSystem(out fileSystem, baseFileSystem, path);
}
private static ReadOnlySpan<byte> FileSystemRootPath => // /
new[]
{
(byte) '/'
};
}
}

View File

@ -0,0 +1,326 @@
using System;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Impl;
using LibHac.FsSrv.Sf;
using LibHac.FsSystem;
using LibHac.Ncm;
using LibHac.Spl;
using LibHac.Util;
namespace LibHac.FsSrv
{
internal class NcaFileSystemService : IRomFileSystemAccessFailureManager, IDisposable
{
private const int AocSemaphoreCount = 128;
private const int RomSemaphoreCount = 10;
private ReferenceCountedDisposable<NcaFileSystemService>.WeakReference SelfReference { get; set; }
private NcaFileSystemServiceImpl ServiceImpl { get; }
private ulong ProcessId { get; }
private SemaphoreAdaptor AocMountCountSemaphore { get; }
private SemaphoreAdaptor RomMountCountSemaphore { get; }
public NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId)
{
ServiceImpl = serviceImpl;
ProcessId = processId;
AocMountCountSemaphore = new SemaphoreAdaptor(AocSemaphoreCount, AocSemaphoreCount);
RomMountCountSemaphore = new SemaphoreAdaptor(RomSemaphoreCount, RomSemaphoreCount);
}
public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ProgramId programId, FileSystemProxyType fsType)
{
throw new NotImplementedException();
}
public Result OpenCodeFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out CodeVerificationData verificationData, in FspPath path, ProgramId programId)
{
throw new NotImplementedException();
}
public Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
throw new NotImplementedException();
}
private Result OpenDataStorageCore(out ReferenceCountedDisposable<IStorage> storage, out Hash ncaHeaderDigest,
ulong id, StorageId storageId)
{
throw new NotImplementedException();
}
public Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable<IStorage> storage)
{
throw new NotImplementedException();
}
public Result OpenDataStorageByProgramId(out ReferenceCountedDisposable<IStorage> storage, ProgramId programId)
{
throw new NotImplementedException();
}
public Result OpenFileSystemWithId(out ReferenceCountedDisposable<IFileSystem> fileSystem, in FspPath path,
ulong id, FileSystemProxyType fsType)
{
fileSystem = default;
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
AccessControl ac = programInfo.AccessControl;
switch (fsType)
{
case FileSystemProxyType.Logo:
if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Control:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Manual:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Meta:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Data:
if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead)
return ResultFs.PermissionDenied.Log();
break;
case FileSystemProxyType.Package:
if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead)
return ResultFs.PermissionDenied.Log();
break;
default:
return ResultFs.InvalidArgument.Log();
}
if (fsType == FileSystemProxyType.Meta)
{
id = ulong.MaxValue;
}
else if (id == ulong.MaxValue)
{
return ResultFs.InvalidArgument.Log();
}
bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead;
var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path));
if (normalizer.Result.IsFailure()) return normalizer.Result;
rc = ServiceImpl.OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> baseFs, out _, path, fsType,
canMountSystemDataPrivate, id);
if (rc.IsFailure()) return rc;
fileSystem = baseFs;
return Result.Success;
}
public Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ProgramId programId)
{
throw new NotImplementedException();
}
public Result OpenDataStorageByDataId(out ReferenceCountedDisposable<IStorage> storage, DataId dataId,
StorageId storageId)
{
throw new NotImplementedException();
}
public Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable<IFileSystem> fileSystem,
byte programIndex)
{
throw new NotImplementedException();
}
public Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable<IStorage> storage,
byte programIndex)
{
throw new NotImplementedException();
}
public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId)
{
throw new NotImplementedException();
}
public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path)
{
throw new NotImplementedException();
}
private Result OpenDataFileSystemCore(out ReferenceCountedDisposable<IFileSystem> fileSystem, out bool isHostFs,
ulong id, StorageId storageId)
{
throw new NotImplementedException();
}
public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ContentStorageId contentStorageId)
{
fileSystem = default;
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
Accessibility accessibility =
programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage);
if (!accessibility.CanRead || !accessibility.CanWrite)
return ResultFs.PermissionDenied.Log();
rc = ServiceImpl.OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> contentFs,
contentStorageId);
if (rc.IsFailure()) return rc;
fileSystem = contentFs;
return Result.Success;
}
public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey))
return ResultFs.PermissionDenied.Log();
return ServiceImpl.RegisterExternalKey(in rightsId, in accessKey);
}
public Result UnregisterExternalKey(in RightsId rightsId)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey))
return ResultFs.PermissionDenied.Log();
return ServiceImpl.UnregisterExternalKey(in rightsId);
}
public Result UnregisterAllExternalKey()
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey))
return ResultFs.PermissionDenied.Log();
return ServiceImpl.UnregisterAllExternalKey();
}
public Result RegisterUpdatePartition()
{
throw new NotImplementedException();
}
public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable<IFileSystem> fileSystem)
{
throw new NotImplementedException();
}
public Result IsArchivedProgram(out bool isArchived, ulong processId)
{
throw new NotImplementedException();
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed)
{
Result rc = GetProgramInfo(out ProgramInfo programInfo);
if (rc.IsFailure()) return rc;
if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed))
return ResultFs.PermissionDenied.Log();
return ServiceImpl.SetSdCardEncryptionSeed(in encryptionSeed);
}
public Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable<IEventNotifier> eventNotifier)
{
throw new NotImplementedException();
}
public Result NotifySystemDataUpdateEvent()
{
throw new NotImplementedException();
}
public Result HandleResolubleAccessFailure(out bool wasDeferred, in Result nonDeferredResult)
{
throw new NotImplementedException();
}
public void IncrementRomFsRemountForDataCorruptionCount()
{
throw new NotImplementedException();
}
public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount()
{
throw new NotImplementedException();
}
public void IncrementRomFsRecoveredByInvalidateCacheCount()
{
throw new NotImplementedException();
}
Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(out ReferenceCountedDisposable<IStorage> storage,
out Hash ncaHeaderDigest, ulong id, StorageId storageId)
{
return OpenDataStorageCore(out storage, out ncaHeaderDigest, id, storageId);
}
internal void SetSelfReference(ReferenceCountedDisposable<NcaFileSystemService> reference)
{
SelfReference = new ReferenceCountedDisposable<NcaFileSystemService>.WeakReference(reference);
}
private Result TryAcquireAddOnContentOpenCountSemaphore(out IUniqueLock semaphoreLock)
{
throw new NotImplementedException();
}
private Result TryAcquireRomMountCountSemaphore(out IUniqueLock semaphoreLock)
{
throw new NotImplementedException();
}
private Result GetProgramInfo(out ProgramInfo programInfo)
{
return ServiceImpl.GetProgramInfo(out programInfo, ProcessId);
}
private PathNormalizer.Option GetPathNormalizerOptions(U8Span path)
{
// Set the PreserveUnc flag if the path is on the host file system
PathNormalizer.Option hostOption = IsHostFs(path) ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None;
return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTailSeparator | hostOption;
}
private bool IsHostFs(U8Span path)
{
int hostMountLength = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName,
PathTools.MountNameLengthMax);
return StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountLength) == 0;
}
public void Dispose()
{
AocMountCountSemaphore?.Dispose();
RomMountCountSemaphore?.Dispose();
}
}
}

View File

@ -0,0 +1,836 @@
using System;
using System.Buffers.Text;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSrv.Creators;
using LibHac.FsSrv.Impl;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
using LibHac.Spl;
using LibHac.Util;
using RightsId = LibHac.Fs.RightsId;
using Utility = LibHac.FsSrv.Impl.Utility;
namespace LibHac.FsSrv
{
public class NcaFileSystemServiceImpl
{
private Configuration _config;
// UpdatePartitionPath
private ExternalKeySet _externalKeyManager;
private LocationResolverSet _locationResolverSet;
// SystemDataUpdateEventManager
private EncryptionSeed _encryptionSeed;
private int _romFsRemountForDataCorruptionCount;
private int _romfsUnrecoverableDataCorruptionByRemountCount;
private int _romFsRecoveredByInvalidateCacheCount;
private object _romfsCountLocker;
public NcaFileSystemServiceImpl(in Configuration configuration, ExternalKeySet externalKeySet)
{
_config = configuration;
_externalKeyManager = externalKeySet;
_locationResolverSet = new LocationResolverSet(_config.HorizonClient);
_romfsCountLocker = new object();
}
public struct Configuration
{
public BaseFileSystemServiceImpl BaseFsService;
public IHostFileSystemCreator HostFsCreator;
public ITargetManagerFileSystemCreator TargetManagerFsCreator;
public IPartitionFileSystemCreator PartitionFsCreator;
public IRomFileSystemCreator RomFsCreator;
public IStorageOnNcaCreator StorageOnNcaCreator;
public ISubDirectoryFileSystemCreator SubDirectoryFsCreator;
public IEncryptedFileSystemCreator EncryptedFsCreator;
public ProgramRegistryServiceImpl ProgramRegistryService;
// AccessFailureService
public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange;
// LibHac additions
public HorizonClient HorizonClient;
public ProgramRegistryImpl ProgramRegistry;
}
public Result OpenFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out CodeVerificationData verificationData, U8Span path, FileSystemProxyType type,
bool canMountSystemDataPrivate, ulong id)
{
fileSystem = default;
Unsafe.SkipInit(out verificationData);
verificationData.IsValid = false;
// Get a reference to the path that will be advanced as each part of the path is parsed
U8Span currentPath = path.Slice(0, StringUtils.GetLength(path));
// Open the root filesystem based on the path's mount name
Result rc = OpenFileSystemFromMountName(ref currentPath,
out ReferenceCountedDisposable<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 = _config.BaseFsService.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 currentPath, out bool isDirectory);
if (rc.IsFailure()) return rc;
if (isDirectory)
{
if (!mountNameInfo.IsHostFs)
return ResultFs.PermissionDenied.Log();
if (type == FileSystemProxyType.Manual)
{
rc = TryOpenCaseSensitiveContentDirectory(
out ReferenceCountedDisposable<IFileSystem> manualFileSystem, baseFileSystem, currentPath);
if (rc.IsFailure()) return rc;
var readOnlyFs = new ReadOnlyFileSystem(manualFileSystem);
fileSystem = new ReferenceCountedDisposable<IFileSystem>(readOnlyFs);
return Result.Success;
}
return TryOpenContentDirectory(currentPath, out fileSystem, baseFileSystem, type, true);
}
rc = TryOpenNsp(ref currentPath, out ReferenceCountedDisposable<IFileSystem> nspFileSystem, baseFileSystem);
if (rc.IsSuccess())
{
// Must be the end of the path to open Application Package FS type
if (currentPath.Length == 0 || currentPath[0] == 0)
{
if (type == FileSystemProxyType.Package)
{
fileSystem = nspFileSystem;
return Result.Success;
}
return ResultFs.InvalidArgument.Log();
}
baseFileSystem = nspFileSystem;
}
if (!mountNameInfo.CanMountNca)
{
return ResultFs.InvalidNcaMountPoint.Log();
}
ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : id;
rc = TryOpenNca(ref currentPath, out Nca nca, baseFileSystem, openProgramId);
if (rc.IsFailure()) return rc;
rc = OpenNcaStorage(out ReferenceCountedDisposable<IStorage> ncaSectionStorage, nca,
out NcaFormatType fsType, type, mountNameInfo.IsGameCard, canMountSystemDataPrivate);
if (rc.IsFailure()) return rc;
switch (fsType)
{
case NcaFormatType.Romfs:
return _config.RomFsCreator.Create(out fileSystem, ncaSectionStorage);
case NcaFormatType.Pfs0:
return _config.PartitionFsCreator.Create(out fileSystem, ncaSectionStorage);
default:
return ResultFs.InvalidNcaFileSystemType.Log();
}
}
private struct MountNameInfo
{
public bool IsGameCard;
public int GcHandle;
public bool IsHostFs;
public bool CanMountNca;
}
private Result OpenFileSystemFromMountName(ref U8Span path,
out ReferenceCountedDisposable<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 = _config.BaseFsService.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 = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.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 = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.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 = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.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 = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.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 = _config.BaseFsService.OpenSdCardProxyFileSystem(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 ReferenceCountedDisposable<IFileSystem> contentFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, FileSystemProxyType fsType, bool preserveUnc)
{
contentFileSystem = default;
Result rc = _config.SubDirectoryFsCreator.Create(out ReferenceCountedDisposable<IFileSystem> subDirFs,
baseFileSystem, path, preserveUnc);
if (rc.IsFailure()) return rc;
return OpenSubDirectoryForFsType(out contentFileSystem, subDirFs, fsType);
}
private Result TryOpenCaseSensitiveContentDirectory(
out ReferenceCountedDisposable<IFileSystem> contentFileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem, U8Span path)
{
contentFileSystem = default;
Unsafe.SkipInit(out FsPath fullPath);
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 = _config.TargetManagerFsCreator.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 _config.SubDirectoryFsCreator.Create(out contentFileSystem, baseFileSystem, fullPath);
}
private Result OpenSubDirectoryForFsType(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ReferenceCountedDisposable<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:
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 = _config.SubDirectoryFsCreator.Create(out ReferenceCountedDisposable<IFileSystem> subDirFs,
baseFileSystem, new U8Span(dirName));
if (rc.IsFailure()) return rc;
if (fsType == FileSystemProxyType.Code)
{
rc = _config.StorageOnNcaCreator.VerifyAcidSignature(subDirFs.Target, null);
if (rc.IsFailure()) return rc;
}
fileSystem = subDirFs;
return Result.Success;
}
private Result TryOpenNsp(ref U8Span path, out ReferenceCountedDisposable<IFileSystem> fileSystem,
ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
fileSystem = 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;
var storage = new FileStorageBasedFileSystem();
using var nspFileStorage = new ReferenceCountedDisposable<FileStorageBasedFileSystem>(storage);
rc = nspFileStorage.Target.Initialize(baseFileSystem, new U8Span(nspPath.Str), OpenMode.Read);
if (rc.IsFailure()) return rc;
rc = _config.PartitionFsCreator.Create(out fileSystem, nspFileStorage.AddReference<IStorage>());
if (rc.IsSuccess())
{
path = path.Slice(nspPathLen);
}
return rc;
}
private Result TryOpenNca(ref U8Span path, out Nca nca, ReferenceCountedDisposable<IFileSystem> baseFileSystem,
ulong ncaId)
{
nca = default;
// Todo: Create ref-counted storage
var ncaFileStorage = new FileStorageBasedFileSystem();
Result rc = ncaFileStorage.Initialize(baseFileSystem, path, OpenMode.Read);
if (rc.IsFailure()) return rc;
rc = _config.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage);
if (rc.IsFailure()) return rc;
if (ncaId == ulong.MaxValue)
{
ulong ncaProgramId = ncaTemp.Header.TitleId;
if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId)
{
return ResultFs.InvalidNcaId.Log();
}
}
nca = ncaTemp;
return Result.Success;
}
private Result OpenNcaStorage(out ReferenceCountedDisposable<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 = _config.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 = _externalKeyManager.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 OpenHostFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem, U8Span path, bool openCaseSensitive)
{
fileSystem = default;
Result rc;
if (!path.IsEmpty())
{
rc = Util.VerifyHostPath(path);
if (rc.IsFailure()) return rc;
}
rc = _config.TargetManagerFsCreator.Create(out ReferenceCountedDisposable<IFileSystem> hostFs,
openCaseSensitive);
if (rc.IsFailure()) return rc;
if (path.IsEmpty())
{
ReadOnlySpan<byte> rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' };
rc = hostFs.Target.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 = _config.SubDirectoryFsCreator.Create(out ReferenceCountedDisposable<IFileSystem> subDirFs, hostFs,
path, preserveUnc: true);
if (rc.IsFailure()) return rc;
fileSystem = subDirFs;
return Result.Success;
}
public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ProgramId programId, FileSystemProxyType fsType)
{
throw new NotImplementedException();
}
public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
ContentStorageId contentStorageId)
{
const int storagePathMaxLen = 0x40;
fileSystem = default;
ReferenceCountedDisposable<IFileSystem> baseFileSystem = null;
ReferenceCountedDisposable<IFileSystem> subDirFileSystem = null;
ReferenceCountedDisposable<IFileSystem> encryptedFileSystem = null;
try
{
Result rc;
// Open the appropriate base file system for the content storage ID
switch (contentStorageId)
{
case ContentStorageId.System:
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty,
BisPartitionId.System);
break;
case ContentStorageId.User:
rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty,
BisPartitionId.User);
break;
case ContentStorageId.SdCard:
rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem);
break;
default:
rc = ResultFs.InvalidArgument.Log();
break;
}
if (rc.IsFailure()) return rc;
// Build the appropriate path for the content storage ID
Span<byte> contentStoragePath = stackalloc byte[storagePathMaxLen];
if (contentStorageId == ContentStorageId.SdCard)
{
var sb = new U8StringBuilder(contentStoragePath);
sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName);
sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName);
}
else
{
var sb = new U8StringBuilder(contentStoragePath);
sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName);
}
// Make sure the content storage path exists
// ReSharper disable once PossibleNullReferenceException
rc = Utility.EnsureDirectory(baseFileSystem.Target, new U8Span(contentStoragePath));
if (rc.IsFailure()) return rc;
rc = _config.SubDirectoryFsCreator.Create(out subDirFileSystem, baseFileSystem,
new U8Span(contentStoragePath));
if (rc.IsFailure()) return rc;
// Only content on the SD card is encrypted
if (contentStorageId != ContentStorageId.SdCard)
{
// Move the shared reference to the out variable
fileSystem = subDirFileSystem;
subDirFileSystem = null;
return Result.Success;
}
// Create an encrypted file system for SD card content storage
rc = _config.EncryptedFsCreator.Create(out encryptedFileSystem, subDirFileSystem,
EncryptedFsKeyId.Content, in _encryptionSeed);
if (rc.IsFailure()) return rc;
fileSystem = encryptedFileSystem;
encryptedFileSystem = null;
return Result.Success;
}
finally
{
baseFileSystem?.Dispose();
subDirFileSystem?.Dispose();
encryptedFileSystem?.Dispose();
}
}
public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey)
{
return _externalKeyManager.Add(rightsId, accessKey);
}
public Result UnregisterExternalKey(in RightsId rightsId)
{
_externalKeyManager.Remove(rightsId);
return Result.Success;
}
public Result UnregisterAllExternalKey()
{
_externalKeyManager.Clear();
return Result.Success;
}
public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed)
{
_encryptionSeed = encryptionSeed;
return Result.Success;
}
internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId)
{
return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId);
}
private static ReadOnlySpan<byte> SdCardNintendoRootDirectoryName => // Nintendo
new[]
{
(byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o'
};
private static ReadOnlySpan<byte> ContentStorageDirectoryName => // Contents
new[]
{
(byte) 'C', (byte) 'o', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 't', (byte) 's'
};
}
public readonly struct InternalProgramIdRangeForSpeedEmulation
{
public readonly ulong ProgramIdMin;
public readonly ulong ProgramIdMax;
public InternalProgramIdRangeForSpeedEmulation(ulong min, ulong max)
{
ProgramIdMin = min;
ProgramIdMax = max;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using LibHac.Sf;
namespace LibHac.FsSrv.Sf
{
public interface IEventNotifier : IDisposable
{
Result GetEventHandle(out NativeHandle handle);
}
}

View File

@ -6,8 +6,8 @@ namespace LibHac.FsSrv.Sf
{
public interface IFileSystemProxyForLoader
{
Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData,
in FspPath path, ProgramId programId);
Result OpenCodeFileSystem(out ReferenceCountedDisposable<IFileSystem> fileSystem,
out CodeVerificationData verificationData, in FspPath path, ProgramId programId);
Result IsArchivedProgram(out bool isArchived, ulong processId);
Result SetCurrentProcess(ulong processId);

View File

@ -0,0 +1,17 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using LibHac.Common;
namespace LibHac.FsSystem
{
[StructLayout(LayoutKind.Sequential, Size = 0x20)]
public struct Hash
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1;
public readonly ReadOnlySpan<byte> Bytes => SpanHelpers.AsReadOnlyByteSpan(in this);
public Span<byte> BytesMutable => SpanHelpers.AsByteSpan(ref this);
}
}

View File

@ -0,0 +1,8 @@
using System;
namespace LibHac.FsSystem
{
public interface IUniqueLock : IDisposable
{
}
}

View File

@ -39,7 +39,7 @@
Code,
Data,
Logo
};
}
public enum NcaContentType
{

View File

@ -15,6 +15,16 @@ namespace LibHac.FsSystem
private PartitionFileSystemMetaCore<T> MetaData { get; set; }
private bool IsInitialized { get; set; }
private int DataOffset { get; set; }
private ReferenceCountedDisposable<IStorage> BaseStorageShared { get; set; }
public Result Initialize(ReferenceCountedDisposable<IStorage> baseStorage)
{
Result rc = Initialize(baseStorage.Target);
if (rc.IsFailure()) return rc;
BaseStorageShared = baseStorage.AddReference();
return Result.Success;
}
public Result Initialize(IStorage baseStorage)
{
@ -33,6 +43,16 @@ namespace LibHac.FsSystem
return Result.Success;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseStorageShared?.Dispose();
}
base.Dispose(disposing);
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
directory = default;

View File

@ -7,12 +7,20 @@ namespace LibHac.FsSystem
public class ReadOnlyFileSystem : IFileSystem
{
private IFileSystem BaseFs { get; }
private ReferenceCountedDisposable<IFileSystem> BaseFsShared { get; }
// Todo: Remove non-shared constructor
public ReadOnlyFileSystem(IFileSystem baseFileSystem)
{
BaseFs = baseFileSystem;
}
public ReadOnlyFileSystem(ReferenceCountedDisposable<IFileSystem> baseFileSystem)
{
BaseFsShared = baseFileSystem;
BaseFs = BaseFsShared.Target;
}
protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode)
{
return BaseFs.OpenDirectory(out directory, path, mode);
@ -36,11 +44,7 @@ namespace LibHac.FsSystem
protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path)
{
freeSpace = 0;
return Result.Success;
// FS does:
// return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log();
return BaseFs.GetFreeSpaceSize(out freeSpace, path);
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path)
@ -79,5 +83,15 @@ namespace LibHac.FsSystem
protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log();
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseFsShared?.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Threading;
namespace LibHac.FsSystem
{
public class SemaphoreAdaptor : IDisposable
{
private SemaphoreSlim _semaphore;
public SemaphoreAdaptor(int initialCount, int maxCount)
{
_semaphore = new SemaphoreSlim(initialCount, maxCount);
}
public bool TryLock()
{
return _semaphore.Wait(TimeSpan.Zero);
}
public void Unlock()
{
_semaphore.Release();
}
public void Dispose()
{
_semaphore?.Dispose();
}
}
}

View File

@ -10,6 +10,7 @@ namespace LibHac.FsSystem
public class SubdirectoryFileSystem : IFileSystem
{
private IFileSystem BaseFileSystem { get; }
private ReferenceCountedDisposable<IFileSystem> BaseFileSystemShared { get; }
private U8String RootPath { get; set; }
private bool PreserveUnc { get; }
@ -35,7 +36,24 @@ namespace LibHac.FsSystem
PreserveUnc = preserveUnc;
}
private Result Initialize(U8Span rootPath)
public SubdirectoryFileSystem(ReferenceCountedDisposable<IFileSystem> baseFileSystem, bool preserveUnc = false)
{
BaseFileSystemShared = baseFileSystem.AddReference();
BaseFileSystem = BaseFileSystemShared.Target;
PreserveUnc = preserveUnc;
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
BaseFileSystemShared?.Dispose();
}
base.Dispose(disposing);
}
public Result Initialize(U8Span rootPath)
{
if (StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength)
return ResultFs.TooLongPath.Log();

View File

@ -1,6 +1,7 @@
using System;
using LibHac.Arp;
using LibHac.Fs;
using LibHac.Lr;
using LibHac.Os;
using LibHac.Sm;
@ -17,6 +18,7 @@ namespace LibHac
public FileSystemClient Fs { get; }
public ServiceManagerClient Sm { get; }
public OsClient Os { get; }
public LrClient Lr { get; }
public ArpClient Arp => ArpLazy.Value;
public ITimeSpanGenerator Time => Horizon.Time;
@ -29,6 +31,7 @@ namespace LibHac
Fs = new FileSystemClient(this);
Sm = new ServiceManagerClient(horizon.ServiceManager);
Os = new OsClient(this);
Lr = new LrClient(this);
ArpLazy = new Lazy<ArpClient>(InitArpClient, true);
}

View File

@ -0,0 +1,35 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public class AddOnContentLocationResolver : IDisposable
{
private ReferenceCountedDisposable<IAddOnContentLocationResolver> _interface;
public AddOnContentLocationResolver(ReferenceCountedDisposable<IAddOnContentLocationResolver> baseInterface)
{
_interface = baseInterface.AddReference();
}
public Result ResolveAddOnContentPath(out Path path, DataId id) =>
_interface.Target.ResolveAddOnContentPath(out path, id);
public Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId) =>
_interface.Target.RegisterAddOnContentStorage(id, applicationId, storageId);
public Result UnregisterAllAddOnContentPath() =>
_interface.Target.UnregisterAllAddOnContentPath();
public Result RefreshApplicationAddOnContent(ReadOnlySpan<Ncm.ApplicationId> ids) =>
_interface.Target.RefreshApplicationAddOnContent(ids);
public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) =>
_interface.Target.UnregisterApplicationAddOnContent(id);
public void Dispose()
{
_interface?.Dispose();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public interface IAddOnContentLocationResolver : IDisposable
{
Result ResolveAddOnContentPath(out Path path, DataId id);
Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId);
Result UnregisterAllAddOnContentPath();
Result RefreshApplicationAddOnContent(ReadOnlySpan<Ncm.ApplicationId> ids);
Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id);
}
}

View File

@ -0,0 +1,29 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public interface ILocationResolver : IDisposable
{
Result ResolveProgramPath(out Path path, ProgramId id);
Result RedirectProgramPath(in Path path, ProgramId id);
Result ResolveApplicationControlPath(out Path path, ProgramId id);
Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id);
Result ResolveDataPath(out Path path, DataId id);
Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId);
Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId);
Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id);
Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId);
Result Refresh();
Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId);
Result ClearApplicationRedirection(ReadOnlySpan<ProgramId> excludingIds);
Result EraseProgramRedirection(ProgramId id);
Result EraseApplicationControlRedirection(ProgramId id);
Result EraseApplicationHtmlDocumentRedirection(ProgramId id);
Result EraseApplicationLegalInformationRedirection(ProgramId id);
Result ResolveProgramPathForDebug(out Path path, ProgramId id);
Result RedirectProgramPathForDebug(in Path path, ProgramId id);
Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id);
Result EraseProgramRedirectionForDebug(ProgramId id);
}
}

View File

@ -0,0 +1,13 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public interface ILocationResolverManager : IDisposable
{
Result OpenLocationResolver(out ReferenceCountedDisposable<ILocationResolver> resolver, StorageId storageId);
Result OpenRegisteredLocationResolver(out ReferenceCountedDisposable<IRegisteredLocationResolver> resolver);
Result RefreshLocationResolver(StorageId storageId);
Result OpenAddOnContentLocationResolver(out ReferenceCountedDisposable<IAddOnContentLocationResolver> resolver);
}
}

View File

@ -0,0 +1,19 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public interface IRegisteredLocationResolver : IDisposable
{
Result ResolveProgramPath(out Path path, ProgramId id);
Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId);
Result UnregisterProgramPath(ProgramId id);
Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId);
Result ResolveHtmlDocumentPath(out Path path, ProgramId id);
Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId);
Result UnregisterHtmlDocumentPath(ProgramId id);
Result RedirectHtmlDocumentPath(in Path path, ProgramId id);
Result Refresh();
Result RefreshExcluding(ReadOnlySpan<ProgramId> ids);
}
}

View File

@ -0,0 +1,80 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public class LocationResolver : IDisposable
{
private ReferenceCountedDisposable<ILocationResolver> _interface;
public LocationResolver(ReferenceCountedDisposable<ILocationResolver> baseInterface)
{
_interface = baseInterface.AddReference();
}
public Result ResolveProgramPath(out Path path, ProgramId id) =>
_interface.Target.ResolveProgramPath(out path, id);
public Result RedirectProgramPath(in Path path, ProgramId id) =>
_interface.Target.RedirectProgramPath(in path, id);
public Result ResolveApplicationControlPath(out Path path, ProgramId id) =>
_interface.Target.ResolveApplicationControlPath(out path, id);
public Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id) =>
_interface.Target.ResolveApplicationHtmlDocumentPath(out path, id);
public Result ResolveDataPath(out Path path, DataId id) =>
_interface.Target.ResolveDataPath(out path, id);
public Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RedirectApplicationControlPath(in path, id, ownerId);
public Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RedirectApplicationHtmlDocumentPath(in path, id, ownerId);
public Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id) =>
_interface.Target.ResolveApplicationLegalInformationPath(out path, id);
public Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RedirectApplicationLegalInformationPath(in path, id, ownerId);
public Result Refresh() =>
_interface.Target.Refresh();
public Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RedirectApplicationProgramPath(in path, id, ownerId);
public Result ClearApplicationRedirection(ReadOnlySpan<ProgramId> excludingIds) =>
_interface.Target.ClearApplicationRedirection(excludingIds);
public Result EraseProgramRedirection(ProgramId id) =>
_interface.Target.EraseProgramRedirection(id);
public Result EraseApplicationControlRedirection(ProgramId id) =>
_interface.Target.EraseApplicationControlRedirection(id);
public Result EraseApplicationHtmlDocumentRedirection(ProgramId id) =>
_interface.Target.EraseApplicationHtmlDocumentRedirection(id);
public Result EraseApplicationLegalInformationRedirection(ProgramId id) =>
_interface.Target.EraseApplicationLegalInformationRedirection(id);
public Result ResolveProgramPathForDebug(out Path path, ProgramId id) =>
_interface.Target.ResolveProgramPathForDebug(out path, id);
public Result RedirectProgramPathForDebug(in Path path, ProgramId id) =>
_interface.Target.RedirectProgramPathForDebug(in path, id);
public Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id) =>
_interface.Target.RedirectApplicationProgramPathForDebug(in path, id);
public Result EraseProgramRedirectionForDebug(ProgramId id) =>
_interface.Target.EraseProgramRedirectionForDebug(id);
public void Dispose()
{
_interface?.Dispose();
}
}
}

102
src/LibHac/Lr/LrClient.cs Normal file
View File

@ -0,0 +1,102 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public class LrClient : IDisposable
{
private HorizonClient Hos { get; }
private ILocationResolverManager LrManager { get; set; }
private readonly object _lrInitLocker = new object();
public LrClient(HorizonClient horizonClient)
{
Hos = horizonClient;
}
public Result OpenLocationResolver(out LocationResolver resolver, StorageId storageId)
{
resolver = default;
EnsureInitialized();
Result rc = LrManager.OpenLocationResolver(out ReferenceCountedDisposable<ILocationResolver> baseResolver,
storageId);
if (rc.IsFailure()) return rc;
using (baseResolver)
{
resolver = new LocationResolver(baseResolver);
return Result.Success;
}
}
public Result OpenRegisteredLocationResolver(out RegisteredLocationResolver resolver)
{
resolver = default;
EnsureInitialized();
Result rc = LrManager.OpenRegisteredLocationResolver(
out ReferenceCountedDisposable<IRegisteredLocationResolver> baseResolver);
if (rc.IsFailure()) return rc;
using (baseResolver)
{
resolver = new RegisteredLocationResolver(baseResolver);
return Result.Success;
}
}
public Result OpenAddOnContentLocationResolver(out AddOnContentLocationResolver resolver)
{
resolver = default;
EnsureInitialized();
Result rc = LrManager.OpenAddOnContentLocationResolver(
out ReferenceCountedDisposable<IAddOnContentLocationResolver> baseResolver);
if (rc.IsFailure()) return rc;
using (baseResolver)
{
resolver = new AddOnContentLocationResolver(baseResolver);
return Result.Success;
}
}
public Result RefreshLocationResolver(StorageId storageId)
{
EnsureInitialized();
Result rc = LrManager.RefreshLocationResolver(storageId);
if (rc.IsFailure()) return rc;
return Result.Success;
}
private void EnsureInitialized()
{
if (LrManager != null)
return;
lock (_lrInitLocker)
{
if (LrManager != null)
return;
Result rc = Hos.Sm.GetService(out ILocationResolverManager manager, "lr");
if (rc.IsFailure())
{
throw new HorizonResultException(rc, "Failed to initialize lr client.");
}
LrManager = manager;
}
}
public void Dispose()
{
LrManager?.Dispose();
}
}
}

36
src/LibHac/Lr/Path.cs Normal file
View File

@ -0,0 +1,36 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Util;
namespace LibHac.Lr
{
[DebuggerDisplay("{ToString()}")]
[StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax)]
public struct Path
{
#if DEBUG
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100;
[DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200;
#endif
public readonly ReadOnlySpan<byte> Str => SpanHelpers.AsReadOnlyByteSpan(in this);
public Span<byte> StrMutable => SpanHelpers.AsByteSpan(ref this);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InitEmpty(out Path path)
{
Unsafe.SkipInit(out path);
SpanHelpers.AsByteSpan(ref path)[0] = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator U8Span(in Path value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value));
public override readonly string ToString() => StringUtils.Utf8ZToString(Str);
}
}

View File

@ -0,0 +1,50 @@
using System;
using LibHac.Ncm;
namespace LibHac.Lr
{
public class RegisteredLocationResolver : IDisposable
{
private ReferenceCountedDisposable<IRegisteredLocationResolver> _interface;
public RegisteredLocationResolver(ReferenceCountedDisposable<IRegisteredLocationResolver> baseInterface)
{
_interface = baseInterface.AddReference();
}
public Result ResolveProgramPath(out Path path, ProgramId id) =>
_interface.Target.ResolveProgramPath(out path, id);
public Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RegisterProgramPath(in path, id, ownerId);
public Result UnregisterProgramPath(ProgramId id) =>
_interface.Target.UnregisterProgramPath(id);
public Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RedirectProgramPath(in path, id, ownerId);
public Result ResolveHtmlDocumentPath(out Path path, ProgramId id) =>
_interface.Target.ResolveHtmlDocumentPath(out path, id);
public Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) =>
_interface.Target.RegisterHtmlDocumentPath(in path, id, ownerId);
public Result UnregisterHtmlDocumentPath(ProgramId id) =>
_interface.Target.UnregisterHtmlDocumentPath(id);
public Result RedirectHtmlDocumentPath(in Path path, ProgramId id) =>
_interface.Target.RedirectHtmlDocumentPath(in path, id);
public Result Refresh() =>
_interface.Target.Refresh();
public Result RefreshExcluding(ReadOnlySpan<ProgramId> ids) =>
_interface.Target.RefreshExcluding(ids);
public void Dispose()
{
_interface?.Dispose();
}
}
}

39
src/LibHac/Lr/ResultLr.cs Normal file
View File

@ -0,0 +1,39 @@
//-----------------------------------------------------------------------------
// This file was automatically generated.
// Changes to this file will be lost when the file is regenerated.
//
// To change this file, modify /build/CodeGen/results.csv at the root of this
// repo and run the build script.
//
// The script can be run with the "codegen" option to run only the
// code generation portion of the build.
//-----------------------------------------------------------------------------
namespace LibHac.Lr
{
public static class ResultLr
{
public const int ModuleLr = 8;
/// <summary>Error code: 2008-0002; Inner value: 0x408</summary>
public static Result.Base ProgramNotFound => new Result.Base(ModuleLr, 2);
/// <summary>Error code: 2008-0003; Inner value: 0x608</summary>
public static Result.Base DataNotFound => new Result.Base(ModuleLr, 3);
/// <summary>Error code: 2008-0004; Inner value: 0x808</summary>
public static Result.Base UnknownStorageId => new Result.Base(ModuleLr, 4);
/// <summary>Error code: 2008-0005; Inner value: 0xa08</summary>
public static Result.Base LocationResolverNotFound => new Result.Base(ModuleLr, 5);
/// <summary>Error code: 2008-0006; Inner value: 0xc08</summary>
public static Result.Base HtmlDocumentNotFound => new Result.Base(ModuleLr, 6);
/// <summary>Error code: 2008-0007; Inner value: 0xe08</summary>
public static Result.Base AddOnContentNotFound => new Result.Base(ModuleLr, 7);
/// <summary>Error code: 2008-0008; Inner value: 0x1008</summary>
public static Result.Base ControlNotFound => new Result.Base(ModuleLr, 8);
/// <summary>Error code: 2008-0009; Inner value: 0x1208</summary>
public static Result.Base LegalInformationNotFound => new Result.Base(ModuleLr, 9);
/// <summary>Error code: 2008-0010; Inner value: 0x1408</summary>
public static Result.Base DebugProgramNotFound => new Result.Base(ModuleLr, 10);
/// <summary>Error code: 2008-0090; Inner value: 0xb408</summary>
public static Result.Base TooManyRegisteredPaths => new Result.Base(ModuleLr, 90);
}
}

View File

@ -43,12 +43,16 @@ namespace LibHac
public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem)
{
var concatFs = new ConcatenationFileSystem(fileSystem);
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Nintendo/Contents".ToU8String()).ThrowIfFailure();
var contentDirFs = new SubdirectoryFileSystem(concatFs);
contentDirFs.Initialize("/Nintendo/Contents".ToU8String()).ThrowIfFailure();
AesXtsFileSystem encSaveFs = null;
if (fileSystem.DirectoryExists("/Nintendo/save"))
{
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem saveDirFs, concatFs, "/Nintendo/save".ToU8String()).ThrowIfFailure();
var saveDirFs = new SubdirectoryFileSystem(concatFs);
saveDirFs.Initialize("/Nintendo/save".ToU8String()).ThrowIfFailure();
encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000);
}

View File

@ -20,7 +20,9 @@ namespace LibHac.Tests.Fs
baseFs.CreateDirectory("/sub".ToU8Span());
baseFs.CreateDirectory("/sub/path".ToU8Span());
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/sub/path".ToU8String()).ThrowIfFailure();
var subFs = new SubdirectoryFileSystem(baseFs);
subFs.Initialize("/sub/path".ToU8String()).ThrowIfFailure();
return (baseFs, subFs);
}
@ -53,7 +55,8 @@ namespace LibHac.Tests.Fs
{
var baseFs = new InMemoryFileSystem();
SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/".ToU8String()).ThrowIfFailure();
var subFs = new SubdirectoryFileSystem(baseFs);
subFs.Initialize("/".ToU8String()).ThrowIfFailure();
return subFs;
}
}