diff --git a/src/LibHac/Fs/Common/Path.cs b/src/LibHac/Fs/Common/Path.cs index 5a77d858..a316d36c 100644 --- a/src/LibHac/Fs/Common/Path.cs +++ b/src/LibHac/Fs/Common/Path.cs @@ -807,5 +807,64 @@ namespace LibHac.Fs return Result.Success; } + + // Only a small number of format strings are used with these functions, so we can hard code them all easily. + + // /%s + internal static Result SetUpFixedPathSingleEntry(ref Path path, Span pathBuffer, + ReadOnlySpan entryName) + { + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').Append(entryName); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /%016llx + internal static Result SetUpFixedPathSaveId(ref Path path, Span pathBuffer, ulong saveDataId) + { + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /%08x.meta + internal static Result SetUpFixedPathSaveMetaName(ref Path path, Span pathBuffer, uint metaType) + { + ReadOnlySpan metaExtension = new[] { (byte)'.', (byte)'m', (byte)'e', (byte)'t', (byte)'a' }; // ".meta" + + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/').AppendFormat(metaType, 'x', 8).Append(metaExtension); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } + + // /saveMeta/%016llx + internal static Result SetUpFixedPathSaveMetaDir(ref Path path, Span pathBuffer, ulong saveDataId) + { + ReadOnlySpan metaDirectoryName = new[] + { + (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', + (byte)'a', (byte)'/' + }; + + var sb = new U8StringBuilder(pathBuffer); + sb.Append(metaDirectoryName).AppendFormat(saveDataId, 'x', 16); + + if (sb.Overflowed) + return ResultFs.InvalidArgument.Log(); + + return SetUpFixedPath(ref path, pathBuffer); + } } } diff --git a/src/LibHac/FsSrv/BaseFileSystemService.cs b/src/LibHac/FsSrv/BaseFileSystemService.cs index 2ae389a8..eaea33c3 100644 --- a/src/LibHac/FsSrv/BaseFileSystemService.cs +++ b/src/LibHac/FsSrv/BaseFileSystemService.cs @@ -3,6 +3,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; +using LibHac.FsSystem; using LibHac.Sf; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; @@ -73,7 +74,7 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false); return Result.Success; } @@ -83,10 +84,10 @@ namespace LibHac.FsSrv } } - public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable outFileSystem, in FspPath rootPath, BisPartitionId partitionId) { - UnsafeHelpers.SkipParamInit(out fileSystem); + UnsafeHelpers.SkipParamInit(out outFileSystem); Result rc = GetProgramInfo(out ProgramInfo programInfo); if (rc.IsFailure()) return rc; @@ -112,27 +113,44 @@ namespace LibHac.FsSrv if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); - // Normalize the path - using var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty); - if (normalizer.Result.IsFailure()) return normalizer.Result; + const StorageType storageFlag = StorageType.Bis; + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - ReferenceCountedDisposable fs = null; + // Normalize the path + var pathNormalized = new Fs.Path(); + rc = pathNormalized.Initialize(rootPath.Str); + if (rc.IsFailure()) return rc; + + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable baseFileSystem = null; + ReferenceCountedDisposable fileSystem = null; try { // Open the file system - rc = _serviceImpl.OpenBisFileSystem(out fs, normalizer.Path, - partitionId); + rc = _serviceImpl.OpenBisFileSystem(out baseFileSystem, partitionId, false); if (rc.IsFailure()) return rc; + rc = Impl.Utility.CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, in pathNormalized); + if (rc.IsFailure()) return rc; + + // Add all the file system wrappers + fileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref fileSystem, storageFlag); + fileSystem = AsynchronousAccessFileSystem.CreateShared(ref fileSystem); + // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + outFileSystem = FileSystemInterfaceAdapter.CreateShared(ref fileSystem, false); return Result.Success; } finally { - fs?.Dispose(); + baseFileSystem?.Dispose(); + fileSystem?.Dispose(); } } @@ -188,7 +206,7 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false); return Result.Success; } @@ -218,7 +236,7 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false); return Result.Success; } @@ -291,7 +309,7 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs, false); return Result.Success; } diff --git a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs index fad27a33..edc11658 100644 --- a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs @@ -41,21 +41,18 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } - public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId) { - return OpenBisFileSystem(out fileSystem, rootPath, partitionId, false); + return OpenBisFileSystem(out fileSystem, partitionId, false); } - public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId, bool caseSensitive) { - UnsafeHelpers.SkipParamInit(out fileSystem); - - Result rc = _config.BisFileSystemCreator.Create(out IFileSystem fs, rootPath.ToString(), partitionId); + Result rc = _config.BisFileSystemCreator.Create(out fileSystem, partitionId, caseSensitive); if (rc.IsFailure()) return rc; - fileSystem = new ReferenceCountedDisposable(fs); return Result.Success; } @@ -95,14 +92,7 @@ namespace LibHac.FsSrv public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive) { - UnsafeHelpers.SkipParamInit(out fileSystem); - - // Todo: Shared - Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, openCaseSensitive); - if (rc.IsFailure()) return rc; - - fileSystem = new ReferenceCountedDisposable(fs); - return Result.Success; + return _config.SdCardFileSystemCreator.Create(out fileSystem, openCaseSensitive); } public Result FormatSdCardProxyFileSystem() diff --git a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs index 28509a22..9dcad3d4 100644 --- a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -26,118 +27,119 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } - public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable fileSystem, + public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable outFileSystem, CustomStorageId storageId) { - UnsafeHelpers.SkipParamInit(out fileSystem); + UnsafeHelpers.SkipParamInit(out outFileSystem); + ReferenceCountedDisposable fileSystem = null; ReferenceCountedDisposable tempFs = null; - ReferenceCountedDisposable encryptedFs = null; try { - Span path = stackalloc byte[0x40]; + const int pathBufferLength = 0x40; - switch (storageId) + // Hack around error CS8350. + Span buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span pathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); + + if (storageId == CustomStorageId.System) { - case CustomStorageId.SdCard: - { - Result rc = BaseFileSystemService.OpenSdCardProxyFileSystem(out tempFs); - if (rc.IsFailure()) return rc; + Result rc = BaseFileSystemService.OpenBisFileSystem(out fileSystem, BisPartitionId.User); + if (rc.IsFailure()) return rc; - U8Span customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard); - var sb = new U8StringBuilder(path); - sb.Append((byte)'/') - .Append(CommonPaths.SdCardNintendoRootDirectoryName) - .Append((byte)'/') - .Append(customStorageDir); + var path = new Path(); + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/') + .Append(CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System)); - rc = Utility.WrapSubDirectory(out tempFs, ref tempFs, new U8Span(path), true); - if (rc.IsFailure()) return rc; + rc = PathFunctions.SetUpFixedPath(ref path, pathBuffer); + if (rc.IsFailure()) return rc; - rc = FsCreators.EncryptedFileSystemCreator.Create(out encryptedFs, tempFs, - EncryptedFsKeyId.CustomStorage, SdEncryptionSeed); - if (rc.IsFailure()) return rc; + tempFs = Shared.Move(ref fileSystem); + rc = Utility.WrapSubDirectory(out fileSystem, ref tempFs, in path, true); + if (rc.IsFailure()) return rc; - return Result.Success; - } - case CustomStorageId.System: - { - Result rc = BaseFileSystemService.OpenBisFileSystem(out tempFs, U8Span.Empty, - BisPartitionId.User); - if (rc.IsFailure()) return rc; - - U8Span customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System); - var sb = new U8StringBuilder(path); - sb.Append((byte)'/') - .Append(customStorageDir); - - rc = Utility.WrapSubDirectory(out tempFs, ref tempFs, new U8Span(path), true); - if (rc.IsFailure()) return rc; - - fileSystem = Shared.Move(ref tempFs); - return Result.Success; - } - default: - return ResultFs.InvalidArgument.Log(); + path.Dispose(); } - } - finally - { - tempFs?.Dispose(); - encryptedFs?.Dispose(); - } - } - - public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, - bool openCaseSensitive) - { - UnsafeHelpers.SkipParamInit(out fileSystem); - Result rc; - - if (!path.IsEmpty()) - { - rc = Util.VerifyHostPath(path); - if (rc.IsFailure()) return rc; - } - - // Todo: Return shared fs from Create - rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, openCaseSensitive); - if (rc.IsFailure()) return rc; - - ReferenceCountedDisposable sharedHostFs = null; - ReferenceCountedDisposable subDirFs = null; - - try - { - sharedHostFs = new ReferenceCountedDisposable(hostFs); - - if (path.IsEmpty()) + else if (storageId == CustomStorageId.SdCard) { - ReadOnlySpan rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' }; - rc = sharedHostFs.Target.GetEntryType(out _, new U8Span(rootHostPath)); + Result rc = BaseFileSystemService.OpenSdCardProxyFileSystem(out fileSystem); + if (rc.IsFailure()) return rc; - // Nintendo ignores all results other than this one - if (ResultFs.TargetNotFound.Includes(rc)) - return rc; + var path = new Path(); + var sb = new U8StringBuilder(pathBuffer); + sb.Append((byte)'/') + .Append(CommonPaths.SdCardNintendoRootDirectoryName) + .Append((byte)'/') + .Append(CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard)); - Shared.Move(out fileSystem, ref sharedHostFs); - return Result.Success; + rc = PathFunctions.SetUpFixedPath(ref path, pathBuffer); + if (rc.IsFailure()) return rc; + + tempFs = Shared.Move(ref fileSystem); + rc = Utility.WrapSubDirectory(out fileSystem, ref tempFs, in path, true); + if (rc.IsFailure()) return rc; + + tempFs = Shared.Move(ref fileSystem); + rc = FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, ref tempFs, + IEncryptedFileSystemCreator.KeyId.CustomStorage, SdEncryptionSeed); + if (rc.IsFailure()) return rc; + + path.Dispose(); + } + else + { + return ResultFs.InvalidArgument.Log(); } - rc = FsCreators.SubDirectoryFileSystemCreator.Create(out subDirFs, ref sharedHostFs, path, - preserveUnc: true); - if (rc.IsFailure()) return rc; - - fileSystem = subDirFs; + outFileSystem = Shared.Move(ref fileSystem); return Result.Success; } finally { - sharedHostFs?.Dispose(); - subDirFs?.Dispose(); + fileSystem?.Dispose(); + tempFs?.Dispose(); } } + private Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, in Path path) + { + UnsafeHelpers.SkipParamInit(out fileSystem); + + var pathHost = new Path(); + Result rc = pathHost.Initialize(in path); + if (rc.IsFailure()) return rc; + + rc = FsCreators.TargetManagerFileSystemCreator.NormalizeCaseOfPath(out bool isSupported, ref pathHost); + if (rc.IsFailure()) return rc; + + rc = FsCreators.TargetManagerFileSystemCreator.Create(out fileSystem, in pathHost, isSupported, false, + Result.Success); + if (rc.IsFailure()) return rc; + + pathHost.Dispose(); + return Result.Success; + } + + public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + bool openCaseSensitive) + { + if (!path.IsEmpty() && openCaseSensitive) + { + Result rc = OpenHostFileSystem(out fileSystem, in path); + if (rc.IsFailure()) return rc; + } + else + { + Result rc = FsCreators.TargetManagerFileSystemCreator.Create(out fileSystem, in path, openCaseSensitive, + false, Result.Success); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) { SdEncryptionSeed = seed; diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs index 5971771d..ee8812dd 100644 --- a/src/LibHac/FsSrv/FileSystemProxyImpl.cs +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -11,6 +11,8 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IFileSf = LibHac.FsSrv.Sf.IFile; using IStorageSf = LibHac.FsSrv.Sf.IStorage; +using Path = LibHac.Fs.Path; +using static LibHac.Fs.StringTraits; namespace LibHac.FsSrv { @@ -525,25 +527,47 @@ namespace LibHac.FsSrv if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); - ReferenceCountedDisposable hostFs = null; + ReferenceCountedDisposable hostFileSystem = null; try { - rc = FsProxyCore.OpenHostFileSystem(out hostFs, new U8Span(path.Str), - option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive)); + var pathNormalized = new Path(); + + if (path.Str.At(0) == DirectorySeparator && path.Str.At(1) != DirectorySeparator) + { + rc = pathNormalized.Initialize(path.Str.Slice(1)); + if (rc.IsFailure()) return rc; + } + else + { + rc = pathNormalized.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; + } + + var flags = new PathFlags(); + flags.AllowWindowsPath(); + flags.AllowRelativePath(); + flags.AllowEmptyPath(); + + rc = pathNormalized.Normalize(flags); if (rc.IsFailure()) return rc; - bool isRootPath = path.Str[0] == 0; + bool isCaseSensitive = option.Flags.HasFlag(MountHostOptionFlag.PseudoCaseSensitive); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref hostFs, isRootPath); + rc = FsProxyCore.OpenHostFileSystem(out hostFileSystem, in pathNormalized, isCaseSensitive); + if (rc.IsFailure()) return rc; - if (fileSystem is null) - return ResultFs.AllocationMemoryFailedCreateShared.Log(); + var adapterFlags = new PathFlags(); + if (path.Str.At(0) == NullTerminator) + adapterFlags.AllowWindowsPath(); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref hostFileSystem, adapterFlags, false); + + pathNormalized.Dispose(); return Result.Success; } finally { - hostFs?.Dispose(); + hostFileSystem?.Dispose(); } } @@ -878,7 +902,7 @@ namespace LibHac.FsSrv tempFs = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFs, storageFlag); tempFs = AsynchronousAccessFileSystem.CreateShared(ref tempFs); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFs, false); return Result.Success; } @@ -915,7 +939,7 @@ namespace LibHac.FsSrv customFs = StorageLayoutTypeSetFileSystem.CreateShared(ref customFs, storageFlag); customFs = AsynchronousAccessFileSystem.CreateShared(ref customFs); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref customFs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref customFs, false); return Result.Success; } diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index 8a4efc27..4b9f615d 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -32,6 +32,7 @@ namespace LibHac.FsSrv public StorageDeviceManagerFactoryGlobals StorageDeviceManagerFactory; public SaveDataSharedFileStorageGlobals SaveDataSharedFileStorage; public MultiCommitManagerGlobals MultiCommitManager; + public LocationResolverSetGlobals LocationResolverSet; public void Initialize(HorizonClient horizonClient, FileSystemServer fsServer) { @@ -40,6 +41,7 @@ namespace LibHac.FsSrv SaveDataSharedFileStorage.Initialize(fsServer); MultiCommitManager.Initialize(); + LocationResolverSet.Initialize(); } } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs index 9d4d8328..62dcbb99 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedBisFileSystemCreator.cs @@ -54,47 +54,13 @@ namespace LibHac.FsSrv.FsCreator Config = config; } - public Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId) - { - UnsafeHelpers.SkipParamInit(out fileSystem); - - if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); - if (rootPath == null) return ResultFs.NullptrArgument.Log(); - - if (Config.TryGetFileSystem(out fileSystem, partitionId)) - { - return Result.Success; - } - - if (Config.RootFileSystem == null) - { - return ResultFs.PreconditionViolation.Log(); - } - - string partitionPath = GetPartitionPath(partitionId); - - Result rc = - Util.CreateSubFileSystem(out IFileSystem subFileSystem, Config.RootFileSystem, partitionPath, true); - - if (rc.IsFailure()) return rc; - - if (rootPath == string.Empty) - { - fileSystem = subFileSystem; - return Result.Success; - } - - return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath); - } - // Todo: Make case sensitive - public Result Create(out ReferenceCountedDisposable fileSystem, U8Span rootPath, - BisPartitionId partitionId, bool caseSensitive) + public Result Create(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId, + bool caseSensitive) { UnsafeHelpers.SkipParamInit(out fileSystem); if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); - if (rootPath.IsNull()) return ResultFs.NullptrArgument.Log(); if (Config.TryGetFileSystem(out IFileSystem fs, partitionId)) { @@ -107,7 +73,9 @@ namespace LibHac.FsSrv.FsCreator return ResultFs.PreconditionViolation.Log(); } - var partitionPath = GetPartitionPath(partitionId).ToU8String(); + var bisRootPath = new Path(); + Result rc = bisRootPath.Initialize(GetPartitionPath(partitionId).ToU8String()); + if (rc.IsFailure()) return rc; ReferenceCountedDisposable partitionFileSystem = null; ReferenceCountedDisposable sharedRootFs = null; @@ -115,17 +83,11 @@ namespace LibHac.FsSrv.FsCreator { sharedRootFs = new ReferenceCountedDisposable(Config.RootFileSystem); - Result rc = Utility.WrapSubDirectory(out partitionFileSystem, ref sharedRootFs, partitionPath, true); - + rc = Utility.WrapSubDirectory(out partitionFileSystem, ref sharedRootFs, in bisRootPath, true); if (rc.IsFailure()) return rc; - if (rootPath.IsEmpty()) - { - Shared.Move(out fileSystem, ref partitionFileSystem); - return Result.Success; - } - - return Utility.CreateSubDirectoryFileSystem(out fileSystem, ref partitionFileSystem, rootPath); + Shared.Move(out fileSystem, ref partitionFileSystem); + return Result.Success; } finally { @@ -134,13 +96,7 @@ namespace LibHac.FsSrv.FsCreator } } - public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId) - { - UnsafeHelpers.SkipParamInit(out fileSystem); - return ResultFs.NotImplemented.Log(); - } - - public Result SetBisRootForHost(BisPartitionId partitionId, string rootPath) + public Result SetBisRoot(BisPartitionId partitionId, string rootPath) { return Config.SetPath(rootPath, partitionId); } diff --git a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs index 052fc865..6fab0289 100644 --- a/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EmulatedSdCardFileSystemCreator.cs @@ -2,64 +2,98 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.FsSrv.Impl; +using LibHac.Util; namespace LibHac.FsSrv.FsCreator { - public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator + public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator, IDisposable { private const string DefaultPath = "/sdcard"; private EmulatedSdCard SdCard { get; } - private IFileSystem RootFileSystem { get; } + private ReferenceCountedDisposable _rootFileSystem; private string Path { get; } - private IFileSystem SdCardFileSystem { get; set; } + private ReferenceCountedDisposable _sdCardFileSystem; public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem) { SdCard = sdCard; - RootFileSystem = rootFileSystem; + _rootFileSystem = new ReferenceCountedDisposable(rootFileSystem); } public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path) { SdCard = sdCard; - RootFileSystem = rootFileSystem; + _rootFileSystem = new ReferenceCountedDisposable(rootFileSystem); Path = path; } - public Result Create(out IFileSystem fileSystem, bool isCaseSensitive) + public void Dispose() { - UnsafeHelpers.SkipParamInit(out fileSystem); + if (_rootFileSystem is not null) + { + _rootFileSystem.Dispose(); + _rootFileSystem = null; + } + + if (_sdCardFileSystem is not null) + { + _sdCardFileSystem.Dispose(); + _sdCardFileSystem = null; + } + } + + public Result Create(out ReferenceCountedDisposable outFileSystem, bool isCaseSensitive) + { + UnsafeHelpers.SkipParamInit(out outFileSystem); if (!SdCard.IsSdCardInserted()) { return ResultFs.PortSdCardNoDevice.Log(); } - if (SdCardFileSystem != null) + if (_sdCardFileSystem is not null) { - fileSystem = SdCardFileSystem; + outFileSystem = _sdCardFileSystem.AddReference(); return Result.Success; } - if (RootFileSystem == null) + if (_rootFileSystem is null) { return ResultFs.PreconditionViolation.Log(); } string path = Path ?? DefaultPath; - // Todo: Add ProxyFileSystem? - - Result rc = Util.CreateSubFileSystem(out IFileSystem sdFileSystem, RootFileSystem, path, true); + var sdCardPath = new Path(); + Result rc = sdCardPath.Initialize(StringUtils.StringToUtf8(path)); if (rc.IsFailure()) return rc; - SdCardFileSystem = sdFileSystem; - fileSystem = sdFileSystem; + var pathFlags = new PathFlags(); + pathFlags.AllowEmptyPath(); + rc = sdCardPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; - return Result.Success; + // Todo: Add ProxyFileSystem? + + ReferenceCountedDisposable tempFs = null; + try + { + tempFs = _rootFileSystem.AddReference(); + rc = Utility.CreateSubDirectoryFileSystem(out _sdCardFileSystem, ref tempFs, in sdCardPath); + if (rc.IsFailure()) return rc; + + outFileSystem = _sdCardFileSystem.AddReference(); + + return Result.Success; + } + finally + { + tempFs?.Dispose(); + } } public Result Format(bool removeFromFatFsCache) diff --git a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs index 8a3a4083..638ca9f9 100644 --- a/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/EncryptedFileSystemCreator.cs @@ -3,6 +3,7 @@ using LibHac.Common.Keys; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; +using static LibHac.FsSrv.FsCreator.IEncryptedFileSystemCreator; namespace LibHac.FsSrv.FsCreator { @@ -15,12 +16,13 @@ namespace LibHac.FsSrv.FsCreator KeySet = keySet; } - public Result Create(out ReferenceCountedDisposable encryptedFileSystem, ReferenceCountedDisposable baseFileSystem, - EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed) + public Result Create(out ReferenceCountedDisposable encryptedFileSystem, + ref ReferenceCountedDisposable baseFileSystem, KeyId idIndex, + in EncryptionSeed encryptionSeed) { UnsafeHelpers.SkipParamInit(out encryptedFileSystem); - if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage) + if (idIndex < KeyId.Save || idIndex > KeyId.CustomStorage) { return ResultFs.InvalidArgument.Log(); } @@ -29,7 +31,8 @@ namespace LibHac.FsSrv.FsCreator KeySet.SetSdSeed(encryptionSeed.Value); // Todo: pass ReferenceCountedDisposable to AesXtsFileSystem - var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000); + var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)idIndex].DataRo.ToArray(), + 0x4000); encryptedFileSystem = new ReferenceCountedDisposable(fs); return Result.Success; diff --git a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs index dc7782df..c61326b8 100644 --- a/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IBuiltInStorageFileSystemCreator.cs @@ -1,15 +1,10 @@ -using LibHac.Common; -using LibHac.Fs; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.FsCreator { public interface IBuiltInStorageFileSystemCreator { - // Todo: Remove raw IFileSystem overload - Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId); - Result Create(out ReferenceCountedDisposable fileSystem, U8Span rootPath, BisPartitionId partitionId, bool caseSensitive); - Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId); - Result SetBisRootForHost(BisPartitionId partitionId, string rootPath); + Result Create(out ReferenceCountedDisposable fileSystem, BisPartitionId partitionId, bool caseSensitive); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs index a8337b05..aaed2322 100644 --- a/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/IEncryptedFileSystemCreator.cs @@ -5,8 +5,15 @@ namespace LibHac.FsSrv.FsCreator { public interface IEncryptedFileSystemCreator { + public enum KeyId + { + Save = 0, + Content = 1, + CustomStorage = 2 + } + Result Create(out ReferenceCountedDisposable encryptedFileSystem, - ReferenceCountedDisposable baseFileSystem, EncryptedFsKeyId keyId, + ref ReferenceCountedDisposable baseFileSystem, KeyId idIndex, in EncryptionSeed encryptionSeed); } diff --git a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs index ecd20873..42acf186 100644 --- a/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISdCardProxyFileSystemCreator.cs @@ -4,7 +4,7 @@ namespace LibHac.FsSrv.FsCreator { public interface ISdCardProxyFileSystemCreator { - Result Create(out IFileSystem fileSystem, bool isCaseSensitive); + Result Create(out ReferenceCountedDisposable outFileSystem, bool isCaseSensitive); /// /// Formats the SD card. diff --git a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs index 9e4c4139..42fc19ef 100644 --- a/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISubDirectoryFileSystemCreator.cs @@ -1,11 +1,10 @@ -using LibHac.Common; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.FsCreator { public interface ISubDirectoryFileSystemCreator { - Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path); - Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc); + Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, in Path path); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs index 63c300e6..f1570bc4 100644 --- a/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ITargetManagerFileSystemCreator.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.FsCreator @@ -7,7 +8,7 @@ namespace LibHac.FsSrv.FsCreator { // Todo: Remove raw IFilesystem function Result Create(out IFileSystem fileSystem, bool openCaseSensitive); - Result Create(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive); - Result NormalizeCaseOfPath(out bool isSupported, Span path); + Result Create(out ReferenceCountedDisposable fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult); + Result NormalizeCaseOfPath(out bool isSupported, ref Path path); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs index fa8f1f9f..83754417 100644 --- a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Common.Keys; using LibHac.Diag; @@ -42,15 +43,20 @@ namespace LibHac.FsSrv.FsCreator bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, ISaveDataCommitTimeStampGetter timeStampGetter) { - Span saveImageName = stackalloc byte[0x12]; + // Hack around error CS8350. + Span buffer = stackalloc byte[0x12]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, 0x12); + UnsafeHelpers.SkipParamInit(out fileSystem, out extraDataAccessor); Assert.SdkRequiresNotNull(cacheManager); - var sb = new U8StringBuilder(saveImageName); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + var saveImageName = new Path(); + Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; - Result rc = baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, new U8Span(saveImageName)); + rc = baseFileSystem.Target.GetEntryType(out DirectoryEntryType type, in saveImageName); if (rc.IsFailure()) { @@ -68,7 +74,7 @@ namespace LibHac.FsSrv.FsCreator { subDirFs = new SubdirectoryFileSystem(ref baseFileSystem); - rc = subDirFs.Initialize(new U8Span(saveImageName)); + rc = subDirFs.Initialize(in saveImageName); if (rc.IsFailure()) return rc; saveFs = DirectorySaveDataFileSystem.CreateShared(Shared.Move(ref subDirFs), _fsServer.Hos.Fs); @@ -80,6 +86,7 @@ namespace LibHac.FsSrv.FsCreator fileSystem = saveFs.AddReference(); extraDataAccessor = saveFs.AddReference(); + saveImageName.Dispose(); return Result.Success; } finally diff --git a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs index 16e2afa6..e09ceda0 100644 --- a/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SubDirectoryFileSystemCreator.cs @@ -1,4 +1,5 @@ using LibHac.Common; +using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; @@ -7,29 +8,31 @@ namespace LibHac.FsSrv.FsCreator public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator { public Result Create(out ReferenceCountedDisposable subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path) - { - return Create(out subDirFileSystem, ref baseFileSystem, path, false); - } - - public Result Create(out ReferenceCountedDisposable subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc) + ref ReferenceCountedDisposable baseFileSystem, in Path path) { UnsafeHelpers.SkipParamInit(out subDirFileSystem); - // Verify the sub-path exists - Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); + Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, in path, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; - // Initialize the SubdirectoryFileSystem - var subDir = new SubdirectoryFileSystem(ref baseFileSystem, preserveUnc); - using var subDirShared = new ReferenceCountedDisposable(subDir); + dir.Dispose(); - rc = subDirShared.Target.Initialize(path); - if (rc.IsFailure()) return rc; + ReferenceCountedDisposable subFs = null; + try + { + subFs = new ReferenceCountedDisposable( + new SubdirectoryFileSystem(ref baseFileSystem)); - subDirFileSystem = subDirShared.AddReference(); - return Result.Success; + rc = subFs.Target.Initialize(in path); + if (rc.IsFailure()) return rc; + + subDirFileSystem = subFs.AddReference(); + return Result.Success; + } + finally + { + subFs?.Dispose(); + } } } } diff --git a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs index 653fbacb..a8da0cf1 100644 --- a/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/TargetManagerFileSystemCreator.cs @@ -1,4 +1,5 @@ using System; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.FsCreator @@ -10,12 +11,12 @@ namespace LibHac.FsSrv.FsCreator throw new NotImplementedException(); } - public Result Create(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive) + public Result Create(out ReferenceCountedDisposable fileSystem, in Path rootPath, bool openCaseSensitive, bool ensureRootPathExists, Result pathNotFoundResult) { throw new NotImplementedException(); } - public Result NormalizeCaseOfPath(out bool isSupported, Span path) + public Result NormalizeCaseOfPath(out bool isSupported, ref Path path) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs index 74eaccf8..2c4938e0 100644 --- a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -283,8 +283,8 @@ namespace LibHac.FsSrv.Impl } } - public static ReferenceCountedDisposable CreateShared(PathFlags flags, - ref ReferenceCountedDisposable baseFileSystem, bool allowAllOperations) + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, PathFlags flags, bool allowAllOperations) { ReferenceCountedDisposable sharedAdapter = null; try diff --git a/src/LibHac/FsSrv/Impl/Utility.cs b/src/LibHac/FsSrv/Impl/Utility.cs index bc1ac103..352d9f89 100644 --- a/src/LibHac/FsSrv/Impl/Utility.cs +++ b/src/LibHac/FsSrv/Impl/Utility.cs @@ -1,8 +1,8 @@ using System; using LibHac.Common; -using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSystem; using LibHac.Util; @@ -10,130 +10,24 @@ namespace LibHac.FsSrv.Impl { internal static class Utility { - public static Result EnsureDirectory(IFileSystem fileSystem, U8Span path) + public static bool IsHostFsMountName(ReadOnlySpan name) { - FsPath.FromSpan(out FsPath pathBuffer, path.Value).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 path) - { - // Double check the trailing separators have been trimmed - Assert.SdkRequires(path.Length <= 1 || 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 path) - { - // The path should not be empty or have a trailing directory separator - Assert.SdkRequiresLess(0, path.Length); - Assert.SdkRequiresNotEqual(path[path.Length - 1], StringTraits.DirectorySeparator); - - // 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 - 1; - while (length > 0 && path[length] != StringTraits.DirectorySeparator) - { - length--; - } - - if (length == 0) - { - // We hit the beginning of the path. Ensure the root directory exists and return - return EnsureDirectoryImpl(fileSystem, Span.Empty); - } - - // We found the length of the parent directory. Ensure it exists - path[length] = StringTraits.NullTerminator; - Result rc = EnsureDirectoryImpl(fileSystem, path.Slice(0, length)); - if (rc.IsFailure()) return rc; - - // Restore the separator - path[length] = StringTraits.DirectorySeparator; - return Result.Success; + return StringUtils.Compare(name, CommonMountNames.HostRootFileSystemMountName) == 0; } public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable subDirFileSystem, - ref ReferenceCountedDisposable baseFileSystem, in Path subPath) + ref ReferenceCountedDisposable baseFileSystem, in Path rootPath) { UnsafeHelpers.SkipParamInit(out subDirFileSystem); - if (subPath.IsEmpty()) + if (rootPath.IsEmpty()) { subDirFileSystem = Shared.Move(ref baseFileSystem); return Result.Success; } // Check if the directory exists - Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, subPath, OpenDirectoryMode.Directory); + Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, rootPath, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; dir.Dispose(); @@ -141,7 +35,7 @@ namespace LibHac.FsSrv.Impl var fs = new SubdirectoryFileSystem(ref baseFileSystem); using (var subDirFs = new ReferenceCountedDisposable(fs)) { - rc = subDirFs.Target.Initialize(in subPath); + rc = subDirFs.Target.Initialize(in rootPath); if (rc.IsFailure()) return rc; subDirFileSystem = subDirFs.AddReference(); @@ -150,22 +44,22 @@ namespace LibHac.FsSrv.Impl } public static Result WrapSubDirectory(out ReferenceCountedDisposable fileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool createIfMissing) + ref ReferenceCountedDisposable baseFileSystem, in Path rootPath, bool createIfMissing) { UnsafeHelpers.SkipParamInit(out fileSystem); // The path must already exist if we're not automatically creating it if (!createIfMissing) { - Result result = baseFileSystem.Target.GetEntryType(out _, path); + Result result = baseFileSystem.Target.GetEntryType(out _, in rootPath); if (result.IsFailure()) return result; } // Ensure the path exists or check if it's a directory - Result rc = EnsureDirectory(baseFileSystem.Target, path); + Result rc = Utility12.EnsureDirectory(baseFileSystem.Target, in rootPath); if (rc.IsFailure()) return rc; - return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, path); + return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, rootPath); } public static long ConvertZeroCommitId(in SaveDataExtraData extraData) @@ -178,11 +72,5 @@ namespace LibHac.FsSrv.Impl Crypto.Sha256.GenerateSha256Hash(SpanHelpers.AsReadOnlyByteSpan(in extraData), hash); return BitConverter.ToInt64(hash); } - - private static ReadOnlySpan FileSystemRootPath => // / - new[] - { - (byte) '/' - }; } } diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs index 7e76ff98..4e306fa1 100644 --- a/src/LibHac/FsSrv/NcaFileSystemService.cs +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -1,7 +1,6 @@ using System; using System.Runtime.CompilerServices; using LibHac.Common; -using LibHac.Diag; using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; @@ -14,8 +13,9 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IStorage = LibHac.Fs.IStorage; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IStorageSf = LibHac.FsSrv.Sf.IStorage; -using Path = LibHac.Lr.Path; +using Path = LibHac.Fs.Path; using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer; +using Utility = LibHac.FsSrv.Impl.Utility; namespace LibHac.FsSrv { @@ -72,10 +72,17 @@ namespace LibHac.FsSrv if (fsType != FileSystemProxyType.Manual) { - if (fsType == FileSystemProxyType.Logo || fsType == FileSystemProxyType.Control) - return ResultFs.NotImplemented.Log(); - else - return ResultFs.InvalidArgument.Log(); + switch (fsType) + { + case FileSystemProxyType.Logo: + case FileSystemProxyType.Control: + case FileSystemProxyType.Meta: + case FileSystemProxyType.Data: + case FileSystemProxyType.Package: + return ResultFs.NotImplemented.Log(); + default: + return ResultFs.InvalidArgument.Log(); + } } Accessibility accessibility = @@ -89,28 +96,17 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Try to find the path to the original version of the file system - Result originalResult = ServiceImpl.ResolveApplicationHtmlDocumentPath(out Path originalPath, - new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId); + var originalPath = new Fs.Path(); + Result originalResult = ServiceImpl.ResolveApplicationHtmlDocumentPath(out bool isDirectory, + ref originalPath, new Ncm.ApplicationId(programId.Value), ownerProgramInfo.StorageId); // The file system might have a patch version with no original version, so continue if not found if (originalResult.IsFailure() && !ResultLr.HtmlDocumentNotFound.Includes(originalResult)) return originalResult; - // Use a separate bool because ref structs can't be used as type parameters - bool originalPathNormalizerHasValue = false; - PathNormalizer originalPathNormalizer = default; - - // Normalize the original version path if found - if (originalResult.IsSuccess()) - { - originalPathNormalizer = new PathNormalizer(originalPath, GetPathNormalizerOptions(originalPath)); - if (originalPathNormalizer.Result.IsFailure()) return originalPathNormalizer.Result; - - originalPathNormalizerHasValue = true; - } - // Try to find the path to the patch file system - Result patchResult = ServiceImpl.ResolveRegisteredHtmlDocumentPath(out Path patchPath, programId.Value); + var patchPath = new Fs.Path(); + Result patchResult = ServiceImpl.ResolveRegisteredHtmlDocumentPath(ref patchPath, programId.Value); ReferenceCountedDisposable tempFileSystem = null; ReferenceCountedDisposable accessFailureManager = null; @@ -122,37 +118,25 @@ namespace LibHac.FsSrv if (originalResult.IsFailure()) return originalResult; - Assert.SdkAssert(originalPathNormalizerHasValue); - // There is an original version and no patch version. Open the original directly - rc = ServiceImpl.OpenFileSystem(out tempFileSystem, originalPathNormalizer.Path, fsType, programId.Value); + rc = ServiceImpl.OpenFileSystem(out tempFileSystem, in originalPath, fsType, programId.Value, + isDirectory); if (rc.IsFailure()) return rc; } else { - // Get the normalized path to the original file system - U8Span normalizedOriginalPath; - if (originalPathNormalizerHasValue) - { - normalizedOriginalPath = originalPathNormalizer.Path; - } - else - { - normalizedOriginalPath = U8Span.Empty; - } - - // Normalize the path to the patch file system - using var patchPathNormalizer = new PathNormalizer(patchPath, GetPathNormalizerOptions(patchPath)); - if (patchPathNormalizer.Result.IsFailure()) return patchPathNormalizer.Result; - if (patchResult.IsFailure()) return patchResult; - U8Span normalizedPatchPath = patchPathNormalizer.Path; + var emptyPath = new Fs.Path(); + rc = emptyPath.InitializeAsEmpty(); + if (rc.IsFailure()) return rc; + + ref Fs.Path originalNcaPath = ref originalResult.IsSuccess() ? ref originalPath : ref emptyPath; // Open the file system using both the original and patch versions - rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, normalizedOriginalPath, - normalizedPatchPath, fsType, programId.Value); + rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, in originalNcaPath, in patchPath, + fsType, programId.Value); if (rc.IsFailure()) return rc; } @@ -163,7 +147,7 @@ namespace LibHac.FsSrv accessFailureManager = SelfReference.AddReference(); tempFileSystem = DeepRetryFileSystem.CreateShared(ref tempFileSystem, ref accessFailureManager); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false); return Result.Success; } finally @@ -200,10 +184,10 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } - public Result OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in FspPath path, + public Result OpenFileSystemWithId(out ReferenceCountedDisposable outFileSystem, in FspPath path, ulong id, FileSystemProxyType fsType) { - UnsafeHelpers.SkipParamInit(out fileSystem); + UnsafeHelpers.SkipParamInit(out outFileSystem); Result rc = GetProgramInfo(out ProgramInfo programInfo); if (rc.IsFailure()) return rc; @@ -251,25 +235,34 @@ namespace LibHac.FsSrv bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; - using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); - if (normalizer.Result.IsFailure()) return normalizer.Result; + var pathNormalized = new Fs.Path(); + rc = pathNormalized.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; - ReferenceCountedDisposable fs = null; + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowMountName(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + bool isDirectory = PathUtility12.IsDirectoryPath(in path); + + ReferenceCountedDisposable fileSystem = null; try { - rc = ServiceImpl.OpenFileSystem(out fs, out _, path, fsType, - canMountSystemDataPrivate, id); + rc = ServiceImpl.OpenFileSystem(out fileSystem, in pathNormalized, fsType, canMountSystemDataPrivate, + id, isDirectory); if (rc.IsFailure()) return rc; // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + outFileSystem = FileSystemInterfaceAdapter.CreateShared(ref fileSystem, false); return Result.Success; } finally { - fs?.Dispose(); + fileSystem?.Dispose(); } } @@ -316,7 +309,7 @@ namespace LibHac.FsSrv if (tempFileSystem is null) return ResultFs.AllocationMemoryFailedAllocateShared.Log(); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false); if (fileSystem is null) return ResultFs.AllocationMemoryFailedCreateShared.Log(); @@ -334,11 +327,11 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } - public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) + public Result GetRightsId(out RightsId outRightsId, ProgramId programId, StorageId storageId) { - UnsafeHelpers.SkipParamInit(out rightsId); + UnsafeHelpers.SkipParamInit(out outRightsId); - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); Result rc = GetProgramInfo(out ProgramInfo programInfo); if (rc.IsFailure()) return rc; @@ -346,24 +339,27 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) return ResultFs.PermissionDenied.Log(); - rc = ServiceImpl.ResolveProgramPath(out Path programPath, programId, storageId); + var programPath = new Path(); + rc = ServiceImpl.ResolveProgramPath(out bool isDirectory, ref programPath, programId, storageId); if (rc.IsFailure()) return rc; - using var normalizer = new PathNormalizer(programPath, GetPathNormalizerOptions(programPath)); - if (normalizer.Result.IsFailure()) return normalizer.Result; + if (isDirectory) + return ResultFs.TargetNotFound.Log(); - rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out _, normalizer.Path, programId); + rc = ServiceImpl.GetRightsId(out RightsId rightsId, out _, in programPath, programId); if (rc.IsFailure()) return rc; - rightsId = tempRightsId; + outRightsId = rightsId; + + programPath.Dispose(); return Result.Success; } - public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path) + public Result GetRightsIdAndKeyGenerationByPath(out RightsId outRightsId, out byte outKeyGeneration, in FspPath path) { - UnsafeHelpers.SkipParamInit(out rightsId, out keyGeneration); + UnsafeHelpers.SkipParamInit(out outRightsId, out outKeyGeneration); - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All); + using var scopedContext = new ScopedStorageLayoutTypeSetter(StorageType.All); Result rc = GetProgramInfo(out ProgramInfo programInfo); if (rc.IsFailure()) return rc; @@ -371,52 +367,61 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) return ResultFs.PermissionDenied.Log(); - using var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); - if (normalizer.Result.IsFailure()) return normalizer.Result; + var pathNormalized = new Path(); + rc = pathNormalized.Initialize(path.Str); + if (rc.IsFailure()) return rc; - rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out byte tempKeyGeneration, normalizer.Path, + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowMountName(); + rc = pathNormalized.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + if (PathUtility12.IsDirectoryPath(in path)) + return ResultFs.TargetNotFound.Log(); + + rc = ServiceImpl.GetRightsId(out RightsId rightsId, out byte keyGeneration, in pathNormalized, new ProgramId(ulong.MaxValue)); if (rc.IsFailure()) return rc; - rightsId = tempRightsId; - keyGeneration = tempKeyGeneration; + outRightsId = rightsId; + outKeyGeneration = keyGeneration; + + pathNormalized.Dispose(); return Result.Success; } - private Result OpenDataFileSystemCore(out ReferenceCountedDisposable fileSystem, out bool isHostFs, + private Result OpenDataFileSystemCore(out ReferenceCountedDisposable outFileSystem, out bool isHostFs, ulong programId, StorageId storageId) { - UnsafeHelpers.SkipParamInit(out fileSystem, out isHostFs); + UnsafeHelpers.SkipParamInit(out outFileSystem, out isHostFs); if (Unsafe.IsNullRef(ref isHostFs)) return ResultFs.NullptrArgument.Log(); StorageType storageFlag = ServiceImpl.GetStorageFlag(programId); - using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + using var scopedContext = new ScopedStorageLayoutTypeSetter(storageFlag); - Result rc = ServiceImpl.ResolveRomPath(out Path romPath, programId, storageId); + var programPath = new Path(); + Result rc = ServiceImpl.ResolveRomPath(out bool isDirectory, ref programPath, programId, storageId); if (rc.IsFailure()) return rc; - using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath)); - if (normalizer.Result.IsFailure()) return normalizer.Result; + isHostFs = Utility.IsHostFsMountName(programPath.GetString()); - isHostFs = IsHostFs(romPath); - - ReferenceCountedDisposable tempFileSystem = null; + ReferenceCountedDisposable fileSystem = null; try { - rc = ServiceImpl.OpenDataFileSystem(out tempFileSystem, normalizer.Path, FileSystemProxyType.Rom, - programId); + rc = ServiceImpl.OpenDataFileSystem(out fileSystem, in programPath, FileSystemProxyType.Rom, programId, isDirectory); if (rc.IsFailure()) return rc; - tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + fileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref fileSystem, storageFlag); - Shared.Move(out fileSystem, ref tempFileSystem); + outFileSystem = Shared.Move(ref fileSystem); return Result.Success; } finally { - tempFileSystem?.Dispose(); + fileSystem?.Dispose(); } } @@ -448,7 +453,7 @@ namespace LibHac.FsSrv tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); // Create an SF adapter for the file system - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false); return Result.Success; } @@ -499,13 +504,11 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.RegisterUpdatePartition)) return ResultFs.PermissionDenied.Log(); - rc = ServiceImpl.ResolveRomPath(out Path romPath, programInfo.ProgramIdValue, programInfo.StorageId); + var programPath = new Path(); + rc = ServiceImpl.ResolveRomPath(out _, ref programPath, programInfo.ProgramIdValue, programInfo.StorageId); if (rc.IsFailure()) return rc; - using var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath)); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, normalizer.Path); + return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, in programPath); } public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem) @@ -537,7 +540,7 @@ namespace LibHac.FsSrv if (tempFileSystem is null) return ResultFs.AllocationMemoryFailedAllocateShared.Log(); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, false); if (fileSystem is null) return ResultFs.AllocationMemoryFailedCreateShared.Log(); @@ -620,21 +623,6 @@ namespace LibHac.FsSrv return ServiceImpl.GetProgramInfoByProgramId(out programInfo, programId); } - 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.PreserveTrailingSeparator | hostOption; - } - - private bool IsHostFs(U8Span path) - { - int hostMountLength = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName, - PathTools.MountNameLengthMax); - - return StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountLength) == 0; - } - Result IRomFileSystemAccessFailureManager.OpenDataStorageCore(out ReferenceCountedDisposable storage, out Hash ncaHeaderDigest, ulong id, StorageId storageId) { diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs index 57c61849..e4e95d53 100644 --- a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -1,6 +1,7 @@ using System; using System.Buffers.Text; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; @@ -13,8 +14,6 @@ using LibHac.Os; using LibHac.Spl; using LibHac.Util; using RightsId = LibHac.Fs.RightsId; -using Utility = LibHac.FsSrv.Impl.Utility; -using PathLr = LibHac.Lr.Path; namespace LibHac.FsSrv { @@ -65,15 +64,23 @@ namespace LibHac.FsSrv public bool CanMountNca; } - public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, - FileSystemProxyType type, ulong id) + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType type, ulong id, bool isDirectory) { - return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), path, type, false, id); + return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), in path, type, false, id, + isDirectory); + } + + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType type, bool canMountSystemDataPrivate, ulong id, bool isDirectory) + { + return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), in path, type, + canMountSystemDataPrivate, id, isDirectory); } public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, - out CodeVerificationData verificationData, U8Span path, FileSystemProxyType type, - bool canMountSystemDataPrivate, ulong id) + out CodeVerificationData verificationData, in Path path, FileSystemProxyType type, + bool canMountSystemDataPrivate, ulong id, bool isDirectory) { UnsafeHelpers.SkipParamInit(out fileSystem, out verificationData); @@ -81,7 +88,7 @@ namespace LibHac.FsSrv 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)); + var currentPath = new U8Span(path.GetString()); // Open the root filesystem based on the path's mount name Result rc = ParseMountName(ref currentPath, @@ -106,7 +113,7 @@ namespace LibHac.FsSrv return rc; } - rc = CheckDirOrNcaOrNsp(ref currentPath, out bool isDirectory); + rc = CheckDirOrNcaOrNsp(ref currentPath, out isDirectory); if (rc.IsFailure()) return rc; if (isDirectory) @@ -114,17 +121,21 @@ namespace LibHac.FsSrv if (!mountNameInfo.IsHostFs) return ResultFs.PermissionDenied.Log(); + var directoryPath = new Path(); + rc = directoryPath.InitializeWithNormalization(currentPath.Value); + if (rc.IsFailure()) return rc; + if (type == FileSystemProxyType.Manual) { - ReferenceCountedDisposable manualFileSystem = null; + ReferenceCountedDisposable hostFileSystem = null; ReferenceCountedDisposable readOnlyFileSystem = null; try { - rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(out manualFileSystem, ref baseFileSystem, - currentPath); + rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(out hostFileSystem, + in directoryPath); if (rc.IsFailure()) return rc; - readOnlyFileSystem = ReadOnlyFileSystem.CreateShared(ref manualFileSystem); + readOnlyFileSystem = ReadOnlyFileSystem.CreateShared(ref hostFileSystem); if (readOnlyFileSystem?.Target is null) return ResultFs.AllocationMemoryFailedAllocateShared.Log(); @@ -133,12 +144,12 @@ namespace LibHac.FsSrv } finally { - manualFileSystem?.Dispose(); + hostFileSystem?.Dispose(); readOnlyFileSystem?.Dispose(); } } - return ParseDir(currentPath, out fileSystem, ref baseFileSystem, type, true); + return ParseDir(in directoryPath, out fileSystem, ref baseFileSystem, type, true); } rc = ParseNsp(ref currentPath, out ReferenceCountedDisposable nspFileSystem, baseFileSystem); @@ -146,7 +157,7 @@ namespace LibHac.FsSrv if (rc.IsSuccess()) { // Must be the end of the path to open Application Package FS type - if (currentPath.Length == 0 || currentPath[0] == 0) + if (currentPath.Value.At(0) == 0) { if (type == FileSystemProxyType.Package) { @@ -185,28 +196,28 @@ namespace LibHac.FsSrv } } - public Result OpenDataFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, - FileSystemProxyType fsType, ulong programId) + public Result OpenDataFileSystem(out ReferenceCountedDisposable fileSystem, in Path path, + FileSystemProxyType fsType, ulong programId, bool isDirectory) { throw new NotImplementedException(); } public Result OpenStorageWithPatch(out ReferenceCountedDisposable storage, out Hash ncaHeaderDigest, - U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id) + in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) { throw new NotImplementedException(); } public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, - U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id) + in Path originalNcaPath, in Path currentNcaPath, FileSystemProxyType fsType, ulong id) { UnsafeHelpers.SkipParamInit(out fileSystem); ReferenceCountedDisposable romFsStorage = null; try { - Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef(), originalNcaPath, - currentNcaPath, fsType, id); + Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef(), in originalNcaPath, + in currentNcaPath, fsType, id); if (rc.IsFailure()) return rc; return _config.RomFsCreator.Create(out fileSystem, romFsStorage); @@ -220,13 +231,14 @@ namespace LibHac.FsSrv public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, ContentStorageId contentStorageId) { - const int storagePathMaxLen = 0x40; + const int pathBufferLength = 0x40; UnsafeHelpers.SkipParamInit(out fileSystem); ReferenceCountedDisposable baseFileSystem = null; ReferenceCountedDisposable subDirFileSystem = null; - ReferenceCountedDisposable encryptedFileSystem = null; + ReferenceCountedDisposable tempFileSystem = null; + Path contentStoragePath = default; try { Result rc; @@ -235,62 +247,62 @@ namespace LibHac.FsSrv switch (contentStorageId) { case ContentStorageId.System: - rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty, - BisPartitionId.System); + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.System); + if (rc.IsFailure()) return rc; break; case ContentStorageId.User: - rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty, - BisPartitionId.User); + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.User); + if (rc.IsFailure()) return rc; break; case ContentStorageId.SdCard: rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem); + if (rc.IsFailure()) return rc; break; default: - rc = ResultFs.InvalidArgument.Log(); - break; + return ResultFs.InvalidArgument.Log(); } - if (rc.IsFailure()) return rc; + // Hack around error CS8350. + Span buffer = stackalloc byte[pathBufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span contentStoragePathBuffer = MemoryMarshal.CreateSpan(ref bufferRef, pathBufferLength); // Build the appropriate path for the content storage ID - Span contentStoragePath = stackalloc byte[storagePathMaxLen]; - if (contentStorageId == ContentStorageId.SdCard) { - var sb = new U8StringBuilder(contentStoragePath); + var sb = new U8StringBuilder(contentStoragePathBuffer); sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName); sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); } else { - var sb = new U8StringBuilder(contentStoragePath); + var sb = new U8StringBuilder(contentStoragePathBuffer); sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); } + contentStoragePath = new Path(); + rc = PathFunctions.SetUpFixedPath(ref contentStoragePath, contentStoragePathBuffer); + if (rc.IsFailure()) return rc; + // Make sure the content storage path exists - // ReSharper disable once PossibleNullReferenceException - rc = Utility.EnsureDirectory(baseFileSystem.Target, new U8Span(contentStoragePath)); + rc = Utility12.EnsureDirectory(baseFileSystem.Target, in contentStoragePath); if (rc.IsFailure()) return rc; rc = _config.SubDirectoryFsCreator.Create(out subDirFileSystem, ref baseFileSystem, - new U8Span(contentStoragePath)); + in contentStoragePath); if (rc.IsFailure()) return rc; // Only content on the SD card is encrypted - if (contentStorageId != ContentStorageId.SdCard) + if (contentStorageId == ContentStorageId.SdCard) { - // Move the shared reference to the out variable - Shared.Move(out fileSystem, ref subDirFileSystem); + tempFileSystem = Shared.Move(ref subDirFileSystem); - return Result.Success; + rc = _config.EncryptedFsCreator.Create(out subDirFileSystem, ref tempFileSystem, + IEncryptedFileSystemCreator.KeyId.Content, in _encryptionSeed); + if (rc.IsFailure()) return rc; } - // 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; - - Shared.Move(out fileSystem, ref encryptedFileSystem); + Shared.Move(out fileSystem, ref subDirFileSystem); return Result.Success; } @@ -298,11 +310,12 @@ namespace LibHac.FsSrv { baseFileSystem?.Dispose(); subDirFileSystem?.Dispose(); - encryptedFileSystem?.Dispose(); + tempFileSystem?.Dispose(); + contentStoragePath.Dispose(); } } - public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, U8Span path, ProgramId programId) + public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, in Path path, ProgramId programId) { throw new NotImplementedException(); } @@ -326,7 +339,7 @@ namespace LibHac.FsSrv return Result.Success; } - public Result RegisterUpdatePartition(ulong programId, U8Span path) + public Result RegisterUpdatePartition(ulong programId, in Path path) { throw new NotImplementedException(); } @@ -423,8 +436,7 @@ namespace LibHac.FsSrv { path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length); - Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, - BisPartitionId.CalibrationFile); + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.CalibrationFile); if (rc.IsFailure()) return rc; } @@ -433,8 +445,7 @@ namespace LibHac.FsSrv { path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length); - Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, - BisPartitionId.SafeMode); + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.SafeMode); if (rc.IsFailure()) return rc; } @@ -443,7 +454,7 @@ namespace LibHac.FsSrv { path = path.Slice(CommonPaths.BisUserPartitionMountName.Length); - Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, BisPartitionId.User); + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.User); if (rc.IsFailure()) return rc; } @@ -452,8 +463,7 @@ namespace LibHac.FsSrv { path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length); - Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, - BisPartitionId.System); + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, BisPartitionId.System); if (rc.IsFailure()) return rc; } @@ -471,10 +481,14 @@ namespace LibHac.FsSrv { path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length); + var rootPathEmpty = new Path(); + Result rc = rootPathEmpty.InitializeAsEmpty(); + if (rc.IsFailure()) return rc; + info.IsHostFs = true; info.CanMountNca = true; - Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false); + rc = OpenHostFileSystem(out fileSystem, in rootPathEmpty, openCaseSensitive: false); if (rc.IsFailure()) return rc; } @@ -540,7 +554,7 @@ namespace LibHac.FsSrv return ResultFs.PathNotFound.Log(); } - private Result ParseDir(U8Span path, + private Result ParseDir(in Path path, out ReferenceCountedDisposable contentFileSystem, ref ReferenceCountedDisposable baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) { @@ -549,7 +563,7 @@ namespace LibHac.FsSrv ReferenceCountedDisposable subDirFs = null; try { - Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, path, preserveUnc); + Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, in path); if (rc.IsFailure()) return rc; return ParseContentTypeForDirectory(out contentFileSystem, ref subDirFs, fsType); @@ -561,32 +575,29 @@ namespace LibHac.FsSrv } private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs( - out ReferenceCountedDisposable contentFileSystem, - ref ReferenceCountedDisposable baseFileSystem, U8Span path) + out ReferenceCountedDisposable fileSystem, in Path path) { - UnsafeHelpers.SkipParamInit(out contentFileSystem); - Unsafe.SkipInit(out FsPath fullPath); + UnsafeHelpers.SkipParamInit(out fileSystem); - var sb = new U8StringBuilder(fullPath.Str); - sb.Append(path) - .Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }); + var pathRoot = new Path(); + var pathData = new Path(); - if (sb.Overflowed) - return ResultFs.TooLongPath.Log(); - - Result rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool success, fullPath.Str); + Result rc = PathFunctions.SetUpFixedPath(ref pathData, + new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a' }); if (rc.IsFailure()) return rc; - // Reopen the host filesystem as case sensitive - if (success) - { - baseFileSystem.Dispose(); + rc = pathRoot.Combine(in path, in pathData); + if (rc.IsFailure()) return rc; - rc = OpenHostFileSystem(out baseFileSystem, U8Span.Empty, openCaseSensitive: true); - if (rc.IsFailure()) return rc; - } + rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isSupported, ref pathRoot); + if (rc.IsFailure()) return rc; - return _config.SubDirectoryFsCreator.Create(out contentFileSystem, ref baseFileSystem, fullPath); + rc = _config.TargetManagerFsCreator.Create(out fileSystem, in pathRoot, isSupported, false, Result.Success); + if (rc.IsFailure()) return rc; + + pathData.Dispose(); + pathRoot.Dispose(); + return Result.Success; } private Result ParseNsp(ref U8Span path, out ReferenceCountedDisposable fileSystem, @@ -710,17 +721,18 @@ namespace LibHac.FsSrv ReferenceCountedDisposable subDirFs = null; try { - // Open the subdirectory filesystem - Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, new U8Span(dirName)); + var directoryPath = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref directoryPath, dirName); if (rc.IsFailure()) return rc; - if (fsType == FileSystemProxyType.Code) - { - rc = _config.StorageOnNcaCreator.VerifyAcidSignature(subDirFs.Target, null); - if (rc.IsFailure()) return rc; - } + if (directoryPath.IsEmpty()) + return ResultFs.InvalidArgument.Log(); - Shared.Move(out fileSystem, ref subDirFs); + // Open the subdirectory filesystem + rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, in directoryPath); + if (rc.IsFailure()) return rc; + + fileSystem = Shared.Move(ref subDirFs); return Result.Success; } finally @@ -828,35 +840,36 @@ namespace LibHac.FsSrv return Result.Success; } - public Result ResolveProgramPath(out PathLr path, ProgramId programId, StorageId storageId) + public Result ResolveProgramPath(out bool isDirectory, ref Path path, ProgramId programId, StorageId storageId) { - Result rc = _locationResolverSet.ResolveProgramPath(out path, programId.Value, storageId); + Result rc = _locationResolverSet.ResolveProgramPath(out isDirectory, ref path, programId, storageId); if (rc.IsSuccess()) return Result.Success; - var dataId = new DataId(programId.Value); + isDirectory = false; - rc = _locationResolverSet.ResolveDataPath(out path, dataId, storageId); + rc = _locationResolverSet.ResolveDataPath(ref path, new DataId(programId.Value), storageId); if (rc.IsSuccess()) return Result.Success; return ResultFs.TargetNotFound.Log(); } - public Result ResolveRomPath(out PathLr path, ulong id, StorageId storageId) + public Result ResolveRomPath(out bool isDirectory, ref Path path, ulong id, StorageId storageId) { - return _locationResolverSet.ResolveRomPath(out path, id, storageId); + return _locationResolverSet.ResolveRomPath(out isDirectory, ref path, new ProgramId(id), storageId); } - public Result ResolveApplicationHtmlDocumentPath(out PathLr path, Ncm.ApplicationId applicationId, - StorageId storageId) + public Result ResolveApplicationHtmlDocumentPath(out bool isDirectory, ref Path path, + Ncm.ApplicationId applicationId, StorageId storageId) { - return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out path, applicationId, storageId); + return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out isDirectory, ref path, applicationId, + storageId); } - public Result ResolveRegisteredHtmlDocumentPath(out PathLr path, ulong id) + public Result ResolveRegisteredHtmlDocumentPath(ref Path path, ulong id) { - return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(out path, id); + return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(ref path, id); } internal StorageType GetStorageFlag(ulong programId) @@ -909,48 +922,12 @@ namespace LibHac.FsSrv _romFsRecoveredByInvalidateCacheCount = 0; } - public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, bool openCaseSensitive) + public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, in Path rootPath, bool openCaseSensitive) { UnsafeHelpers.SkipParamInit(out fileSystem); - Result rc; - if (!path.IsEmpty()) - { - rc = Util.VerifyHostPath(path); - if (rc.IsFailure()) return rc; - } - - ReferenceCountedDisposable hostFs = null; - ReferenceCountedDisposable subDirFs = null; - try - { - rc = _config.TargetManagerFsCreator.Create(out hostFs, openCaseSensitive); - if (rc.IsFailure()) return rc; - - if (path.IsEmpty()) - { - ReadOnlySpan 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; - - Shared.Move(out fileSystem, ref hostFs); - return Result.Success; - } - - rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref hostFs, path, preserveUnc: true); - if (rc.IsFailure()) return rc; - - Shared.Move(out fileSystem, ref subDirFs); - return Result.Success; - } - finally - { - hostFs?.Dispose(); - subDirFs?.Dispose(); - } + return _config.TargetManagerFsCreator.Create(out fileSystem, in rootPath, openCaseSensitive, false, + Result.Success); } internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs index d1c789c2..66baff43 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemService.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -15,9 +15,10 @@ using IFileSystem = LibHac.Fs.Fsa.IFileSystem; using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; using IFile = LibHac.Fs.Fsa.IFile; using IFileSf = LibHac.FsSrv.Sf.IFile; -using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer; +using Path = LibHac.Fs.Path; using SaveData = LibHac.Fs.SaveData; using Utility = LibHac.FsSystem.Utility; +using static LibHac.Fs.StringTraits; namespace LibHac.FsSrv { @@ -34,21 +35,21 @@ namespace LibHac.FsSrv private const int SaveDataBlockSize = 0x4000; - private ReferenceCountedDisposable.WeakReference SelfReference { get; set; } - private SaveDataFileSystemServiceImpl ServiceImpl { get; } - private ulong ProcessId { get; } - private FsPath SaveDataRootPath { get; set; } - private SemaphoreAdapter OpenEntryCountSemaphore { get; } - private SemaphoreAdapter SaveDataMountCountSemaphore { get; } + private ReferenceCountedDisposable.WeakReference _selfReference; + private SaveDataFileSystemServiceImpl _serviceImpl; + private ulong _processId; + private Path.Stored _saveDataRootPath; + private SemaphoreAdapter _openEntryCountSemaphore; + private SemaphoreAdapter _saveDataMountCountSemaphore; - private HorizonClient Hos => ServiceImpl.Hos; + private HorizonClient Hos => _serviceImpl.Hos; public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) { - ServiceImpl = serviceImpl; - ProcessId = processId; - OpenEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); - SaveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + _serviceImpl = serviceImpl; + _processId = processId; + _openEntryCountSemaphore = new SemaphoreAdapter(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); + _saveDataMountCountSemaphore = new SemaphoreAdapter(SaveMountSemaphoreCount, SaveMountSemaphoreCount); } public static ReferenceCountedDisposable CreateShared( @@ -59,7 +60,7 @@ namespace LibHac.FsSrv // Wrap the service in a ref-counter and give the service a weak self-reference var sharedService = new ReferenceCountedDisposable(saveService); - saveService.SelfReference = + saveService._selfReference = new ReferenceCountedDisposable.WeakReference(sharedService); return sharedService; @@ -457,12 +458,13 @@ namespace LibHac.FsSrv private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) { // Delete the save data's meta files - Result rc = ServiceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId); + Result rc = _serviceImpl.DeleteAllSaveDataMetas(saveDataId, spaceId); if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; // Delete the actual save data. - rc = ServiceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, SaveDataRootPath); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + rc = _serviceImpl.DeleteSaveDataFileSystem(spaceId, saveDataId, wipeSaveFile, in saveDataRootPath); if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; @@ -526,7 +528,7 @@ namespace LibHac.FsSrv // Only the FS process may delete the save indexer's save data. if (saveDataId == SaveData.SaveIndexerId) { - if (!IsCurrentProcess(ProcessId)) + if (!IsCurrentProcess(_processId)) return ResultFs.PermissionDenied.Log(); actualSpaceId = spaceId; @@ -554,8 +556,8 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; Result GetExtraData(out SaveDataExtraData data) => - ServiceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, - SaveDataRootPath); + _serviceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, + _saveDataRootPath.GetPath()); rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); if (rc.IsFailure()) return rc; @@ -625,28 +627,30 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) return ResultFs.PermissionDenied.Log(); - if (StringUtils.GetLength(path.Str, PathTool.EntryNameLengthMax + 1) > PathTool.EntryNameLengthMax) - return ResultFs.TooLongPath.Log(); + var saveDataRootPath = new Path(); - if (path.Str[0] == 0) - SaveDataRootPath.Str[0] = 0; - - var sb = new U8StringBuilder(SaveDataRootPath.Str); - sb.Append((byte)'/').Append(path.Str); - - if (StringUtils.Compare(SaveDataRootPath.Str.Slice(1), new[] { (byte)'/', (byte)'/' }, 2) == 0) + if (path.Str[0] == NullTerminator) { - for (int i = 0; SaveDataRootPath.Str[i] == '/'; i++) - { - SaveDataRootPath.Str[i] = (byte)'\\'; - } + rc = saveDataRootPath.Initialize(new[] { (byte)'/' }); + if (rc.IsFailure()) return rc; + } + else + { + rc = saveDataRootPath.InitializeWithReplaceUnc(path.Str); + if (rc.IsFailure()) return rc; } - using var normalizer = new PathNormalizer(SaveDataRootPath, PathNormalizer.Option.PreserveUnc); - if (normalizer.Result.IsFailure()) - return normalizer.Result; + var pathFlags = new PathFlags(); + pathFlags.AllowWindowsPath(); + pathFlags.AllowRelativePath(); + pathFlags.AllowEmptyPath(); + + rc = saveDataRootPath.Normalize(pathFlags); + if (rc.IsFailure()) return rc; + + _saveDataRootPath.Initialize(in saveDataRootPath); + saveDataRootPath.Dispose(); - StringUtils.Copy(SaveDataRootPath.Str, normalizer.Path); return Result.Success; } @@ -658,7 +662,13 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) return ResultFs.PermissionDenied.Log(); - SaveDataRootPath.Str.Clear(); + var saveDataRootPath = new Path(); + rc = saveDataRootPath.InitializeAsEmpty(); + if (rc.IsFailure()) return rc; + + _saveDataRootPath.Initialize(in saveDataRootPath); + saveDataRootPath.Dispose(); + return Result.Success; } @@ -709,7 +719,7 @@ namespace LibHac.FsSrv { // The save indexer doesn't index itself saveDataId = SaveData.SaveIndexerId; - rc = ServiceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId); + rc = _serviceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId); if (rc.IsSuccess() && saveExists) { @@ -780,8 +790,9 @@ namespace LibHac.FsSrv } // After the new save was added to the save indexer, create the save data file or directory. - rc = ServiceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, SaveDataRootPath, - in hashSalt, false); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, + in saveDataRootPath, in hashSalt, false); if (rc.IsFailure()) { @@ -792,21 +803,21 @@ namespace LibHac.FsSrv rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); if (rc.IsFailure()) return rc; - rc = ServiceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, - SaveDataRootPath, in hashSalt, false); + rc = _serviceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, + in saveDataRootPath, in hashSalt, false); if (rc.IsFailure()) return rc; } if (metaInfo.Type != SaveDataMetaType.None) { // Create the requested save data meta file. - rc = ServiceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type, + rc = _serviceImpl.CreateSaveDataMeta(saveDataId, creationInfo.SpaceId, metaInfo.Type, metaInfo.Size); if (rc.IsFailure()) return rc; if (metaInfo.Type == SaveDataMetaType.Thumbnail) { - rc = ServiceImpl.OpenSaveDataMeta(out IFile metaFile, saveDataId, creationInfo.SpaceId, + rc = _serviceImpl.OpenSaveDataMeta(out IFile metaFile, saveDataId, creationInfo.SpaceId, metaInfo.Type); using (metaFile) @@ -866,7 +877,6 @@ namespace LibHac.FsSrv } accessor?.Dispose(); - } } @@ -896,7 +906,7 @@ namespace LibHac.FsSrv if (dataSize < 0 || journalSize < 0) return ResultFs.InvalidSize.Log(); - return ServiceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); + return _serviceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); } public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, @@ -1037,8 +1047,9 @@ namespace LibHac.FsSrv } // Open the save data using its ID - Result saveFsResult = ServiceImpl.OpenSaveDataFileSystem(out fileSystem, spaceId, tempSaveDataId, - SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + Result saveFsResult = _serviceImpl.OpenSaveDataFileSystem(out fileSystem, spaceId, tempSaveDataId, + in saveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); if (saveFsResult.IsSuccess()) { @@ -1117,7 +1128,8 @@ namespace LibHac.FsSrv Result rc = TryAcquireSaveDataMountCountSemaphore(out mountCountSemaphore); if (rc.IsFailure()) return rc; - bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); // Open the file system rc = OpenSaveDataFileSystemCore(out tempFileSystem, out ulong saveDataId, spaceId, in attribute, @@ -1127,8 +1139,12 @@ namespace LibHac.FsSrv // Can't use attribute in a closure, so copy the needed field SaveDataType type = attribute.Type; - Result ReadExtraData(out SaveDataExtraData data) => - ServiceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, SaveDataRootPath); + Result ReadExtraData(out SaveDataExtraData data) + { + Path savePath = _saveDataRootPath.GetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, + in savePath); + } // Check if we have permissions to open this save data rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); @@ -1142,13 +1158,15 @@ namespace LibHac.FsSrv tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); } - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService); tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter, ref mountCountSemaphore); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + var pathFlags = new PathFlags(); + pathFlags.AllowBackslash(); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, pathFlags, false); return Result.Success; } finally @@ -1223,7 +1241,8 @@ namespace LibHac.FsSrv if (!accessibility.CanRead || !accessibility.CanWrite) return ResultFs.PermissionDenied.Log(); - bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + bool useAsyncFileSystem = !_serviceImpl.IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); ReferenceCountedDisposable tempFileSystem = null; ReferenceCountedDisposable saveService = null; @@ -1240,7 +1259,8 @@ namespace LibHac.FsSrv SaveDataType type = attribute.Type; Result ReadExtraData(out SaveDataExtraData data) => - ServiceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, SaveDataRootPath); + _serviceImpl.ReadSaveDataFileSystemExtraData(out data, spaceId, saveDataId, type, + _saveDataRootPath.GetPath()); // Check if we have permissions to open this save data rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); @@ -1254,12 +1274,14 @@ namespace LibHac.FsSrv tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); } - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService); tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter); - fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + var pathFlags = new PathFlags(); + pathFlags.AllowBackslash(); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem, pathFlags, false); return Result.Success; } finally @@ -1288,8 +1310,9 @@ namespace LibHac.FsSrv rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId); if (rc.IsFailure()) return rc; - return ServiceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, - SaveDataRootPath); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + return _serviceImpl.ReadSaveDataFileSystemExtraData(out extraData, spaceId, saveDataId, key.Type, + in saveDataRootPath); } finally { @@ -1361,15 +1384,16 @@ namespace LibHac.FsSrv } } - Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data, - resolvedSpaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str)); + Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, + resolvedSpaceId, saveDataId, key.Type, _saveDataRootPath.GetPath()); rc = SaveDataAccessibilityChecker.CheckReadExtraData(in key, in extraDataMask, programInfo, ReadExtraData); if (rc.IsFailure()) return rc; - rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId, - saveDataId, key.Type, new U8Span(SaveDataRootPath.Str)); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData tempExtraData, resolvedSpaceId, + saveDataId, key.Type, in saveDataRootPath); if (rc.IsFailure()) return rc; MaskExtraData(ref tempExtraData, in extraDataMask); @@ -1469,7 +1493,8 @@ namespace LibHac.FsSrv { using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); - return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, SaveDataRootPath, + Path saveDataRootPath = _saveDataRootPath.GetPath(); + return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraData, in saveDataRootPath, saveType, updateTimeStamp); } @@ -1490,21 +1515,22 @@ namespace LibHac.FsSrv rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId); if (rc.IsFailure()) return rc; - Result ReadExtraData(out SaveDataExtraData data) => ServiceImpl.ReadSaveDataFileSystemExtraData(out data, - spaceId, saveDataId, key.Type, new U8Span(SaveDataRootPath.Str)); + Result ReadExtraData(out SaveDataExtraData data) => _serviceImpl.ReadSaveDataFileSystemExtraData(out data, + spaceId, saveDataId, key.Type, _saveDataRootPath.GetPath()); rc = SaveDataAccessibilityChecker.CheckWriteExtraData(in key, in extraDataMask, programInfo, ReadExtraData); if (rc.IsFailure()) return rc; - rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, - saveDataId, key.Type, SaveDataRootPath); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraDataModify, spaceId, + saveDataId, key.Type, in saveDataRootPath); if (rc.IsFailure()) return rc; ModifySaveDataExtraData(ref extraDataModify, in extraData, in extraDataMask); - return ServiceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, - SaveDataRootPath, key.Type, false); + return _serviceImpl.WriteSaveDataFileSystemExtraData(spaceId, saveDataId, in extraDataModify, + in saveDataRootPath, key.Type, false); } finally { @@ -1872,7 +1898,7 @@ namespace LibHac.FsSrv Result rc; // Cache storage on the SD card will always take priority over case storage in NAND - if (ServiceImpl.IsSdCardAccessible()) + if (_serviceImpl.IsSdCardAccessible()) { rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdCache); if (rc.IsFailure()) return rc; @@ -1957,8 +1983,9 @@ namespace LibHac.FsSrv Result rc = FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, index); if (rc.IsFailure()) return rc; - rc = ServiceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, - saveInfo.SaveDataId, saveInfo.Type, new U8Span(SaveDataRootPath.Str)); + Path saveDataRootPath = _saveDataRootPath.GetPath(); + rc = _serviceImpl.ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, spaceId, + saveInfo.SaveDataId, saveInfo.Type, in saveDataRootPath); if (rc.IsFailure()) return rc; usableDataSize = extraData.DataSize; @@ -2005,7 +2032,7 @@ namespace LibHac.FsSrv public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) { - return ServiceImpl.SetSdCardEncryptionSeed(in seed); + return _serviceImpl.SetSdCardEncryptionSeed(in seed); } public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, @@ -2016,7 +2043,7 @@ namespace LibHac.FsSrv private ProgramId ResolveDefaultSaveDataReferenceProgramId(ProgramId programId) { - return ServiceImpl.ResolveDefaultSaveDataReferenceProgramId(programId); + return _serviceImpl.ResolveDefaultSaveDataReferenceProgramId(programId); } public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, @@ -2085,15 +2112,17 @@ namespace LibHac.FsSrv ReferenceCountedDisposable fileSystem = null; try { - Result rc = ServiceImpl.OpenSaveDataDirectoryFileSystem(out fileSystem, SaveDataSpaceId.Temporary, - U8Span.Empty, false); + Result rc = _serviceImpl.OpenSaveDataDirectoryFileSystem(out fileSystem, SaveDataSpaceId.Temporary); if (rc.IsFailure()) return rc; - var rootPath = new U8Span(new[] { (byte)'/' }); - rc = fileSystem.Target.CleanDirectoryRecursively(rootPath); + var pathRoot = new Path(); + rc = PathFunctions.SetUpFixedPath(ref pathRoot, new[] { (byte)'/' }); if (rc.IsFailure()) return rc; - ServiceImpl.ResetTemporaryStorageIndexer(); + rc = fileSystem.Target.CleanDirectoryRecursively(in pathRoot); + if (rc.IsFailure()) return rc; + + _serviceImpl.ResetTemporaryStorageIndexer(); return Result.Success; } finally @@ -2116,10 +2145,10 @@ namespace LibHac.FsSrv ReferenceCountedDisposable commitInterface = null; try { - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); commitInterface = saveService.AddReference(); - commitManager = MultiCommitManager.CreateShared(ServiceImpl.FsServer, ref commitInterface); + commitManager = MultiCommitManager.CreateShared(_serviceImpl.FsServer, ref commitInterface); return Result.Success; } finally @@ -2146,12 +2175,12 @@ namespace LibHac.FsSrv public Result RecoverMultiCommit() { - return MultiCommitManager.Recover(ServiceImpl.FsServer, this, ServiceImpl); + return MultiCommitManager.Recover(_serviceImpl.FsServer, this, _serviceImpl); } public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) { - return ServiceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); + return _serviceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); } public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) @@ -2197,9 +2226,9 @@ namespace LibHac.FsSrv IUniqueLock uniqueLock = null; try { - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); - Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, OpenEntryCountSemaphore, + Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, _openEntryCountSemaphore, ref saveService); if (rc.IsFailure()) return rc; @@ -2221,9 +2250,9 @@ namespace LibHac.FsSrv IUniqueLock uniqueLock = null; try { - saveService = SelfReference.AddReference(); + saveService = _selfReference.AddReference(); - Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, SaveDataMountCountSemaphore, + Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, _saveDataMountCountSemaphore, ref saveService); if (rc.IsFailure()) return rc; @@ -2250,13 +2279,13 @@ namespace LibHac.FsSrv if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility)) return ResultFs.PermissionDenied.Log(); - ServiceImpl.SetSdCardAccessibility(isAccessible); + _serviceImpl.SetSdCardAccessibility(isAccessible); return Result.Success; } public Result IsSdCardAccessible(out bool isAccessible) { - isAccessible = ServiceImpl.IsSdCardAccessible(); + isAccessible = _serviceImpl.IsSdCardAccessible(); return Result.Success; } @@ -2267,7 +2296,7 @@ namespace LibHac.FsSrv SaveDataIndexerAccessor accessorTemp = null; try { - Result rc = ServiceImpl.OpenSaveDataIndexerAccessor(out accessorTemp, out bool neededInit, spaceId); + Result rc = _serviceImpl.OpenSaveDataIndexerAccessor(out accessorTemp, out bool neededInit, spaceId); if (rc.IsFailure()) return rc; if (neededInit) @@ -2287,7 +2316,7 @@ namespace LibHac.FsSrv private Result GetProgramInfo(out ProgramInfo programInfo) { - return ServiceImpl.GetProgramInfo(out programInfo, ProcessId); + return _serviceImpl.GetProgramInfo(out programInfo, _processId); } private bool IsCurrentProcess(ulong processId) @@ -2387,8 +2416,8 @@ namespace LibHac.FsSrv public void Dispose() { - OpenEntryCountSemaphore.Dispose(); - SaveDataMountCountSemaphore.Dispose(); + _openEntryCountSemaphore.Dispose(); + _saveDataMountCountSemaphore.Dispose(); } } } diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 2e439a57..7c2edef7 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -1,5 +1,5 @@ using System; -using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Diag; using LibHac.Fs; @@ -88,15 +88,21 @@ namespace LibHac.FsSrv ReferenceCountedDisposable fileSystem = null; try { - Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, U8Span.Empty, true); + Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId); if (rc.IsFailure()) return rc; // Get the path of the save data - Span saveDataPath = stackalloc byte[0x12]; - var sb = new U8StringBuilder(saveDataPath); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - rc = fileSystem.Target.GetEntryType(out _, new U8Span(saveDataPath)); + var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; + + rc = fileSystem.Target.GetEntryType(out _, in saveImageName); if (rc.IsSuccess()) { @@ -120,7 +126,7 @@ namespace LibHac.FsSrv } public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool openReadOnly, SaveDataType type, + SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) { UnsafeHelpers.SkipParamInit(out fileSystem); @@ -130,10 +136,10 @@ namespace LibHac.FsSrv ReferenceCountedDisposable saveDataFs = null; try { - Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, saveDataRootPath, true); + Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, in saveDataRootPath, true); if (rc.IsFailure()) return rc; - bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, saveDataRootPath); + bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, in saveDataRootPath); // Note: When directory save data is allowed, Nintendo creates the save directory if it doesn't exist. // This bypasses normal save data creation, leaving the save with empty extra data. @@ -209,22 +215,24 @@ namespace LibHac.FsSrv public Result OpenSaveDataMetaDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId) { - var metaDirPath = // /saveMeta/ - new U8Span(new[] - { - (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', - (byte)'a', (byte)'/' - }); + UnsafeHelpers.SkipParamInit(out fileSystem); - Span directoryName = stackalloc byte[0x1B]; - var sb = new U8StringBuilder(directoryName); - sb.Append(metaDirPath).AppendFormat(saveDataId, 'x', 16); + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(directoryName)); + var saveDataMetaIdDirectoryName = new Path(); + Result rc = PathFunctions.SetUpFixedPathSaveMetaDir(ref saveDataMetaIdDirectoryName, + saveDataMetaIdDirectoryNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; + + return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataMetaIdDirectoryName); } public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool useSecondMacKey) + SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool useSecondMacKey) { throw new NotImplementedException(); } @@ -245,11 +253,18 @@ namespace LibHac.FsSrv Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId); if (rc.IsFailure()) return rc; - Span saveMetaPathBuffer = stackalloc byte[0xF]; - var sb = new U8StringBuilder(saveMetaPathBuffer); - sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8); + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - return metaDirFs.Target.CreateFile(new U8Span(saveMetaPathBuffer), metaSize); + var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc; + + return metaDirFs.Target.CreateFile(in saveDataMetaName, metaSize); } finally { @@ -265,11 +280,18 @@ namespace LibHac.FsSrv Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId); if (rc.IsFailure()) return rc; - Span saveMetaPathBuffer = stackalloc byte[0xF]; - var sb = new U8StringBuilder(saveMetaPathBuffer); - sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8); + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - return metaDirFs.Target.DeleteFile(new U8Span(saveMetaPathBuffer)); + var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc; + + return metaDirFs.Target.DeleteFile(in saveDataMetaName); } finally { @@ -279,30 +301,43 @@ namespace LibHac.FsSrv public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId) { - var metaDirPath = // /saveMeta - new U8Span(new[] + ReadOnlySpan metaDirName = // /saveMeta + new[] { - (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e', (byte)'M', (byte)'e', (byte)'t', - (byte)'a' - }); + (byte) '/', (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'M', (byte) 'e', (byte) 't', + (byte) 'a' + }; + + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataIdDirectoryNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); ReferenceCountedDisposable fileSystem = null; try { - Result rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, metaDirPath, false); + var saveDataMetaDirectoryName = new Path(); + Result rc = PathFunctions.SetUpFixedPath(ref saveDataMetaDirectoryName, metaDirName); if (rc.IsFailure()) return rc; - // Get the path of the save data meta - Span saveDataPathBuffer = stackalloc byte[0x12]; - var sb = new U8StringBuilder(saveDataPathBuffer); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataMetaDirectoryName, false); + if (rc.IsFailure()) return rc; + + var saveDataIdDirectoryName = new Path(); + PathFunctions.SetUpFixedPathSaveId(ref saveDataIdDirectoryName, saveDataIdDirectoryNameBuffer, + saveDataId); + if (rc.IsFailure()) return rc; // Delete the save data's meta directory, ignoring the error if the directory is already gone - rc = fileSystem.Target.DeleteDirectoryRecursively(new U8Span(saveDataPathBuffer)); + rc = fileSystem.Target.DeleteDirectoryRecursively(in saveDataIdDirectoryName); if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; + saveDataMetaDirectoryName.Dispose(); + saveDataIdDirectoryName.Dispose(); + return Result.Success; } finally @@ -322,11 +357,18 @@ namespace LibHac.FsSrv Result rc = OpenSaveDataMetaDirectoryFileSystem(out metaDirFs, spaceId, saveDataId); if (rc.IsFailure()) return rc; - Span saveMetaPathBuffer = stackalloc byte[0xF]; - var sb = new U8StringBuilder(saveMetaPathBuffer); - sb.Append((byte)'/').AppendFormat((int)metaType, 'x', 8); + // Hack around error CS8350. + const int bufferLength = 0xF; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveDataMetaNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - return metaDirFs.Target.OpenFile(out metaFile, new U8Span(saveMetaPathBuffer), OpenMode.ReadWrite); + var saveDataMetaName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveMetaName(ref saveDataMetaName, saveDataMetaNameBuffer, + (uint)metaType); + if (rc.IsFailure()) return rc; + + return metaDirFs.Target.OpenFile(out metaFile, in saveDataMetaName, OpenMode.ReadWrite); } finally { @@ -335,25 +377,31 @@ namespace LibHac.FsSrv } public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute, - in SaveDataCreationInfo creationInfo, U8Span saveDataRootPath, in Optional hashSalt, + in SaveDataCreationInfo creationInfo, in Path saveDataRootPath, in Optional hashSalt, bool skipFormat) { // Use directory save data for now + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + ReferenceCountedDisposable saveDirectoryFs = null; try { - Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, creationInfo.SpaceId, saveDataRootPath, - false); + Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, creationInfo.SpaceId, + in saveDataRootPath, false); if (rc.IsFailure()) return rc; - Span saveDataPathBuffer = stackalloc byte[0x12]; - var sb = new U8StringBuilder(saveDataPathBuffer); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; if (_config.IsPseudoSaveData()) { - rc = Utility.EnsureDirectory(saveDirectoryFs.Target, new U8Span(saveDataPathBuffer)); + rc = Utility12.EnsureDirectory(saveDirectoryFs.Target, in saveImageName); if (rc.IsFailure()) return rc; ReferenceCountedDisposable saveFs = null; @@ -405,51 +453,55 @@ namespace LibHac.FsSrv } } - private Result WipeData(IFileSystem fileSystem, U8Span filePath, RandomDataGenerator random) + private Result WipeData(IFileSystem fileSystem, in Path filePath, RandomDataGenerator random) { throw new NotImplementedException(); } public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile, - U8Span saveDataRootPath) + in Path saveDataRootPath) { + // Hack around error CS8350. + const int bufferLength = 0x12; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span saveImageNameBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); + ReferenceCountedDisposable fileSystem = null; try { _saveDataFsCacheManager.Unregister(spaceId, saveDataId); // Open the directory containing the save data - Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, saveDataRootPath, false); + Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, in saveDataRootPath, false); if (rc.IsFailure()) return rc; - // Get the path of the save data - Span saveDataPathBuffer = stackalloc byte[0x12]; - var saveDataPath = new U8Span(saveDataPathBuffer); - - var sb = new U8StringBuilder(saveDataPathBuffer); - sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + var saveImageName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName, saveImageNameBuffer, saveDataId); + if (rc.IsFailure()) return rc; // Check if the save data is a file or a directory - rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, saveDataPath); + rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, in saveImageName); if (rc.IsFailure()) return rc; // Delete the save data, wiping the file if needed if (entryType == DirectoryEntryType.Directory) { - rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath); + rc = fileSystem.Target.DeleteDirectoryRecursively(in saveImageName); if (rc.IsFailure()) return rc; } else { if (wipeSaveFile) { - WipeData(fileSystem.Target, saveDataPath, _config.GenerateRandomData).IgnoreResult(); + WipeData(fileSystem.Target, in saveImageName, _config.GenerateRandomData).IgnoreResult(); } - rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath); + rc = fileSystem.Target.DeleteFile(in saveImageName); if (rc.IsFailure()) return rc; } + saveImageName.Dispose(); return Result.Success; } finally @@ -459,7 +511,7 @@ namespace LibHac.FsSrv } public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, - ulong saveDataId, SaveDataType type, U8Span saveDataRootPath) + ulong saveDataId, SaveDataType type, in Path saveDataRootPath) { UnsafeHelpers.SkipParamInit(out extraData); @@ -515,7 +567,7 @@ namespace LibHac.FsSrv } public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId, - in SaveDataExtraData extraData, U8Span saveDataRootPath, SaveDataType type, bool updateTimeStamp) + in SaveDataExtraData extraData, in Path saveDataRootPath, SaveDataType type, bool updateTimeStamp) { // Nintendo does nothing when writing directory save data extra data. // We've extended directory save data to store extra data so we don't return early. @@ -573,7 +625,7 @@ namespace LibHac.FsSrv } public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset, - U8Span saveDataRootPath) + in Path saveDataRootPath) { throw new NotImplementedException(); } @@ -583,43 +635,51 @@ namespace LibHac.FsSrv return _config.TimeService.GetCurrentPosixTime(out timeStamp); } - private bool IsSaveEmulated(U8Span saveDataRootPath) + private bool IsSaveEmulated(in Path saveDataRootPath) { return !saveDataRootPath.IsEmpty(); } public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, - SaveDataSpaceId spaceId, U8Span saveDataRootPath, bool allowEmulatedSave) + SaveDataSpaceId spaceId) { - if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, saveDataRootPath)) + var rootPath = new Path(); + + return OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, in rootPath, true); + } + + public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in Path saveDataRootPath, bool allowEmulatedSave) + { + Result rc; + UnsafeHelpers.SkipParamInit(out fileSystem); + + if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, in saveDataRootPath)) { - UnsafeHelpers.SkipParamInit(out fileSystem); ReferenceCountedDisposable tmFs = null; try { // Ensure the target save data directory exists - Result rc = _config.TargetManagerFsCreator.Create(out tmFs, false); + rc = _config.TargetManagerFsCreator.Create(out tmFs, in saveDataRootPath, false, true, + ResultFs.SaveDataRootPathUnavailable.Value); if (rc.IsFailure()) return rc; - rc = Utility.EnsureDirectory(tmFs.Target, saveDataRootPath); - if (rc.IsFailure()) - return ResultFs.SaveDataRootPathUnavailable.LogConverted(rc); - tmFs.Dispose(); - // Nintendo does a straight copy here without checking the input path length, - // stopping before the null terminator. - // FsPath used instead of stackalloc for convenience - FsPath.FromSpan(out FsPath path, saveDataRootPath).IgnoreResult(); - - rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, path.Str); + var path = new Path(); + rc = path.Initialize(in saveDataRootPath); if (rc.IsFailure()) return rc; - rc = _config.TargetManagerFsCreator.Create(out tmFs, isTargetFsCaseSensitive); + rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path); if (rc.IsFailure()) return rc; - return Utility.CreateSubDirectoryFileSystem(out fileSystem, ref tmFs, new U8Span(path.Str), true); + rc = _config.TargetManagerFsCreator.Create(out tmFs, in path, isTargetFsCaseSensitive, false, + ResultFs.SaveDataRootPathUnavailable.Value); + if (rc.IsFailure()) return rc; + + path.Dispose(); + return Result.Success; } finally { @@ -627,6 +687,7 @@ namespace LibHac.FsSrv } } + var saveDataDirPath = new Path(); ReadOnlySpan saveDirName; if (spaceId == SaveDataSpaceId.Temporary) @@ -638,22 +699,29 @@ namespace LibHac.FsSrv saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save } - return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(saveDirName), true); + rc = PathFunctions.SetUpFixedPath(ref saveDataDirPath, saveDirName); + if (rc.IsFailure()) return rc; + + rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in saveDataDirPath, true); + if (rc.IsFailure()) return rc; + + saveDataDirPath.Dispose(); + return Result.Success; } public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable fileSystem, - SaveDataSpaceId spaceId, U8Span basePath) + SaveDataSpaceId spaceId, in Path basePath) { - return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, basePath, true); + return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, in basePath, true); } public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable fileSystem, - SaveDataSpaceId spaceId, U8Span basePath, bool createIfMissing) + SaveDataSpaceId spaceId, in Path basePath, bool createIfMissing) { UnsafeHelpers.SkipParamInit(out fileSystem); - ReferenceCountedDisposable tempFs = null; - ReferenceCountedDisposable tempSubFs = null; + ReferenceCountedDisposable baseFileSystem = null; + ReferenceCountedDisposable tempFileSystem = null; try { Result rc; @@ -661,51 +729,63 @@ namespace LibHac.FsSrv switch (spaceId) { case SaveDataSpaceId.System: - rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.System, - true); + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.System, true); if (rc.IsFailure()) return rc; - return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing); case SaveDataSpaceId.User: case SaveDataSpaceId.Temporary: - rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.User, - true); + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.User, true); if (rc.IsFailure()) return rc; - return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing); case SaveDataSpaceId.SdSystem: case SaveDataSpaceId.SdCache: - rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out tempFs, true); + rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem, true); if (rc.IsFailure()) return rc; - Unsafe.SkipInit(out FsPath path); - var sb = new U8StringBuilder(path.Str); - sb.Append((byte)'/') - .Append(basePath.Value) - .Append((byte)'/') - .Append(CommonPaths.SdCardNintendoRootDirectoryName); + // Hack around error CS8350. + const int bufferLength = 0x40; + Span buffer = stackalloc byte[bufferLength]; + ref byte bufferRef = ref MemoryMarshal.GetReference(buffer); + Span pathParentBuffer = MemoryMarshal.CreateSpan(ref bufferRef, bufferLength); - rc = Utility.WrapSubDirectory(out tempSubFs, ref tempFs, path, createIfMissing); + var parentPath = new Path(); + rc = PathFunctions.SetUpFixedPathSingleEntry(ref parentPath, pathParentBuffer, + CommonPaths.SdCardNintendoRootDirectoryName); if (rc.IsFailure()) return rc; - return _config.EncryptedFsCreator.Create(out fileSystem, tempSubFs, EncryptedFsKeyId.Save, - in _encryptionSeed); + var pathSdRoot = new Path(); + rc = pathSdRoot.Combine(in parentPath, in basePath); + if (rc.IsFailure()) return rc; + + tempFileSystem = Shared.Move(ref baseFileSystem); + rc = Utility.WrapSubDirectory(out baseFileSystem, ref tempFileSystem, in pathSdRoot, + createIfMissing); + if (rc.IsFailure()) return rc; + + rc = _config.EncryptedFsCreator.Create(out fileSystem, ref baseFileSystem, + IEncryptedFileSystemCreator.KeyId.Save, in _encryptionSeed); + if (rc.IsFailure()) return rc; + + parentPath.Dispose(); + pathSdRoot.Dispose(); + return Result.Success; case SaveDataSpaceId.ProperSystem: - rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.SystemProperPartition, true); if (rc.IsFailure()) return rc; - return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing); case SaveDataSpaceId.SafeMode: - rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SafeMode, - true); + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, BisPartitionId.SafeMode, true); if (rc.IsFailure()) return rc; - return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + return Utility.WrapSubDirectory(out fileSystem, ref baseFileSystem, in basePath, createIfMissing); default: return ResultFs.InvalidArgument.Log(); @@ -713,8 +793,8 @@ namespace LibHac.FsSrv } finally { - tempFs?.Dispose(); - tempSubFs?.Dispose(); + baseFileSystem?.Dispose(); + tempFileSystem?.Dispose(); } } @@ -734,18 +814,18 @@ namespace LibHac.FsSrv throw new NotImplementedException(); } - public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, U8Span saveDataRootPath) + public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath) { - return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath); + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath); } // Todo: remove once file save data is supported // Used to always allow directory save data in OpenSaveDataFileSystem - public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, U8Span saveDataRootPath) + public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, in Path saveDataRootPath) { // Todo: remove "|| true" once file save data is supported // ReSharper disable once ConditionIsAlwaysTrueOrFalse - return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath) || true; + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath) || true; } public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) diff --git a/src/LibHac/FsSrv/Util.cs b/src/LibHac/FsSrv/Util.cs deleted file mode 100644 index 356bb92f..00000000 --- a/src/LibHac/FsSrv/Util.cs +++ /dev/null @@ -1,81 +0,0 @@ -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; -using PathNormalizer = LibHac.FsSrv.Impl.PathNormalizer; - -namespace LibHac.FsSrv -{ - public static class Util - { - public static Result CreateSubFileSystem(out IFileSystem subFileSystem, IFileSystem baseFileSystem, string path, - bool createPathIfMissing) - { - UnsafeHelpers.SkipParamInit(out subFileSystem); - Result rc; - - if (!createPathIfMissing) - { - if (path == null) return ResultFs.NullptrArgument.Log(); - - rc = baseFileSystem.GetEntryType(out DirectoryEntryType entryType, path.ToU8Span()); - - if (rc.IsFailure() || entryType != DirectoryEntryType.Directory) - { - return ResultFs.PathNotFound.Log(); - } - } - - rc = baseFileSystem.EnsureDirectoryExists(path); - if (rc.IsFailure()) return rc; - - return CreateSubFileSystemImpl(out subFileSystem, baseFileSystem, path); - } - - public static Result CreateSubFileSystemImpl(out IFileSystem subFileSystem, IFileSystem baseFileSystem, string path) - { - UnsafeHelpers.SkipParamInit(out subFileSystem); - - if (path == null) return ResultFs.NullptrArgument.Log(); - - Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path.ToU8Span(), OpenDirectoryMode.Directory); - if (rc.IsFailure()) return rc; - - rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String()); - subFileSystem = fs; - return rc; - } - - public static Result VerifyHostPath(U8Span path) - { - if (path.IsEmpty()) - return Result.Success; - - if (path[0] != StringTraits.DirectorySeparator) - return ResultFs.InvalidPathFormat.Log(); - - U8Span path2 = path.Slice(1); - - if (path2.IsEmpty()) - return Result.Success; - - int skipLength = WindowsPath.GetWindowsPathSkipLength(path2); - int remainingLength = PathTools.MaxPathLength - skipLength; - - Result rc = PathUtility.VerifyPath(null, path2.Slice(skipLength), remainingLength, remainingLength); - if (rc.IsFailure()) return rc; - - using var normalizer = new PathNormalizer(path, PathNormalizer.Option.PreserveUnc); - return normalizer.Result; - } - - public static bool UseDeviceUniqueSaveMac(SaveDataSpaceId spaceId) - { - return spaceId == SaveDataSpaceId.System || - spaceId == SaveDataSpaceId.User || - spaceId == SaveDataSpaceId.Temporary || - spaceId == SaveDataSpaceId.ProperSystem || - spaceId == SaveDataSpaceId.SafeMode; - } - } -}