diff --git a/DotnetCliVersion.txt b/DotnetCliVersion.txt index bbe838d1..80d23bcf 100644 --- a/DotnetCliVersion.txt +++ b/DotnetCliVersion.txt @@ -1 +1 @@ -3.1.401 \ No newline at end of file +3.1.402 \ No newline at end of file diff --git a/build/CodeGen/result_modules.csv b/build/CodeGen/result_modules.csv index 9cda319c..891c2c0b 100644 --- a/build/CodeGen/result_modules.csv +++ b/build/CodeGen/result_modules.csv @@ -1,6 +1,7 @@ Name,Index Svc,1 Fs,2 +Lr,8 Loader,9 Sf,10 Kvdb,20 diff --git a/build/CodeGen/result_paths.csv b/build/CodeGen/result_paths.csv index 80c6be94..fbeddcb2 100644 --- a/build/CodeGen/result_paths.csv +++ b/build/CodeGen/result_paths.csv @@ -1,6 +1,7 @@ Name,Namespace,Path Svc,LibHac.Svc,LibHac/Svc/ResultSvc.cs Fs,LibHac.Fs,LibHac/Fs/ResultFs.cs +Lr,LibHac.Lr,LibHac/Lr/ResultLr.cs Loader,LibHac.Loader,LibHac/Loader/ResultLoader.cs Sf,LibHac.Sf,LibHac/Sf/ResultSf.cs Kvdb,LibHac.Kvdb,LibHac/Kvdb/ResultKvdb.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index fa74307a..6c945570 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -89,12 +89,29 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,3005,,OutOfRange, 2,3200,3499,AllocationMemoryFailed, +2,3256,,AllocationFailureInNcaFileSystemServiceImplA,In ParseNsp allocating FileStorageBasedFileSystem +2,3257,,AllocationFailureInNcaFileSystemServiceImplB,In ParseNca allocating FileStorageBasedFileSystem 2,3258,,AllocationFailureInProgramRegistryManagerA,In RegisterProgram allocating ProgramInfoNode +2,3264,,AllocationFailureFatFileSystemA,In Initialize allocating ProgramInfoNode +2,3280,,AllocationFailureInPartitionFileSystemCreatorA,In Create allocating PartitionFileSystemCore 2,3312,,AllocationFailureInAesXtsFileA,In Initialize allocating FileStorage 2,3313,,AllocationFailureInAesXtsFileB,In Initialize allocating AesXtsStorage 2,3314,,AllocationFailureInAesXtsFileC,In Initialize allocating AlignmentMatchingStoragePooledBuffer 2,3315,,AllocationFailureInAesXtsFileD,In Initialize allocating StorageFile -2,3383,,AllocationFailureInAesXtsFileE,In Initialize allocating SubStorage +2,3347,,AllocationFailureInPartitionFileSystemA,In Initialize allocating PartitionFileSystemMetaCore +2,3348,,AllocationFailureInPartitionFileSystemB,In DoOpenFile allocating PartitionFile +2,3349,,AllocationFailureInPartitionFileSystemC,In DoOpenDirectory allocating PartitionDirectory +2,3350,,AllocationFailureInPartitionFileSystemMetaA,In Initialize allocating metadata buffer +2,3351,,AllocationFailureInPartitionFileSystemMetaB,In Sha256 Initialize allocating metadata buffer +2,3355,,AllocationFailureInSubdirectoryFileSystemA,In Initialize allocating RootPathBuffer +2,3383,,AllocationFailureInAesXtsFileE,In Initialize +2,3394,,AllocationFailureInEncryptedFileSystemCreatorA,In Create allocating AesXtsFileSystem +2,3407,,AllocationFailureInFileSystemInterfaceAdapter, In OpenFile or OpenDirectory +2,3420,,AllocationFailureInNew, +2,3421,,AllocationFailureInCreateShared, +2,3422,,AllocationFailureInMakeUnique, +2,3423,,AllocationFailureInAllocateShared, +2,3424,,AllocationFailurePooledBufferNotEnoughSize, 2,3500,3999,MmcAccessFailed, @@ -148,8 +165,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,4464,,AllocationTableIteratedRangeEntry, 2,4501,4599,NcaCorrupted, -2,4512,,InvalidNcaFsType, -2,4527,,InvalidNcaProgramId, +2,4512,,InvalidNcaFileSystemType, +2,4527,,InvalidNcaId, 2,4601,4639,IntegrityVerificationStorageCorrupted, 2,4602,,InvalidIvfcMagic, @@ -205,6 +222,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,4812,,IncompleteBlockInZeroBitmapHashStorageFile, 2,5000,5999,Unexpected, +2,5121,,UnexpectedFatFileSystemSectorCount, 2,5307,,UnexpectedErrorInHostFileFlush, 2,5308,,UnexpectedErrorInHostFileGetSize, 2,5309,,UnknownHostFileSystemError, @@ -221,7 +239,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6030,6059,InvalidPathForOperation, 2,6031,,DirectoryNotDeletable, -2,6032,,DestinationIsSubPathOfSource, +2,6032,,DirectoryNotRenamable, 2,6033,,PathNotFoundInSaveDataFileTable, 2,6034,,DifferentDestFileSystem, @@ -249,6 +267,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6203,,InvalidOpenModeForWrite, 2,6300,6399,UnsupportedOperation, +2,6301,,UnsupportedCommitTarget, 2,6302,,UnsupportedOperationInSubStorageSetSize,Attempted to resize a non-resizable SubStorage. 2,6303,,UnsupportedOperationInResizableSubStorageSetSize,Attempted to resize a SubStorage that wasn't located at the end of the base storage. 2,6304,,UnsupportedOperationInMemoryStorageSetSize, @@ -260,6 +279,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6350,,UnsupportedOperationInRoGameCardStorageWrite, 2,6351,,UnsupportedOperationInRoGameCardStorageSetSize, 2,6359,,UnsupportedOperationInConcatFsQueryEntry, +2,6362,,UnsupportedOperationInFileServiceObjectAdapterA,Called OperateRange with an invalid operation ID. 2,6364,,UnsupportedOperationModifyRomFsFileSystem, 2,6365,,UnsupportedOperationInRomFsFileSystem,Called RomFsFileSystem::CommitProvisionally. 2,6366,,UnsupportedOperationRomFsFileSystemGetSpace, @@ -278,10 +298,15 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6452,,ExternalKeyAlreadyRegistered, 2,6454,,WriteStateUnflushed, +2,6456,,DirectoryNotClosed, 2,6457,,WriteModeFileNotClosed, +2,6458,,AllocatorAlreadyRegistered, +2,6459,,DefaultAllocatorUsed, 2,6461,,AllocatorAlignmentViolation, 2,6463,,MultiCommitFileSystemAlreadyAdded,The provided file system has already been added to the multi-commit manager. 2,6465,,UserNotExist, +2,6466,,DefaultGlobalFileDataCacheEnabled, +2,6467,,SaveDataRootPathUnavailable, 2,6600,6699,EntryNotFound, 2,6605,,TargetProgramNotFound,Specified program is not found in the program registry. @@ -302,6 +327,17 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6905,,NotMounted, 2,6906,,SaveDataIsExtending, +8,2,,ProgramNotFound, +8,3,,DataNotFound, +8,4,,UnknownStorageId, +8,5,,LocationResolverNotFound, +8,6,,HtmlDocumentNotFound, +8,7,,AddOnContentNotFound, +8,8,,ControlNotFound, +8,9,,LegalInformationNotFound, +8,10,,DebugProgramNotFound, +8,90,,TooManyRegisteredPaths, + 9,1,,TooLongArgument, 9,2,,TooManyArguments, 9,3,,TooLargeMeta, diff --git a/src/LibHac/Boot/Package1.cs b/src/LibHac/Boot/Package1.cs index 93660663..971a8a38 100644 --- a/src/LibHac/Boot/Package1.cs +++ b/src/LibHac/Boot/Package1.cs @@ -502,7 +502,7 @@ namespace LibHac.Boot Package1Section.Bootloader => 0, Package1Section.SecureMonitor => 1, Package1Section.WarmBoot => 2, - _ => -1, + _ => -1 }; } @@ -513,7 +513,7 @@ namespace LibHac.Boot Package1Section.Bootloader => 1, Package1Section.SecureMonitor => 2, Package1Section.WarmBoot => 0, - _ => -1, + _ => -1 }; } @@ -522,7 +522,7 @@ namespace LibHac.Boot Package1Section.Bootloader => 1, Package1Section.SecureMonitor => 0, Package1Section.WarmBoot => 2, - _ => -1, + _ => -1 }; } diff --git a/src/LibHac/Common/Keys/KeyInfo.cs b/src/LibHac/Common/Keys/KeyInfo.cs index ba27dc56..09f360f3 100644 --- a/src/LibHac/Common/Keys/KeyInfo.cs +++ b/src/LibHac/Common/Keys/KeyInfo.cs @@ -31,7 +31,7 @@ namespace LibHac.Common.Keys CommonSeedDiff = Common | Seed | DifferentDev, CommonDrvd = Common | Derived, DeviceRoot = Device | Root, - DeviceDrvd = Device | Derived, + DeviceDrvd = Device | Derived } public readonly string Name; diff --git a/src/LibHac/Common/SharedObjectHelpers.cs b/src/LibHac/Common/SharedObjectHelpers.cs new file mode 100644 index 00000000..db0f8097 --- /dev/null +++ b/src/LibHac/Common/SharedObjectHelpers.cs @@ -0,0 +1,22 @@ +using System.Runtime.CompilerServices; + +namespace LibHac.Common +{ + public static class Shared + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Move(ref T value) + { + T tmp = value; + value = default; + return tmp; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Move(out T dest, ref T value) + { + dest = value; + value = default; + } + } +} diff --git a/src/LibHac/Fat/FatError.cs b/src/LibHac/Fat/FatError.cs new file mode 100644 index 00000000..a6ff8b3d --- /dev/null +++ b/src/LibHac/Fat/FatError.cs @@ -0,0 +1,19 @@ +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.Fat +{ + [StructLayout(LayoutKind.Explicit, Size = 0x20)] + public struct FatError + { + private const int FunctionNameLength = 0x10; + + [FieldOffset(0x00)] public int Error; + [FieldOffset(0x04)] public int ExtraError; + [FieldOffset(0x08)] public int DriveId; + [FieldOffset(0x0C)] private byte _functionName; + + public U8SpanMutable ErrorName => + new U8SpanMutable(SpanHelpers.CreateSpan(ref _functionName, FunctionNameLength)); + } +} diff --git a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs index 09e37668..6ee3fcfa 100644 --- a/src/LibHac/Fs/Accessors/FileSystemAccessor.cs +++ b/src/LibHac/Fs/Accessors/FileSystemAccessor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using LibHac.Common; using LibHac.Fs.Fsa; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Accessors { @@ -20,10 +21,13 @@ namespace LibHac.Fs.Accessors private readonly object _locker = new object(); internal bool IsAccessLogEnabled { get; set; } + public IMultiCommitTarget MultiCommitTarget { get; } - public FileSystemAccessor(string name, IFileSystem baseFileSystem, FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator) + public FileSystemAccessor(U8Span name, IMultiCommitTarget multiCommitTarget, IFileSystem baseFileSystem, + FileSystemClient fsClient, ICommonMountNameGenerator nameGenerator) { - Name = name; + Name = name.ToString(); + MultiCommitTarget = multiCommitTarget; FileSystem = baseFileSystem; FsClient = fsClient; MountNameGenerator = nameGenerator; @@ -147,6 +151,11 @@ namespace LibHac.Fs.Accessors return MountNameGenerator.GenerateCommonMountName(nameBuffer); } + public ReferenceCountedDisposable GetMultiCommitTarget() + { + return MultiCommitTarget?.GetMultiCommitTarget(); + } + internal void NotifyCloseFile(FileAccessor file) { lock (_locker) diff --git a/src/LibHac/Fs/ApplicationSaveDataManagement.cs b/src/LibHac/Fs/ApplicationSaveDataManagement.cs index 1be980a9..d1b44184 100644 --- a/src/LibHac/Fs/ApplicationSaveDataManagement.cs +++ b/src/LibHac/Fs/ApplicationSaveDataManagement.cs @@ -232,7 +232,7 @@ namespace LibHac.Fs } private static Result EnsureApplicationCacheStorageImpl(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, long dataSize, long journalSize, bool allowExisting) { requiredSize = default; @@ -299,7 +299,7 @@ namespace LibHac.Fs } public static Result EnsureApplicationCacheStorage(this FileSystemClient fs, out long requiredSize, - out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index, + out CacheStorageTargetMedia target, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, long dataSize, long journalSize, bool allowExisting) { return EnsureApplicationCacheStorageImpl(fs, out requiredSize, out target, applicationId, saveDataOwnerId, @@ -334,7 +334,7 @@ namespace LibHac.Fs } public static Result TryCreateCacheStorage(this FileSystemClient fs, out long requiredSize, - SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, short index, + SaveDataSpaceId spaceId, Ncm.ApplicationId applicationId, ulong saveDataOwnerId, ushort index, long dataSize, long journalSize, bool allowExisting) { requiredSize = default; diff --git a/src/LibHac/Fs/CommonMountNames.cs b/src/LibHac/Fs/CommonPaths.cs similarity index 81% rename from src/LibHac/Fs/CommonMountNames.cs rename to src/LibHac/Fs/CommonPaths.cs index c26e0a47..950b7c9d 100644 --- a/src/LibHac/Fs/CommonMountNames.cs +++ b/src/LibHac/Fs/CommonPaths.cs @@ -1,8 +1,9 @@ -using LibHac.Common; +using System; +using LibHac.Common; namespace LibHac.Fs { - internal static class CommonMountNames + internal static class CommonPaths { public const char ReservedMountNamePrefixCharacter = '@'; @@ -21,5 +22,11 @@ namespace LibHac.Fs public const char GameCardFileSystemMountNameUpdateSuffix = 'U'; public const char GameCardFileSystemMountNameNormalSuffix = 'N'; public const char GameCardFileSystemMountNameSecureSuffix = 'S'; + + public static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo + new[] + { + (byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o' + }; } } diff --git a/src/LibHac/Fs/EncryptionSeed.cs b/src/LibHac/Fs/EncryptionSeed.cs index 7979b17c..f8d592c3 100644 --- a/src/LibHac/Fs/EncryptionSeed.cs +++ b/src/LibHac/Fs/EncryptionSeed.cs @@ -11,7 +11,7 @@ namespace LibHac.Fs { private readonly Key128 Key; - public ReadOnlySpan Value => SpanHelpers.AsByteSpan(ref this); + public readonly ReadOnlySpan Value => SpanHelpers.AsReadOnlyByteSpan(in this); public EncryptionSeed(ReadOnlySpan bytes) { diff --git a/src/LibHac/Fs/FileStorageBasedFileSystem.cs b/src/LibHac/Fs/FileStorageBasedFileSystem.cs index 843c840b..35d9efd3 100644 --- a/src/LibHac/Fs/FileStorageBasedFileSystem.cs +++ b/src/LibHac/Fs/FileStorageBasedFileSystem.cs @@ -6,35 +6,17 @@ namespace LibHac.Fs public class FileStorageBasedFileSystem : FileStorage2 { // ReSharper disable once UnusedAutoPropertyAccessor.Local - // FS keeps a shared pointer to the base filesystem - private IFileSystem BaseFileSystem { get; set; } + private ReferenceCountedDisposable BaseFileSystem { get; set; } private IFile BaseFile { get; set; } - private FileStorageBasedFileSystem() + public FileStorageBasedFileSystem() { FileSize = SizeNotInitialized; } - public static Result CreateNew(out FileStorageBasedFileSystem created, IFileSystem baseFileSystem, U8Span path, - OpenMode mode) + public Result Initialize(ReferenceCountedDisposable baseFileSystem, U8Span path, OpenMode mode) { - var obj = new FileStorageBasedFileSystem(); - Result rc = obj.Initialize(baseFileSystem, path, mode); - - if (rc.IsSuccess()) - { - created = obj; - return Result.Success; - } - - obj.Dispose(); - created = default; - return rc; - } - - private Result Initialize(IFileSystem baseFileSystem, U8Span path, OpenMode mode) - { - Result rc = baseFileSystem.OpenFile(out IFile file, path, mode); + Result rc = baseFileSystem.Target.OpenFile(out IFile file, path, mode); if (rc.IsFailure()) return rc; SetFile(file); @@ -49,6 +31,7 @@ namespace LibHac.Fs if (disposing) { BaseFile?.Dispose(); + BaseFileSystem?.Dispose(); } base.Dispose(disposing); diff --git a/src/LibHac/Fs/FileSystemClient.AccessLog.cs b/src/LibHac/Fs/FileSystemClient.AccessLog.cs index 7dd5f9eb..ed596f79 100644 --- a/src/LibHac/Fs/FileSystemClient.AccessLog.cs +++ b/src/LibHac/Fs/FileSystemClient.AccessLog.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Fs.Accessors; using LibHac.FsSrv; +using LibHac.Sf; namespace LibHac.Fs { @@ -187,7 +188,7 @@ namespace LibHac.Fs string logString = AccessLogHelpers.BuildDefaultLogLine(result, startTime, endTime, handleId, message, caller); IFileSystemProxy fsProxy = GetFileSystemProxyServiceObject(); - fsProxy.OutputAccessLogToSdCard(logString.ToU8Span()); + fsProxy.OutputAccessLogToSdCard(new InBuffer(logString.ToU8Span())).IgnoreResult(); } } diff --git a/src/LibHac/Fs/FileSystemClient.cs b/src/LibHac/Fs/FileSystemClient.cs index 2de35327..32bf8285 100644 --- a/src/LibHac/Fs/FileSystemClient.cs +++ b/src/LibHac/Fs/FileSystemClient.cs @@ -9,6 +9,7 @@ using LibHac.FsSrv; using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.Util; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; namespace LibHac.Fs { @@ -134,7 +135,13 @@ namespace LibHac.Fs public Result Register(U8Span mountName, IFileSystem fileSystem, ICommonMountNameGenerator nameGenerator) { - var accessor = new FileSystemAccessor(mountName.ToString(), fileSystem, this, nameGenerator); + return Register(mountName, null, fileSystem, nameGenerator); + } + + public Result Register(U8Span mountName, IMultiCommitTarget multiCommitTarget, IFileSystem fileSystem, + ICommonMountNameGenerator nameGenerator) + { + var accessor = new FileSystemAccessor(mountName, multiCommitTarget, fileSystem, this, nameGenerator); Result rc = MountTable.Mount(accessor); if (rc.IsFailure()) return rc; @@ -173,8 +180,8 @@ namespace LibHac.Fs if (path.IsNull()) return ResultFs.NullptrArgument.Log(); - int hostMountNameLen = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName); - if (StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountNameLen) == 0) + int hostMountNameLen = StringUtils.GetLength(CommonPaths.HostRootFileSystemMountName); + if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, hostMountNameLen) == 0) { return ResultFs.NotMounted.Log(); } @@ -195,7 +202,7 @@ namespace LibHac.Fs if (PathUtility.IsWindowsDrive(path) || PathUtility.IsUnc(path)) { - StringUtils.Copy(mountName.Name, CommonMountNames.HostRootFileSystemMountName); + StringUtils.Copy(mountName.Name, CommonPaths.HostRootFileSystemMountName); mountName.Name[PathTools.MountNameLengthMax] = StringTraits.NullTerminator; subPath = path; diff --git a/src/LibHac/Fs/FileSystemProxyErrorInfo.cs b/src/LibHac/Fs/FileSystemProxyErrorInfo.cs new file mode 100644 index 00000000..b1f20736 --- /dev/null +++ b/src/LibHac/Fs/FileSystemProxyErrorInfo.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; +using LibHac.Fat; + +namespace LibHac.Fs +{ + [StructLayout(LayoutKind.Explicit, Size = 0x80)] + public struct FileSystemProxyErrorInfo + { + [FieldOffset(0x00)] public int RomFsRemountForDataCorruptionCount; + [FieldOffset(0x04)] public int RomFsUnrecoverableDataCorruptionByRemountCount; + [FieldOffset(0x08)] public FatError FatError; + [FieldOffset(0x28)] public int RomFsRecoveredByInvalidateCacheCount; + [FieldOffset(0x2C)] public int SaveDataIndexCount; + } +} diff --git a/src/LibHac/Fs/FileTimeStamp.cs b/src/LibHac/Fs/FileTimeStamp.cs index 74aff00a..cda15cec 100644 --- a/src/LibHac/Fs/FileTimeStamp.cs +++ b/src/LibHac/Fs/FileTimeStamp.cs @@ -1,9 +1,13 @@ -namespace LibHac.Fs +using System.Runtime.InteropServices; + +namespace LibHac.Fs { + [StructLayout(LayoutKind.Sequential, Size = 0x20)] public struct FileTimeStampRaw { public long Created; public long Accessed; public long Modified; + public bool IsLocalTime; } } diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index a39a281d..6939c687 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -96,10 +96,11 @@ namespace LibHac.Fs public enum SaveDataState : byte { Normal = 0, - Creating = 1, + Processing = 1, State2 = 2, MarkedForDeletion = 3, - Extending = 4 + Extending = 4, + ImportSuspended = 5 } public enum ImageDirectoryId @@ -165,7 +166,8 @@ namespace LibHac.Fs KeepAfterResettingSystemSaveData = 1 << 0, KeepAfterRefurbishment = 1 << 1, KeepAfterResettingSystemSaveDataWithoutUserSaveData = 1 << 2, - NeedsSecureDelete = 1 << 3 + NeedsSecureDelete = 1 << 3, + Restore = 1 << 4 } public enum SdmmcPort @@ -188,4 +190,11 @@ namespace LibHac.Fs None = 0, PseudoCaseSensitive = 1 } + + public enum SimulatingDeviceDetectionMode + { + None = 0, + Inserted = 1, + NotInserted = 2 + } } diff --git a/src/LibHac/Fs/Fsa/IFileSystem.cs b/src/LibHac/Fs/Fsa/IFileSystem.cs index df54e17b..21c471f4 100644 --- a/src/LibHac/Fs/Fsa/IFileSystem.cs +++ b/src/LibHac/Fs/Fsa/IFileSystem.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.FsSystem; @@ -36,6 +37,31 @@ namespace LibHac.Fs.Fsa return DoCreateFile(path, size, option); } + /// + /// Creates or overwrites a file at the specified path. + /// + /// The full path of the file to create. + /// The initial size of the created file. + /// Should usually be + /// The of the requested operation. + /// + /// The following codes may be returned under certain conditions: + /// + /// The parent directory of the specified path does not exist: + /// Specified path already exists as either a file or directory: + /// Insufficient free space to create the file: + /// + public Result CreateFile(U8Span path, long size) + { + if (path.IsNull()) + return ResultFs.NullptrArgument.Log(); + + if (size < 0) + return ResultFs.OutOfRange.Log(); + + return DoCreateFile(path, size, CreateFileOptions.None); + } + /// /// Deletes the specified file. /// @@ -167,7 +193,7 @@ namespace LibHac.Fs.Fsa /// does not exist or is a file: /// 's parent directory does not exist: /// already exists as either a file or directory: - /// Either or is a subpath of the other: + /// Either or is a subpath of the other: /// public Result RenameDirectory(U8Span oldPath, U8Span newPath) { @@ -391,7 +417,7 @@ namespace LibHac.Fs.Fsa protected virtual Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) { - timeStamp = default; + Unsafe.SkipInit(out timeStamp); return ResultFs.NotImplemented.Log(); } diff --git a/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs b/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs new file mode 100644 index 00000000..7953e0a6 --- /dev/null +++ b/src/LibHac/Fs/Fsa/IMultiCommitTarget.cs @@ -0,0 +1,9 @@ +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; + +namespace LibHac.Fs.Fsa +{ + public interface IMultiCommitTarget + { + ReferenceCountedDisposable GetMultiCommitTarget(); + } +} diff --git a/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs new file mode 100644 index 00000000..e9312614 --- /dev/null +++ b/src/LibHac/Fs/Impl/DirectoryServiceObjectAdapter.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Sf; +using IDirectory = LibHac.Fs.Fsa.IDirectory; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; + +namespace LibHac.Fs.Impl +{ + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC directory object so it can be used as an locally. + /// + internal class DirectoryServiceObjectAdapter : IDirectory + { + private ReferenceCountedDisposable BaseDirectory { get; } + + public DirectoryServiceObjectAdapter(ReferenceCountedDisposable baseDirectory) + { + BaseDirectory = baseDirectory.AddReference(); + } + + protected override Result DoRead(out long entriesRead, Span entryBuffer) + { + Span buffer = MemoryMarshal.Cast(entryBuffer); + return BaseDirectory.Target.Read(out entriesRead, new OutBuffer(buffer)); + } + + protected override Result DoGetEntryCount(out long entryCount) + { + return BaseDirectory.Target.GetEntryCount(out entryCount); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseDirectory?.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs new file mode 100644 index 00000000..48581d22 --- /dev/null +++ b/src/LibHac/Fs/Impl/FileServiceObjectAdapter.cs @@ -0,0 +1,75 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using IFile = LibHac.Fs.Fsa.IFile; +using IFileSf = LibHac.FsSrv.Sf.IFile; + +namespace LibHac.Fs.Impl +{ + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC file object so it can be used as an locally. + /// + internal class FileServiceObjectAdapter : IFile + { + private ReferenceCountedDisposable BaseFile { get; } + + public FileServiceObjectAdapter(ReferenceCountedDisposable baseFile) + { + BaseFile = baseFile.AddReference(); + } + + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) + { + return BaseFile.Target.Read(out bytesRead, offset, destination, option); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) + { + return BaseFile.Target.Write(offset, source, option); + } + + protected override Result DoFlush() + { + return BaseFile.Target.Flush(); + } + + protected override Result DoSetSize(long size) + { + return BaseFile.Target.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + return BaseFile.Target.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + return BaseFile.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); + + return BaseFile.Target.OperateRange(out info, (int)OperationId.QueryRange, offset, size); + default: + return ResultFs.UnsupportedOperationInFileServiceObjectAdapterA.Log(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseFile?.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs new file mode 100644 index 00000000..dbc38408 --- /dev/null +++ b/src/LibHac/Fs/Impl/FileSystemServiceObjectAdapter.cs @@ -0,0 +1,231 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.Sf; +using LibHac.Util; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; + +namespace LibHac.Fs.Impl +{ + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC file system object so it can be used as an locally. + /// + internal class FileSystemServiceObjectAdapter : Fsa.IFileSystem, IMultiCommitTarget + { + private ReferenceCountedDisposable BaseFs { get; } + + public FileSystemServiceObjectAdapter(ReferenceCountedDisposable baseFileSystem) + { + BaseFs = baseFileSystem.AddReference(); + } + + protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.CreateFile(in sfPath, size, (int)option); + } + + protected override Result DoDeleteFile(U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.DeleteFile(in sfPath); + } + + protected override Result DoCreateDirectory(U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.DeleteFile(in sfPath); + } + + protected override Result DoDeleteDirectory(U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.DeleteDirectory(in sfPath); + } + + protected override Result DoDeleteDirectoryRecursively(U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.DeleteDirectoryRecursively(in sfPath); + } + + protected override Result DoCleanDirectoryRecursively(U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.CleanDirectoryRecursively(in sfPath); + } + + protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) + { + Result rc = GetPathForServiceObject(out Path oldSfPath, oldPath); + if (rc.IsFailure()) return rc; + + rc = GetPathForServiceObject(out Path newSfPath, newPath); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.RenameFile(in oldSfPath, in newSfPath); + } + + protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) + { + Result rc = GetPathForServiceObject(out Path oldSfPath, oldPath); + if (rc.IsFailure()) return rc; + + rc = GetPathForServiceObject(out Path newSfPath, newPath); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.RenameDirectory(in oldSfPath, in newSfPath); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) + { + Unsafe.SkipInit(out entryType); + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + ref uint sfEntryType = ref Unsafe.As(ref entryType); + + return BaseFs.Target.GetEntryType(out sfEntryType, in sfPath); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) + { + Unsafe.SkipInit(out freeSpace); + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.GetFreeSpaceSize(out freeSpace, in sfPath); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) + { + Unsafe.SkipInit(out totalSpace); + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.GetTotalSpaceSize(out totalSpace, in sfPath); + } + + protected override Result DoOpenFile(out Fsa.IFile file, U8Span path, OpenMode mode) + { + file = default; + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable sfFile = null; + try + { + rc = BaseFs.Target.OpenFile(out sfFile, in sfPath, (uint)mode); + if (rc.IsFailure()) return rc; + + file = new FileServiceObjectAdapter(sfFile); + return Result.Success; + } + finally + { + sfFile?.Dispose(); + } + } + + protected override Result DoOpenDirectory(out Fsa.IDirectory directory, U8Span path, OpenDirectoryMode mode) + { + directory = default; + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable sfDir = null; + try + { + rc = BaseFs.Target.OpenDirectory(out sfDir, in sfPath, (uint)mode); + if (rc.IsFailure()) return rc; + + directory = new DirectoryServiceObjectAdapter(sfDir); + return Result.Success; + } + finally + { + sfDir?.Dispose(); + } + } + + protected override Result DoCommit() + { + return BaseFs.Target.Commit(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + { + Unsafe.SkipInit(out timeStamp); + + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.GetFileTimeStampRaw(out timeStamp, in sfPath); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + U8Span path) + { + Result rc = GetPathForServiceObject(out Path sfPath, path); + if (rc.IsFailure()) return rc; + + return BaseFs.Target.QueryEntry(outBuffer, inBuffer, (int)queryId, in sfPath); + } + + public ReferenceCountedDisposable GetMultiCommitTarget() + { + return BaseFs.AddReference(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseFs?.Dispose(); + } + + base.Dispose(disposing); + } + + private Result GetPathForServiceObject(out Path sfPath, U8Span path) + { + // This is the function used to create Sf.Path structs. Get an unsafe byte span for init only. + Unsafe.SkipInit(out sfPath); + Span outPath = SpanHelpers.AsByteSpan(ref sfPath); + + // Copy and null terminate + StringUtils.Copy(outPath, path); + outPath[Unsafe.SizeOf() - 1] = StringTraits.NullTerminator; + + // Replace directory separators + PathUtility.Replace(outPath, StringTraits.AltDirectorySeparator, StringTraits.DirectorySeparator); + + // Get lengths + int windowsSkipLength = PathUtility.GetWindowsPathSkipLength(path); + var nonWindowsPath = new U8Span(sfPath.Str.Slice(windowsSkipLength)); + int maxLength = PathTool.EntryNameLengthMax - windowsSkipLength; + return PathUtility.VerifyPath(nonWindowsPath, maxLength, maxLength); + } + } +} diff --git a/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs b/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs new file mode 100644 index 00000000..5911caf2 --- /dev/null +++ b/src/LibHac/Fs/Impl/ReaderWriterLockHandlers.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading; + +namespace LibHac.Fs.Impl +{ + /// + /// A wrapper for handling write access to a reader-writer lock. + /// + public class UniqueLock : IDisposable + { + private ReaderWriterLockSlim _lock; + private bool _hasLock; + + public UniqueLock(ReaderWriterLockSlim readerWriterLock) + { + _lock = readerWriterLock; + readerWriterLock.EnterWriteLock(); + _hasLock = true; + } + + public void Dispose() + { + if (_hasLock) + { + _lock.ExitWriteLock(); + } + } + } + + /// + /// A wrapper for handling read access to a reader-writer lock. + /// + public class SharedLock : IDisposable + { + private ReaderWriterLockSlim _lock; + private bool _hasLock; + + public SharedLock(ReaderWriterLockSlim readerWriterLock) + { + _lock = readerWriterLock; + readerWriterLock.EnterReadLock(); + _hasLock = true; + } + + public void Dispose() + { + if (_hasLock) + { + _lock.EnterReadLock(); + } + } + } +} diff --git a/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs new file mode 100644 index 00000000..0d4eafe7 --- /dev/null +++ b/src/LibHac/Fs/Impl/StorageServiceObjectAdapter.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.Fs.Impl +{ + /// + /// An adapter for using an service object as an . Used + /// when receiving a Horizon IPC storage object so it can be used as an locally. + /// + internal class StorageServiceObjectAdapter : IStorage + { + private ReferenceCountedDisposable BaseStorage { get; } + + public StorageServiceObjectAdapter(ReferenceCountedDisposable baseStorage) + { + BaseStorage = baseStorage.AddReference(); + } + protected override Result DoRead(long offset, Span destination) + { + return BaseStorage.Target.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + return BaseStorage.Target.Write(offset, source); + } + + protected override Result DoFlush() + { + return BaseStorage.Target.Flush(); + } + + protected override Result DoSetSize(long size) + { + return BaseStorage.Target.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + return BaseStorage.Target.GetSize(out size); + } + + protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + return BaseStorage.Target.OperateRange(out _, (int)OperationId.InvalidateCache, offset, size); + case OperationId.QueryRange: + if (outBuffer.Length != Unsafe.SizeOf()) + return ResultFs.InvalidSize.Log(); + + ref QueryRangeInfo info = ref SpanHelpers.AsStruct(outBuffer); + + return BaseStorage.Target.OperateRange(out info, (int)OperationId.QueryRange, offset, size); + default: + return ResultFs.UnsupportedOperationInFileServiceObjectAdapterA.Log(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseStorage?.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/LibHac/Fs/MountHelpers.cs b/src/LibHac/Fs/MountHelpers.cs index a91be5bb..b693509c 100644 --- a/src/LibHac/Fs/MountHelpers.cs +++ b/src/LibHac/Fs/MountHelpers.cs @@ -1,5 +1,5 @@ using LibHac.Common; -using static LibHac.Fs.CommonMountNames; +using static LibHac.Fs.CommonPaths; namespace LibHac.Fs { diff --git a/src/LibHac/Fs/PathUtility.cs b/src/LibHac/Fs/PathUtility.cs index 93af9103..23fd693f 100644 --- a/src/LibHac/Fs/PathUtility.cs +++ b/src/LibHac/Fs/PathUtility.cs @@ -19,6 +19,7 @@ namespace LibHac.Fs [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsWindowsDriveCharacter(byte c) { + // Mask lowercase letters to uppercase and check if it's in range return (0b1101_1111 & c) - 'A' <= 'Z' - 'A'; //return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'; } diff --git a/src/LibHac/Fs/QueryRangeInfo.cs b/src/LibHac/Fs/QueryRangeInfo.cs index 58473e6a..6cbb4bff 100644 --- a/src/LibHac/Fs/QueryRangeInfo.cs +++ b/src/LibHac/Fs/QueryRangeInfo.cs @@ -1,11 +1,31 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace LibHac.Fs { [StructLayout(LayoutKind.Sequential, Size = 0x40)] public struct QueryRangeInfo { - public uint AesCtrKeyType; - public uint SpeedEmulationType; + public int AesCtrKeyType; + public int SpeedEmulationType; + + public void Clear() + { + this = default; + } + + public void Merge(in QueryRangeInfo other) + { + AesCtrKeyType |= other.AesCtrKeyType; + SpeedEmulationType |= other.SpeedEmulationType; + } + + [Flags] + public enum AesCtrKeyTypeFlag + { + InternalKeyForSoftwareAes = 1 << 0, + InternalKeyForHardwareAes = 1 << 1, + ExternalKeyForHardwareAes = 1 << 2 + } } } diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index eaf025fb..c589dc6b 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -96,8 +96,16 @@ namespace LibHac.Fs /// Error code: 2002-3200; Range: 3200-3499; Inner value: 0x190002 public static Result.Base AllocationMemoryFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3200, 3499); } + /// In ParseNsp allocating FileStorageBasedFileSystem
Error code: 2002-3256; Inner value: 0x197002
+ public static Result.Base AllocationFailureInNcaFileSystemServiceImplA => new Result.Base(ModuleFs, 3256); + /// In ParseNca allocating FileStorageBasedFileSystem
Error code: 2002-3257; Inner value: 0x197202
+ public static Result.Base AllocationFailureInNcaFileSystemServiceImplB => new Result.Base(ModuleFs, 3257); /// In RegisterProgram allocating ProgramInfoNode
Error code: 2002-3258; Inner value: 0x197402
public static Result.Base AllocationFailureInProgramRegistryManagerA => new Result.Base(ModuleFs, 3258); + /// In Initialize allocating ProgramInfoNode
Error code: 2002-3264; Inner value: 0x198002
+ public static Result.Base AllocationFailureFatFileSystemA => new Result.Base(ModuleFs, 3264); + /// In Create allocating PartitionFileSystemCore
Error code: 2002-3280; Inner value: 0x19a002
+ public static Result.Base AllocationFailureInPartitionFileSystemCreatorA => new Result.Base(ModuleFs, 3280); /// In Initialize allocating FileStorage
Error code: 2002-3312; Inner value: 0x19e002
public static Result.Base AllocationFailureInAesXtsFileA => new Result.Base(ModuleFs, 3312); /// In Initialize allocating AesXtsStorage
Error code: 2002-3313; Inner value: 0x19e202
@@ -106,8 +114,34 @@ namespace LibHac.Fs public static Result.Base AllocationFailureInAesXtsFileC => new Result.Base(ModuleFs, 3314); /// In Initialize allocating StorageFile
Error code: 2002-3315; Inner value: 0x19e602
public static Result.Base AllocationFailureInAesXtsFileD => new Result.Base(ModuleFs, 3315); - /// In Initialize allocating SubStorage
Error code: 2002-3383; Inner value: 0x1a6e02
+ /// In Initialize allocating PartitionFileSystemMetaCore
Error code: 2002-3347; Inner value: 0x1a2602
+ public static Result.Base AllocationFailureInPartitionFileSystemA => new Result.Base(ModuleFs, 3347); + /// In DoOpenFile allocating PartitionFile
Error code: 2002-3348; Inner value: 0x1a2802
+ public static Result.Base AllocationFailureInPartitionFileSystemB => new Result.Base(ModuleFs, 3348); + /// In DoOpenDirectory allocating PartitionDirectory
Error code: 2002-3349; Inner value: 0x1a2a02
+ public static Result.Base AllocationFailureInPartitionFileSystemC => new Result.Base(ModuleFs, 3349); + /// In Initialize allocating metadata buffer
Error code: 2002-3350; Inner value: 0x1a2c02
+ public static Result.Base AllocationFailureInPartitionFileSystemMetaA => new Result.Base(ModuleFs, 3350); + /// In Sha256 Initialize allocating metadata buffer
Error code: 2002-3351; Inner value: 0x1a2e02
+ public static Result.Base AllocationFailureInPartitionFileSystemMetaB => new Result.Base(ModuleFs, 3351); + /// In Initialize allocating RootPathBuffer
Error code: 2002-3355; Inner value: 0x1a3602
+ public static Result.Base AllocationFailureInSubdirectoryFileSystemA => new Result.Base(ModuleFs, 3355); + /// In Initialize
Error code: 2002-3383; Inner value: 0x1a6e02
public static Result.Base AllocationFailureInAesXtsFileE => new Result.Base(ModuleFs, 3383); + /// In Create allocating AesXtsFileSystem
Error code: 2002-3394; Inner value: 0x1a8402
+ public static Result.Base AllocationFailureInEncryptedFileSystemCreatorA => new Result.Base(ModuleFs, 3394); + /// In OpenFile or OpenDirectory
Error code: 2002-3407; Inner value: 0x1a9e02
+ public static Result.Base AllocationFailureInFileSystemInterfaceAdapter => new Result.Base(ModuleFs, 3407); + /// Error code: 2002-3420; Inner value: 0x1ab802 + public static Result.Base AllocationFailureInNew => new Result.Base(ModuleFs, 3420); + /// Error code: 2002-3421; Inner value: 0x1aba02 + public static Result.Base AllocationFailureInCreateShared => new Result.Base(ModuleFs, 3421); + /// Error code: 2002-3422; Inner value: 0x1abc02 + public static Result.Base AllocationFailureInMakeUnique => new Result.Base(ModuleFs, 3422); + /// Error code: 2002-3423; Inner value: 0x1abe02 + public static Result.Base AllocationFailureInAllocateShared => new Result.Base(ModuleFs, 3423); + /// Error code: 2002-3424; Inner value: 0x1ac002 + public static Result.Base AllocationFailurePooledBufferNotEnoughSize => new Result.Base(ModuleFs, 3424); /// Error code: 2002-3500; Range: 3500-3999; Inner value: 0x1b5802 public static Result.Base MmcAccessFailed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 3500, 3999); } @@ -205,9 +239,9 @@ namespace LibHac.Fs /// Error code: 2002-4501; Range: 4501-4599; Inner value: 0x232a02 public static Result.Base NcaCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4501, 4599); } /// Error code: 2002-4512; Inner value: 0x234002 - public static Result.Base InvalidNcaFsType => new Result.Base(ModuleFs, 4512); + public static Result.Base InvalidNcaFileSystemType => new Result.Base(ModuleFs, 4512); /// Error code: 2002-4527; Inner value: 0x235e02 - public static Result.Base InvalidNcaProgramId => new Result.Base(ModuleFs, 4527); + public static Result.Base InvalidNcaId => new Result.Base(ModuleFs, 4527); /// Error code: 2002-4601; Range: 4601-4639; Inner value: 0x23f202 public static Result.Base IntegrityVerificationStorageCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4601, 4639); } @@ -305,6 +339,8 @@ namespace LibHac.Fs /// Error code: 2002-5000; Range: 5000-5999; Inner value: 0x271002 public static Result.Base Unexpected { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 5000, 5999); } + /// Error code: 2002-5121; Inner value: 0x280202 + public static Result.Base UnexpectedFatFileSystemSectorCount => new Result.Base(ModuleFs, 5121); /// Error code: 2002-5307; Inner value: 0x297602 public static Result.Base UnexpectedErrorInHostFileFlush => new Result.Base(ModuleFs, 5307); /// Error code: 2002-5308; Inner value: 0x297802 @@ -336,7 +372,7 @@ namespace LibHac.Fs /// Error code: 2002-6031; Inner value: 0x2f1e02 public static Result.Base DirectoryNotDeletable => new Result.Base(ModuleFs, 6031); /// Error code: 2002-6032; Inner value: 0x2f2002 - public static Result.Base DestinationIsSubPathOfSource => new Result.Base(ModuleFs, 6032); + public static Result.Base DirectoryNotRenamable => new Result.Base(ModuleFs, 6032); /// Error code: 2002-6033; Inner value: 0x2f2202 public static Result.Base PathNotFoundInSaveDataFileTable => new Result.Base(ModuleFs, 6033); /// Error code: 2002-6034; Inner value: 0x2f2402 @@ -387,6 +423,8 @@ namespace LibHac.Fs /// Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802 public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); } + /// Error code: 2002-6301; Inner value: 0x313a02 + public static Result.Base UnsupportedCommitTarget => new Result.Base(ModuleFs, 6301); /// Attempted to resize a non-resizable SubStorage.
Error code: 2002-6302; Inner value: 0x313c02
public static Result.Base UnsupportedOperationInSubStorageSetSize => new Result.Base(ModuleFs, 6302); /// Attempted to resize a SubStorage that wasn't located at the end of the base storage.
Error code: 2002-6303; Inner value: 0x313e02
@@ -409,6 +447,8 @@ namespace LibHac.Fs public static Result.Base UnsupportedOperationInRoGameCardStorageSetSize => new Result.Base(ModuleFs, 6351); /// Error code: 2002-6359; Inner value: 0x31ae02 public static Result.Base UnsupportedOperationInConcatFsQueryEntry => new Result.Base(ModuleFs, 6359); + /// Called OperateRange with an invalid operation ID.
Error code: 2002-6362; Inner value: 0x31b402
+ public static Result.Base UnsupportedOperationInFileServiceObjectAdapterA => new Result.Base(ModuleFs, 6362); /// Error code: 2002-6364; Inner value: 0x31b802 public static Result.Base UnsupportedOperationModifyRomFsFileSystem => new Result.Base(ModuleFs, 6364); /// Called RomFsFileSystem::CommitProvisionally.
Error code: 2002-6365; Inner value: 0x31ba02
@@ -443,14 +483,24 @@ namespace LibHac.Fs public static Result.Base ExternalKeyAlreadyRegistered => new Result.Base(ModuleFs, 6452); /// Error code: 2002-6454; Inner value: 0x326c02 public static Result.Base WriteStateUnflushed => new Result.Base(ModuleFs, 6454); + /// Error code: 2002-6456; Inner value: 0x327002 + public static Result.Base DirectoryNotClosed => new Result.Base(ModuleFs, 6456); /// Error code: 2002-6457; Inner value: 0x327202 public static Result.Base WriteModeFileNotClosed => new Result.Base(ModuleFs, 6457); + /// Error code: 2002-6458; Inner value: 0x327402 + public static Result.Base AllocatorAlreadyRegistered => new Result.Base(ModuleFs, 6458); + /// Error code: 2002-6459; Inner value: 0x327602 + public static Result.Base DefaultAllocatorUsed => new Result.Base(ModuleFs, 6459); /// Error code: 2002-6461; Inner value: 0x327a02 public static Result.Base AllocatorAlignmentViolation => new Result.Base(ModuleFs, 6461); /// The provided file system has already been added to the multi-commit manager.
Error code: 2002-6463; Inner value: 0x327e02
public static Result.Base MultiCommitFileSystemAlreadyAdded => new Result.Base(ModuleFs, 6463); /// Error code: 2002-6465; Inner value: 0x328202 public static Result.Base UserNotExist => new Result.Base(ModuleFs, 6465); + /// Error code: 2002-6466; Inner value: 0x328402 + public static Result.Base DefaultGlobalFileDataCacheEnabled => new Result.Base(ModuleFs, 6466); + /// Error code: 2002-6467; Inner value: 0x328602 + public static Result.Base SaveDataRootPathUnavailable => new Result.Base(ModuleFs, 6467); /// Error code: 2002-6600; Range: 6600-6699; Inner value: 0x339002 public static Result.Base EntryNotFound { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6600, 6699); } diff --git a/src/LibHac/Fs/SaveDataStructs.cs b/src/LibHac/Fs/SaveDataStructs.cs index ed13769a..4efbc685 100644 --- a/src/LibHac/Fs/SaveDataStructs.cs +++ b/src/LibHac/Fs/SaveDataStructs.cs @@ -1,7 +1,9 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Ncm; +using LibHac.Util; namespace LibHac.Fs { @@ -13,17 +15,17 @@ namespace LibHac.Fs [FieldOffset(0x18)] public ulong StaticSaveDataId; [FieldOffset(0x20)] public SaveDataType Type; [FieldOffset(0x21)] public SaveDataRank Rank; - [FieldOffset(0x22)] public short Index; + [FieldOffset(0x22)] public ushort Index; public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId) : this( programId, type, userId, saveDataId, 0, SaveDataRank.Primary) { } public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, - short index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary) + ushort index) : this(programId, type, userId, saveDataId, index, SaveDataRank.Primary) { } - public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, short index, + public SaveDataAttribute(ProgramId programId, SaveDataType type, UserId userId, ulong saveDataId, ushort index, SaveDataRank rank) { ProgramId = programId; @@ -34,6 +36,38 @@ namespace LibHac.Fs Rank = rank; } + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId) + { + return Make(out attribute, programId, type, userId, staticSaveDataId, 0, SaveDataRank.Primary); + } + + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId, ushort index) + { + return Make(out attribute, programId, type, userId, staticSaveDataId, index, SaveDataRank.Primary); + } + + public static Result Make(out SaveDataAttribute attribute, ProgramId programId, SaveDataType type, + UserId userId, ulong staticSaveDataId, ushort index, SaveDataRank rank) + { + Unsafe.SkipInit(out attribute); + SaveDataAttribute tempAttribute = default; + + tempAttribute.ProgramId = programId; + tempAttribute.Type = type; + tempAttribute.UserId = userId; + tempAttribute.StaticSaveDataId = staticSaveDataId; + tempAttribute.Index = index; + tempAttribute.Rank = rank; + + if (!SaveDataTypesValidity.IsValid(in tempAttribute)) + return ResultFs.InvalidArgument.Log(); + + attribute = tempAttribute; + return Result.Success; + } + public override readonly bool Equals(object obj) { return obj is SaveDataAttribute attribute && Equals(attribute); @@ -85,40 +119,96 @@ namespace LibHac.Fs [FieldOffset(0x04)] public bool FilterByIndex; [FieldOffset(0x05)] public SaveDataRank Rank; - [FieldOffset(0x08)] public ProgramId ProgramId; - [FieldOffset(0x10)] public UserId UserId; - [FieldOffset(0x20)] public ulong SaveDataId; - [FieldOffset(0x28)] public SaveDataType SaveDataType; - [FieldOffset(0x2A)] public short Index; + [FieldOffset(0x08)] public SaveDataAttribute Attribute; public void SetProgramId(ProgramId value) { FilterByProgramId = true; - ProgramId = value; + Attribute.ProgramId = value; } public void SetSaveDataType(SaveDataType value) { FilterBySaveDataType = true; - SaveDataType = value; + Attribute.Type = value; } public void SetUserId(UserId value) { FilterByUserId = true; - UserId = value; + Attribute.UserId = value; } public void SetSaveDataId(ulong value) { FilterBySaveDataId = true; - SaveDataId = value; + Attribute.StaticSaveDataId = value; } - public void SetIndex(short value) + public void SetIndex(ushort value) { FilterByIndex = true; - Index = value; + Attribute.Index = value; + } + + public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index) + { + return Make(out filter, programId, saveType, userId, saveDataId, index, SaveDataRank.Primary); + } + + public static Result Make(out SaveDataFilter filter, Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) + { + Unsafe.SkipInit(out filter); + + SaveDataFilter tempFilter = Make(programId, saveType, userId, saveDataId, index, rank); + + if (!SaveDataTypesValidity.IsValid(in tempFilter)) + return ResultFs.InvalidArgument.Log(); + + filter = tempFilter; + return Result.Success; + } + + public static SaveDataFilter Make(Optional programId, Optional saveType, + Optional userId, Optional saveDataId, Optional index, SaveDataRank rank) + { + var filter = new SaveDataFilter(); + + if (programId.HasValue) + { + filter.FilterByProgramId = true; + filter.Attribute.ProgramId = new ProgramId(programId.Value); + } + + if (saveType.HasValue) + { + filter.FilterBySaveDataType = true; + filter.Attribute.Type = saveType.Value; + } + + if (userId.HasValue) + { + filter.FilterByUserId = true; + filter.Attribute.UserId = userId.Value; + } + + if (saveDataId.HasValue) + { + filter.FilterBySaveDataId = true; + filter.Attribute.StaticSaveDataId = saveDataId.Value; + } + + if (index.HasValue) + { + filter.FilterByIndex = true; + filter.Attribute.Index = index.Value; + } + + filter.Rank = rank; + + return filter; } } @@ -130,6 +220,7 @@ namespace LibHac.Fs [FieldOffset(0x00)] private byte _hashStart; public Span Hash => SpanHelpers.CreateSpan(ref _hashStart, HashLength); + public ReadOnlySpan HashRo => SpanHelpers.CreateReadOnlySpan(in _hashStart, HashLength); } [StructLayout(LayoutKind.Sequential, Pack = 1)] @@ -140,7 +231,7 @@ namespace LibHac.Fs } [StructLayout(LayoutKind.Explicit, Size = 0x10)] - public struct SaveMetaCreateInfo + public struct SaveDataMetaInfo { [FieldOffset(0)] public int Size; [FieldOffset(4)] public SaveDataMetaType Type; @@ -151,7 +242,7 @@ namespace LibHac.Fs { [FieldOffset(0x00)] public long Size; [FieldOffset(0x08)] public long JournalSize; - [FieldOffset(0x10)] public ulong BlockSize; + [FieldOffset(0x10)] public long BlockSize; [FieldOffset(0x18)] public ulong OwnerId; [FieldOffset(0x20)] public SaveDataFlags Flags; [FieldOffset(0x24)] public SaveDataSpaceId SpaceId; @@ -168,8 +259,66 @@ namespace LibHac.Fs [FieldOffset(0x20)] public ulong StaticSaveDataId; [FieldOffset(0x28)] public ProgramId ProgramId; [FieldOffset(0x30)] public long Size; - [FieldOffset(0x38)] public short Index; + [FieldOffset(0x38)] public ushort Index; [FieldOffset(0x3A)] public SaveDataRank Rank; [FieldOffset(0x3B)] public SaveDataState State; } + + [StructLayout(LayoutKind.Explicit, Size = 0x200)] + public struct SaveDataExtraData + { + [FieldOffset(0x00)] public SaveDataAttribute Attribute; + [FieldOffset(0x40)] public ulong OwnerId; + [FieldOffset(0x48)] public ulong TimeStamp; + [FieldOffset(0x50)] public SaveDataFlags Flags; + [FieldOffset(0x58)] public long DataSize; + [FieldOffset(0x60)] public long JournalSize; + [FieldOffset(0x68)] public long CommitId; + } + + internal static class SaveDataTypesValidity + { + public static bool IsValid(in SaveDataAttribute attribute) + { + return IsValid(in attribute.Type) && IsValid(in attribute.Rank); + } + + public static bool IsValid(in SaveDataCreationInfo creationInfo) + { + return creationInfo.Size >= 0 && creationInfo.JournalSize >= 0 && creationInfo.BlockSize >= 0 && + IsValid(in creationInfo.SpaceId); + } + + public static bool IsValid(in SaveDataMetaInfo metaInfo) + { + return IsValid(in metaInfo.Type); + } + + public static bool IsValid(in SaveDataFilter filter) + { + return IsValid(in filter.Attribute); + } + + public static bool IsValid(in SaveDataType type) + { + // SaveDataType.SystemBcat is excluded in this check + return (uint)type <= (uint)SaveDataType.Cache; + } + + public static bool IsValid(in SaveDataRank rank) + { + return (uint)rank <= (uint)SaveDataRank.Secondary; + } + + public static bool IsValid(in SaveDataSpaceId spaceId) + { + return (uint)spaceId <= (uint)SaveDataSpaceId.SdCache || spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.SafeMode; + } + + public static bool IsValid(in SaveDataMetaType metaType) + { + return (uint)metaType <= (uint)SaveDataMetaType.ExtensionContext; + } + } } diff --git a/src/LibHac/Fs/SdCardAccessLog.cs b/src/LibHac/Fs/SdCardAccessLog.cs index b5494ab9..35712aa0 100644 --- a/src/LibHac/Fs/SdCardAccessLog.cs +++ b/src/LibHac/Fs/SdCardAccessLog.cs @@ -4,7 +4,7 @@ using LibHac.FsSrv; namespace LibHac.Fs { /// - /// The default access logger that will output to the SD card via . + /// The default access logger that will output to the SD card via . /// public class SdCardAccessLog : IAccessLog { diff --git a/src/LibHac/Fs/Shim/Application.cs b/src/LibHac/Fs/Shim/Application.cs index 6da9840c..50ceb910 100644 --- a/src/LibHac/Fs/Shim/Application.cs +++ b/src/LibHac/Fs/Shim/Application.cs @@ -1,8 +1,9 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; -using LibHac.FsSystem; +using LibHac.FsSrv.Sf; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -40,14 +41,20 @@ namespace LibHac.Fs.Shim Result rc = MountHelpers.CheckMountName(mountName); if (rc.IsFailure()) return rc; - FsPath.FromSpan(out FsPath fsPath, path); + FspPath.FromSpan(out FspPath sfPath, path); IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenFileSystemWithId(out IFileSystem fileSystem, ref fsPath, default, FileSystemProxyType.Package); + rc = fsProxy.OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in sfPath, + default, FileSystemProxyType.Package); if (rc.IsFailure()) return rc; - return fs.Register(mountName, fileSystem); + using (fileSystem) + { + var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem); + + return fs.Register(mountName, fileSystemAdapter); + } } } } diff --git a/src/LibHac/Fs/Shim/BcatSaveData.cs b/src/LibHac/Fs/Shim/BcatSaveData.cs index aba42d96..7a4c4d83 100644 --- a/src/LibHac/Fs/Shim/BcatSaveData.cs +++ b/src/LibHac/Fs/Shim/BcatSaveData.cs @@ -1,7 +1,8 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -43,12 +44,23 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.Zero, 0); + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.InvalidId, 0); - rc = fsProxy.OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId.User, ref attribute); - if (rc.IsFailure()) return rc; + ReferenceCountedDisposable saveFs = null; - return fs.Register(mountName, fileSystem); + try + { + rc = fsProxy.OpenSaveDataFileSystem(out saveFs, SaveDataSpaceId.User, in attribute); + if (rc.IsFailure()) return rc; + + var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs); + + return fs.Register(mountName, fileSystemAdapter); + } + finally + { + saveFs?.Dispose(); + } } } } diff --git a/src/LibHac/Fs/Shim/Bis.cs b/src/LibHac/Fs/Shim/Bis.cs index 3cae5f21..8f1f164e 100644 --- a/src/LibHac/Fs/Shim/Bis.cs +++ b/src/LibHac/Fs/Shim/Bis.cs @@ -1,11 +1,15 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.Util; -using static LibHac.Fs.CommonMountNames; +using static LibHac.Fs.CommonPaths; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; namespace LibHac.Fs.Shim { @@ -80,25 +84,28 @@ namespace LibHac.Fs.Shim } // ReSharper disable once UnusedParameter.Local - private static Result MountBisImpl(FileSystemClient fs, U8Span mountName, BisPartitionId partitionId, U8Span rootPath) + private static Result MountBisImpl(FileSystemClient fs, U8Span mountName, BisPartitionId partitionId, + U8Span rootPath) { Result rc = MountHelpers.CheckMountNameAcceptingReservedMountName(mountName); if (rc.IsFailure()) return rc; - FsPath sfPath; - unsafe { _ = &sfPath; } // workaround for CS0165 - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); // Nintendo doesn't use the provided rootPath - sfPath.Str[0] = 0; + FspPath.CreateEmpty(out FspPath sfPath); - rc = fsProxy.OpenBisFileSystem(out IFileSystem fileSystem, ref sfPath, partitionId); + rc = fsProxy.OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in sfPath, + partitionId); if (rc.IsFailure()) return rc; - var nameGenerator = new BisCommonMountNameGenerator(partitionId); + using (fileSystem) + { + var nameGenerator = new BisCommonMountNameGenerator(partitionId); + var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem); - return fs.Register(mountName, fileSystem, nameGenerator); + return fs.Register(mountName, fileSystemAdapter, nameGenerator); + } } public static U8Span GetBisMountName(BisPartitionId partitionId) @@ -134,8 +141,7 @@ namespace LibHac.Fs.Shim // todo: Decide how to handle SetBisRootForHost since it allows mounting any directory on the user's computer public static Result SetBisRootForHost(this FileSystemClient fs, BisPartitionId partitionId, U8Span rootPath) { - FsPath sfPath; - unsafe { _ = &sfPath; } // workaround for CS0165 + Unsafe.SkipInit(out FsPath path); int pathLen = StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1); if (pathLen > PathTools.MaxPathLength) @@ -147,30 +153,38 @@ namespace LibHac.Fs.Shim ? StringTraits.NullTerminator : StringTraits.DirectorySeparator; - var sb = new U8StringBuilder(sfPath.Str); + var sb = new U8StringBuilder(path.Str); Result rc = sb.Append(rootPath).Append(endingSeparator).ToSfPath(); if (rc.IsFailure()) return rc; } else { - sfPath.Str[0] = StringTraits.NullTerminator; + path.Str[0] = StringTraits.NullTerminator; } + FspPath.FromSpan(out FspPath sfPath, path.Str); + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - return fsProxy.SetBisRootForHost(partitionId, ref sfPath); + return fsProxy.SetBisRootForHost(partitionId, in sfPath); } - public static Result OpenBisPartition(this FileSystemClient fs, out IStorage partitionStorage, BisPartitionId partitionId) + public static Result OpenBisPartition(this FileSystemClient fs, out IStorage partitionStorage, + BisPartitionId partitionId) { partitionStorage = default; IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenBisStorage(out IStorage storage, partitionId); + Result rc = fsProxy.OpenBisStorage(out ReferenceCountedDisposable storage, partitionId); if (rc.IsFailure()) return rc; - partitionStorage = storage; - return Result.Success; + using (storage) + { + var storageAdapter = new StorageServiceObjectAdapter(storage); + + partitionStorage = storageAdapter; + return Result.Success; + } } public static Result InvalidateBisCache(this FileSystemClient fs) diff --git a/src/LibHac/Fs/Shim/Code.cs b/src/LibHac/Fs/Shim/Code.cs index 08c6df51..2df86927 100644 --- a/src/LibHac/Fs/Shim/Code.cs +++ b/src/LibHac/Fs/Shim/Code.cs @@ -1,9 +1,10 @@ using System; using System.Runtime.CompilerServices; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv.Sf; using LibHac.Ncm; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -49,10 +50,16 @@ namespace LibHac.Fs.Shim IFileSystemProxyForLoader fsProxy = fs.GetFileSystemProxyForLoaderServiceObject(); - rc = fsProxy.OpenCodeFileSystem(out IFileSystem codeFs, out verificationData, in fsPath, programId); + rc = fsProxy.OpenCodeFileSystem(out ReferenceCountedDisposable codeFs, out verificationData, + in fsPath, programId); if (rc.IsFailure()) return rc; - return fs.Register(mountName, codeFs); + using (codeFs) + { + var fileSystemAdapter = new FileSystemServiceObjectAdapter(codeFs); + + return fs.Register(mountName, fileSystemAdapter); + } } } } diff --git a/src/LibHac/Fs/Shim/Content.cs b/src/LibHac/Fs/Shim/Content.cs index a6e96bb6..ffc8d290 100644 --- a/src/LibHac/Fs/Shim/Content.cs +++ b/src/LibHac/Fs/Shim/Content.cs @@ -1,9 +1,10 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; -using LibHac.FsSystem; +using LibHac.FsSrv.Sf; using LibHac.Ncm; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -27,10 +28,16 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenFileSystemWithPatch(out IFileSystem fileSystem, programId, fspType); + rc = fsProxy.OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, programId, + fspType); if (rc.IsFailure()) return rc; - return fs.Register(mountName, fileSystem); + using (fileSystem) + { + var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem); + + return fs.Register(mountName, fileSystemAdapter); + } } public static Result MountContent(this FileSystemClient fs, U8Span mountName, U8Span path, ProgramId programId, ContentType type) @@ -55,14 +62,20 @@ namespace LibHac.Fs.Shim private static Result MountContentImpl(FileSystemClient fs, U8Span mountName, U8Span path, ulong id, FileSystemProxyType type) { - FsPath.FromSpan(out FsPath fsPath, path); + FspPath.FromSpan(out FspPath fsPath, path); IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenFileSystemWithId(out IFileSystem fileSystem, ref fsPath, id, type); + Result rc = fsProxy.OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in fsPath, + id, type); if (rc.IsFailure()) return rc; - return fs.Register(mountName, fileSystem); + using (fileSystem) + { + var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem); + + return fs.Register(mountName, fileSystemAdapter); + } } private static FileSystemProxyType ConvertToFileSystemProxyType(ContentType type) => type switch @@ -71,7 +84,7 @@ namespace LibHac.Fs.Shim ContentType.Control => FileSystemProxyType.Control, ContentType.Manual => FileSystemProxyType.Manual, ContentType.Data => FileSystemProxyType.Data, - _ => throw new ArgumentOutOfRangeException(nameof(type), type, null), + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) }; } } diff --git a/src/LibHac/Fs/Shim/ContentStorage.cs b/src/LibHac/Fs/Shim/ContentStorage.cs index 7283cc77..64ac316b 100644 --- a/src/LibHac/Fs/Shim/ContentStorage.cs +++ b/src/LibHac/Fs/Shim/ContentStorage.cs @@ -1,8 +1,9 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; using LibHac.Util; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -20,12 +21,17 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenContentStorageFileSystem(out IFileSystem contentFs, storageId); + rc = fsProxy.OpenContentStorageFileSystem(out ReferenceCountedDisposable contentFs, storageId); if (rc.IsFailure()) return rc; - var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId); + using (contentFs) + { + var mountNameGenerator = new ContentStorageCommonMountNameGenerator(storageId); - return fs.Register(mountName, contentFs, mountNameGenerator); + var fileSystemAdapter = new FileSystemServiceObjectAdapter(contentFs); + + return fs.Register(mountName, fileSystemAdapter, mountNameGenerator); + } } public static U8String GetContentStorageMountName(ContentStorageId storageId) @@ -33,11 +39,11 @@ namespace LibHac.Fs.Shim switch (storageId) { case ContentStorageId.System: - return CommonMountNames.ContentStorageSystemMountName; + return CommonPaths.ContentStorageSystemMountName; case ContentStorageId.User: - return CommonMountNames.ContentStorageUserMountName; + return CommonPaths.ContentStorageUserMountName; case ContentStorageId.SdCard: - return CommonMountNames.ContentStorageSdCardMountName; + return CommonPaths.ContentStorageSdCardMountName; default: throw new ArgumentOutOfRangeException(nameof(storageId), storageId, null); } diff --git a/src/LibHac/Fs/Shim/CustomStorage.cs b/src/LibHac/Fs/Shim/CustomStorage.cs index 4734acbd..0df018ae 100644 --- a/src/LibHac/Fs/Shim/CustomStorage.cs +++ b/src/LibHac/Fs/Shim/CustomStorage.cs @@ -1,7 +1,8 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -12,12 +13,22 @@ namespace LibHac.Fs.Shim Result rc = MountHelpers.CheckMountName(mountName); if (rc.IsFailure()) return rc; - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + ReferenceCountedDisposable customFs = null; + try + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenCustomStorageFileSystem(out IFileSystem customFs, storageId); - if (rc.IsFailure()) return rc; + rc = fsProxy.OpenCustomStorageFileSystem(out customFs, storageId); + if (rc.IsFailure()) return rc; - return fs.Register(mountName, customFs); + var adapter = new FileSystemServiceObjectAdapter(customFs); + + return fs.Register(mountName, adapter); + } + finally + { + customFs?.Dispose(); + } } public static string GetCustomStorageDirectoryName(CustomStorageId storageId) diff --git a/src/LibHac/Fs/Shim/GameCard.cs b/src/LibHac/Fs/Shim/GameCard.cs index a63a89bc..d8071058 100644 --- a/src/LibHac/Fs/Shim/GameCard.cs +++ b/src/LibHac/Fs/Shim/GameCard.cs @@ -1,7 +1,10 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; namespace LibHac.Fs.Shim { @@ -11,33 +14,63 @@ namespace LibHac.Fs.Shim { handle = default; - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + ReferenceCountedDisposable deviceOperator = null; + try + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator); - if (rc.IsFailure()) return rc; + Result rc = fsProxy.OpenDeviceOperator(out deviceOperator); + if (rc.IsFailure()) return rc; - return deviceOperator.GetGameCardHandle(out handle); + return deviceOperator.Target.GetGameCardHandle(out handle); + } + finally + { + deviceOperator?.Dispose(); + } } public static bool IsGameCardInserted(this FileSystemClient fs) { - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + ReferenceCountedDisposable deviceOperator = null; + try + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator); - if (rc.IsFailure()) throw new LibHacException("Abort"); + Result rc = fsProxy.OpenDeviceOperator(out deviceOperator); + if (rc.IsFailure()) throw new LibHacException("Abort"); - rc = deviceOperator.IsGameCardInserted(out bool isInserted); - if (rc.IsFailure()) throw new LibHacException("Abort"); + rc = deviceOperator.Target.IsGameCardInserted(out bool isInserted); + if (rc.IsFailure()) throw new LibHacException("Abort"); - return isInserted; + return isInserted; + } + finally + { + deviceOperator?.Dispose(); + } } public static Result OpenGameCardPartition(this FileSystemClient fs, out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionType) { - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + storage = default; - return fsProxy.OpenGameCardStorage(out storage, handle, partitionType); + ReferenceCountedDisposable sfStorage = null; + try + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + + Result rc = fsProxy.OpenGameCardStorage(out sfStorage, handle, partitionType); + if (rc.IsFailure()) return rc; + + storage = new StorageServiceObjectAdapter(sfStorage); + return Result.Success; + } + finally + { + sfStorage?.Dispose(); + } } public static Result MountGameCardPartition(this FileSystemClient fs, U8Span mountName, GameCardHandle handle, @@ -48,12 +81,16 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenGameCardFileSystem(out IFileSystem cardFs, handle, partitionId); + rc = fsProxy.OpenGameCardFileSystem(out ReferenceCountedDisposable cardFs, handle, partitionId); if (rc.IsFailure()) return rc; - var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId); + using (cardFs) + { + var mountNameGenerator = new GameCardCommonMountNameGenerator(handle, partitionId); + var fileSystemAdapter = new FileSystemServiceObjectAdapter(cardFs); - return fs.Register(mountName, cardFs, mountNameGenerator); + return fs.Register(mountName, fileSystemAdapter, mountNameGenerator); + } } private class GameCardCommonMountNameGenerator : ICommonMountNameGenerator @@ -71,7 +108,7 @@ namespace LibHac.Fs.Shim { char letter = GetGameCardMountNameSuffix(PartitionId); - string mountName = $"{CommonMountNames.GameCardFileSystemMountName}{letter}{Handle.Value:x8}"; + string mountName = $"{CommonPaths.GameCardFileSystemMountName}{letter}{Handle.Value:x8}"; new U8Span(mountName).Value.CopyTo(nameBuffer); return Result.Success; diff --git a/src/LibHac/Fs/Shim/Host.cs b/src/LibHac/Fs/Shim/Host.cs index e75080df..bae6e674 100644 --- a/src/LibHac/Fs/Shim/Host.cs +++ b/src/LibHac/Fs/Shim/Host.cs @@ -2,11 +2,14 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; using LibHac.FsSystem; using LibHac.Util; -using static LibHac.Fs.CommonMountNames; +using static LibHac.Fs.CommonPaths; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -76,12 +79,11 @@ namespace LibHac.Fs.Shim public static Result MountHostRoot(this FileSystemClient fs) { IFileSystem hostFileSystem = default; - var path = new FsPath(); - path.Str[0] = 0; + FspPath.CreateEmpty(out FspPath path); static string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}\""; - Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, MountHostOption.None); + Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, in path, MountHostOption.None); Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem, new HostRootCommonMountNameGenerator()); @@ -110,13 +112,12 @@ namespace LibHac.Fs.Shim public static Result MountHostRoot(this FileSystemClient fs, MountHostOption option) { IFileSystem hostFileSystem = default; - var path = new FsPath(); - path.Str[0] = 0; + FspPath.CreateEmpty(out FspPath path); string LogMessageGenerator() => $", name: \"{HostRootFileSystemMountName.ToString()}, mount_host_option: {option}\""; - Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, ref path, option); + Result OpenHostFs() => OpenHostFileSystemImpl(fs, out hostFileSystem, in path, option); Result MountHostFs() => fs.Register(HostRootFileSystemMountName, hostFileSystem, new HostRootCommonMountNameGenerator()); @@ -318,8 +319,7 @@ namespace LibHac.Fs.Shim if (pathLength + 1 > PathTools.MaxPathLength) return ResultFs.TooLongPath.Log(); - FsPath fullPath; - unsafe { _ = &fullPath; } // workaround for CS0165 + Unsafe.SkipInit(out FsPath fullPath); var sb = new U8StringBuilder(fullPath.Str); sb.Append(StringTraits.DirectorySeparator).Append(path); @@ -341,7 +341,9 @@ namespace LibHac.Fs.Shim } } - return OpenHostFileSystemImpl(fs, out fileSystem, ref fullPath, option); + FspPath.FromSpan(out FspPath sfPath, fullPath.Str); + + return OpenHostFileSystemImpl(fs, out fileSystem, in sfPath, option); } /// @@ -352,26 +354,34 @@ namespace LibHac.Fs.Shim /// The path on the host computer to open. e.g. /C:\Windows\System32/ /// Options for opening the host file system. /// The of the operation. - private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, ref FsPath path, MountHostOption option) + private static Result OpenHostFileSystemImpl(FileSystemClient fs, out IFileSystem fileSystem, in FspPath path, + MountHostOption option) { fileSystem = default; IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - IFileSystem hostFs; + ReferenceCountedDisposable hostFs = null; - if (option == MountHostOption.None) + try { - Result rc = fsProxy.OpenHostFileSystem(out hostFs, ref path); - if (rc.IsFailure()) return rc; - } - else - { - Result rc = fsProxy.OpenHostFileSystemWithOption(out hostFs, ref path, option); - if (rc.IsFailure()) return rc; - } + if (option == MountHostOption.None) + { + Result rc = fsProxy.OpenHostFileSystem(out hostFs, in path); + if (rc.IsFailure()) return rc; + } + else + { + Result rc = fsProxy.OpenHostFileSystemWithOption(out hostFs, in path, option); + if (rc.IsFailure()) return rc; + } - fileSystem = hostFs; - return Result.Success; + fileSystem = new FileSystemServiceObjectAdapter(hostFs); + return Result.Success; + } + finally + { + hostFs?.Dispose(); + } } } } diff --git a/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs b/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs index 241f9684..a6a4625a 100644 --- a/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs +++ b/src/LibHac/Fs/Shim/ProgramIndexMapInfo.cs @@ -1,6 +1,7 @@ using System; using System.Runtime.InteropServices; using LibHac.FsSrv; +using LibHac.Sf; namespace LibHac.Fs.Shim { @@ -21,7 +22,7 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - ReadOnlySpan mapInfoBuffer = MemoryMarshal.Cast(mapInfo); + var mapInfoBuffer = new InBuffer(MemoryMarshal.Cast(mapInfo)); return fsProxy.RegisterProgramIndexMapInfo(mapInfoBuffer, mapInfo.Length); } diff --git a/src/LibHac/Fs/Shim/ProgramRegistry.cs b/src/LibHac/Fs/Shim/ProgramRegistry.cs index 3dc37ac0..c4bdced5 100644 --- a/src/LibHac/Fs/Shim/ProgramRegistry.cs +++ b/src/LibHac/Fs/Shim/ProgramRegistry.cs @@ -2,6 +2,7 @@ using LibHac.FsSrv; using LibHac.FsSrv.Sf; using LibHac.Ncm; +using LibHac.Sf; namespace LibHac.Fs.Shim { @@ -16,8 +17,8 @@ namespace LibHac.Fs.Shim Result rc = registry.SetCurrentProcess(fs.Hos.ProcessId.Value); if (rc.IsFailure()) return rc; - return registry.RegisterProgram(processId, programId, storageId, accessControlData, - accessControlDescriptor); + return registry.RegisterProgram(processId, programId, storageId, new InBuffer(accessControlData), + new InBuffer(accessControlDescriptor)); } /// diff --git a/src/LibHac/Fs/Shim/RightsId.cs b/src/LibHac/Fs/Shim/RightsId.cs index 574ee5b1..28661d59 100644 --- a/src/LibHac/Fs/Shim/RightsId.cs +++ b/src/LibHac/Fs/Shim/RightsId.cs @@ -1,6 +1,6 @@ using LibHac.Common; using LibHac.FsSrv; -using LibHac.FsSystem; +using LibHac.FsSrv.Sf; using LibHac.Ncm; using LibHac.Spl; using FsRightsId = LibHac.Fs.RightsId; @@ -23,10 +23,10 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = FsPath.FromSpan(out FsPath fsPath, path); + Result rc = FspPath.FromSpan(out FspPath sfPath, path); if (rc.IsFailure()) return rc; - return fsProxy.GetRightsIdByPath(out rightsId, ref fsPath); + return fsProxy.GetRightsIdByPath(out rightsId, in sfPath); } public static Result GetRightsId(this FileSystemClient fs, out FsRightsId rightsId, out byte keyGeneration, U8Span path) @@ -36,24 +36,24 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = FsPath.FromSpan(out FsPath fsPath, path); + Result rc = FspPath.FromSpan(out FspPath sfPath, path); if (rc.IsFailure()) return rc; - return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, ref fsPath); + return fsProxy.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in sfPath); } - public static Result RegisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId, ref AccessKey key) + public static Result RegisterExternalKey(this FileSystemClient fs, in FsRightsId rightsId, in AccessKey key) { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - return fsProxy.RegisterExternalKey(ref rightsId, ref key); + return fsProxy.RegisterExternalKey(in rightsId, in key); } public static Result UnregisterExternalKey(this FileSystemClient fs, ref FsRightsId rightsId) { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - return fsProxy.UnregisterExternalKey(ref rightsId); + return fsProxy.UnregisterExternalKey(in rightsId); } public static Result UnregisterAllExternalKey(this FileSystemClient fs) diff --git a/src/LibHac/Fs/Shim/SaveData.cs b/src/LibHac/Fs/Shim/SaveData.cs index dcb0abea..23669cb0 100644 --- a/src/LibHac/Fs/Shim/SaveData.cs +++ b/src/LibHac/Fs/Shim/SaveData.cs @@ -1,8 +1,9 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; using LibHac.Ncm; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -115,14 +116,14 @@ namespace LibHac.Fs.Shim if (fs.IsEnabledAccessLog(AccessLogTarget.Application)) { TimeSpan startTime = fs.Time.GetCurrent(); - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index); TimeSpan endTime = fs.Time.GetCurrent(); fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", index: {index}"); } else { - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (short)index); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, default, default, SaveDataType.Cache, false, (ushort)index); } if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.Application)) @@ -165,14 +166,14 @@ namespace LibHac.Fs.Shim if (fs.IsEnabledAccessLog(AccessLogTarget.System)) { TimeSpan startTime = fs.Time.GetCurrent(); - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index); TimeSpan endTime = fs.Time.GetCurrent(); fs.OutputAccessLog(rc, startTime, endTime, $", name: \"{mountName.ToString()}\", applicationid: 0x{applicationId}, index: {index}"); } else { - rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (short)index); + rc = MountSaveDataImpl(fs, mountName, SaveDataSpaceId.User, applicationId, default, SaveDataType.Cache, false, (ushort)index); } if (rc.IsSuccess() && fs.IsEnabledAccessLog(AccessLogTarget.System)) @@ -184,7 +185,7 @@ namespace LibHac.Fs.Shim } private static Result MountSaveDataImpl(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, - ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, short index) + ProgramId programId, UserId userId, SaveDataType type, bool openReadOnly, ushort index) { Result rc = MountHelpers.CheckMountName(mountName); if (rc.IsFailure()) return rc; @@ -193,20 +194,29 @@ namespace LibHac.Fs.Shim var attribute = new SaveDataAttribute(programId, type, userId, 0, index); - IFileSystem saveFs; + ReferenceCountedDisposable saveFs = null; - if (openReadOnly) + try { - rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, ref attribute); + if (openReadOnly) + { + rc = fsProxy.OpenReadOnlySaveDataFileSystem(out saveFs, spaceId, in attribute); + } + else + { + rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, in attribute); + } + + if (rc.IsFailure()) return rc; + + var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs); + + return fs.Register(mountName, fileSystemAdapter, fileSystemAdapter, null); } - else + finally { - rc = fsProxy.OpenSaveDataFileSystem(out saveFs, spaceId, ref attribute); + saveFs?.Dispose(); } - - if (rc.IsFailure()) return rc; - - return fs.Register(mountName, saveFs); } } } diff --git a/src/LibHac/Fs/Shim/SaveDataManagement.cs b/src/LibHac/Fs/Shim/SaveDataManagement.cs index 8f5876d0..7cc125eb 100644 --- a/src/LibHac/Fs/Shim/SaveDataManagement.cs +++ b/src/LibHac/Fs/Shim/SaveDataManagement.cs @@ -1,9 +1,10 @@ using System; using System.Runtime.InteropServices; -using LibHac.Common; using LibHac.Diag; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; using LibHac.Ncm; +using LibHac.Sf; namespace LibHac.Fs.Shim { @@ -29,13 +30,13 @@ namespace LibHac.Fs.Shim SpaceId = SaveDataSpaceId.User }; - var metaInfo = new SaveMetaCreateInfo + var metaInfo = new SaveDataMetaInfo { Type = SaveDataMetaType.Thumbnail, Size = 0x40060 }; - return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); + return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo); }, () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); @@ -61,14 +62,14 @@ namespace LibHac.Fs.Shim SpaceId = SaveDataSpaceId.User }; - var metaInfo = new SaveMetaCreateInfo + var metaInfo = new SaveDataMetaInfo { Type = SaveDataMetaType.Thumbnail, Size = 0x40060 }; - return fsProxy.CreateSaveDataFileSystemWithHashSalt(ref attribute, ref createInfo, ref metaInfo, - ref hashSalt); + return fsProxy.CreateSaveDataFileSystemWithHashSalt(in attribute, in createInfo, in metaInfo, + in hashSalt); }, () => $", applicationid: 0x{applicationId.Value:X}, userid: 0x{userId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); @@ -81,7 +82,7 @@ namespace LibHac.Fs.Shim { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.Zero, 0); + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Bcat, UserId.InvalidId, 0); var createInfo = new SaveDataCreationInfo { @@ -93,9 +94,9 @@ namespace LibHac.Fs.Shim SpaceId = SaveDataSpaceId.User }; - var metaInfo = new SaveMetaCreateInfo(); + var metaInfo = new SaveDataMetaInfo(); - return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); + return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo); }, () => $", applicationid: 0x{applicationId.Value:X}, save_data_size: {size}"); } @@ -108,7 +109,7 @@ namespace LibHac.Fs.Shim { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Device, UserId.Zero, 0); + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Device, UserId.InvalidId, 0); var createInfo = new SaveDataCreationInfo { @@ -120,9 +121,9 @@ namespace LibHac.Fs.Shim SpaceId = SaveDataSpaceId.User }; - var metaInfo = new SaveMetaCreateInfo(); + var metaInfo = new SaveDataMetaInfo(); - return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); + return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo); }, () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } @@ -134,7 +135,7 @@ namespace LibHac.Fs.Shim { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Temporary, UserId.Zero, 0); + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Temporary, UserId.InvalidId, 0); var createInfo = new SaveDataCreationInfo { @@ -145,22 +146,22 @@ namespace LibHac.Fs.Shim SpaceId = SaveDataSpaceId.Temporary }; - var metaInfo = new SaveMetaCreateInfo(); + var metaInfo = new SaveDataMetaInfo(); - return fsProxy.CreateSaveDataFileSystem(ref attribute, ref createInfo, ref metaInfo); + return fsProxy.CreateSaveDataFileSystem(in attribute, in createInfo, in metaInfo); }, () => $", applicationid: 0x{applicationId.Value:X}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_flags: 0x{(int)flags:X8}"); } public static Result CreateCacheStorage(this FileSystemClient fs, Ncm.ApplicationId applicationId, - SaveDataSpaceId spaceId, ulong ownerId, short index, long size, long journalSize, SaveDataFlags flags) + SaveDataSpaceId spaceId, ulong ownerId, ushort index, long size, long journalSize, SaveDataFlags flags) { return fs.RunOperationWithAccessLog(AccessLogTarget.System, () => { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - var attribute = new SaveDataAttribute(applicationId, SaveDataType.Cache, UserId.Zero, 0, index); + var attribute = new SaveDataAttribute(applicationId, SaveDataType.Cache, UserId.InvalidId, 0, index); var creationInfo = new SaveDataCreationInfo { @@ -172,9 +173,9 @@ namespace LibHac.Fs.Shim SpaceId = spaceId }; - var metaInfo = new SaveMetaCreateInfo(); + var metaInfo = new SaveDataMetaInfo(); - return fsProxy.CreateSaveDataFileSystem(ref attribute, ref creationInfo, ref metaInfo); + return fsProxy.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); }, () => $", applicationid: 0x{applicationId.Value:X}, savedataspaceid: {spaceId}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:X8}"); } @@ -211,7 +212,7 @@ namespace LibHac.Fs.Shim SpaceId = spaceId }; - return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(ref attribute, ref createInfo); + return fsProxy.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in createInfo); }, () => $", savedataspaceid: {spaceId}, savedataid: 0x{saveDataId:X}, userid: 0x{userId.Id.High:X16}{userId.Id.Low:X16}, save_data_owner_id: 0x{ownerId:X}, save_data_size: {size}, save_data_journal_size: {journalSize}, save_data_flags: 0x{(int)flags:x8}"); } @@ -231,19 +232,19 @@ namespace LibHac.Fs.Shim public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) { - return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, ownerId, size, journalSize, flags); + return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.InvalidId, ownerId, size, journalSize, flags); } public static Result CreateSystemSaveData(this FileSystemClient fs, ulong saveDataId, long size, long journalSize, SaveDataFlags flags) { - return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.Zero, 0, size, journalSize, flags); + return CreateSystemSaveData(fs, SaveDataSpaceId.System, saveDataId, UserId.InvalidId, 0, size, journalSize, flags); } public static Result CreateSystemSaveData(this FileSystemClient fs, SaveDataSpaceId spaceId, ulong saveDataId, ulong ownerId, long size, long journalSize, SaveDataFlags flags) { - return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.Zero, ownerId, size, journalSize, flags); + return CreateSystemSaveData(fs, spaceId, saveDataId, UserId.InvalidId, ownerId, size, journalSize, flags); } public static Result DeleteSaveData(this FileSystemClient fs, ulong saveDataId) @@ -283,8 +284,8 @@ namespace LibHac.Fs.Shim tempInfo = new SaveDataInfo(); - Result rc = fsProxy.FindSaveDataWithFilter(out long count, SpanHelpers.AsByteSpan(ref tempInfo), - spaceId, ref tempFilter); + Result rc = fsProxy.FindSaveDataWithFilter(out long count, OutBuffer.FromStruct(ref tempInfo), + spaceId, in tempFilter); if (rc.IsFailure()) return rc; if (count == 0) @@ -329,49 +330,64 @@ namespace LibHac.Fs.Shim { var tempIterator = new SaveDataIterator(); - Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + try + { + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId( - out ReferenceCountedDisposable reader, spaceId); - if (rc.IsFailure()) return rc; + Result rc = fsProxy.OpenSaveDataInfoReaderBySaveDataSpaceId( + out ReferenceCountedDisposable reader, spaceId); + if (rc.IsFailure()) return rc; - tempIterator = new SaveDataIterator(fs, reader); + tempIterator = new SaveDataIterator(fs, reader); - return Result.Success; - }, - () => $", savedataspaceid: {spaceId}"); + return Result.Success; + }, + () => $", savedataspaceid: {spaceId}"); - iterator = result.IsSuccess() ? tempIterator : default; + iterator = result.IsSuccess() ? tempIterator : default; + tempIterator = default; - return result; + return result; + } + finally + { + tempIterator.Dispose(); + } } public static Result OpenSaveDataIterator(this FileSystemClient fs, out SaveDataIterator iterator, SaveDataSpaceId spaceId, ref SaveDataFilter filter) { + ReferenceCountedDisposable reader = null; var tempIterator = new SaveDataIterator(); SaveDataFilter tempFilter = filter; - Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, - () => - { - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + try + { + Result result = fs.RunOperationWithAccessLog(AccessLogTarget.System, + () => + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter( - out ReferenceCountedDisposable reader, spaceId, ref tempFilter); - if (rc.IsFailure()) return rc; + Result rc = fsProxy.OpenSaveDataInfoReaderWithFilter(out reader, spaceId, in tempFilter); + if (rc.IsFailure()) return rc; - tempIterator = new SaveDataIterator(fs, reader); + tempIterator = new SaveDataIterator(fs, reader); - return Result.Success; - }, - () => $", savedataspaceid: {spaceId}"); + return Result.Success; + }, + () => $", savedataspaceid: {spaceId}"); - iterator = result.IsSuccess() ? tempIterator : default; + iterator = result.IsSuccess() ? tempIterator : default; - return result; + return result; + } + finally + { + reader?.Dispose(); + } } public static void DisableAutoSaveDataCreation(this FileSystemClient fsClient) @@ -395,14 +411,14 @@ namespace LibHac.Fs.Shim internal SaveDataIterator(FileSystemClient fsClient, ReferenceCountedDisposable reader) { FsClient = fsClient; - Reader = reader; + Reader = reader.AddReference(); } public Result ReadSaveDataInfo(out long readCount, Span buffer) { Result rc; - Span byteBuffer = MemoryMarshal.Cast(buffer); + var byteBuffer = new OutBuffer(MemoryMarshal.Cast(buffer)); if (FsClient.IsEnabledAccessLog(AccessLogTarget.System)) { diff --git a/src/LibHac/Fs/Shim/SdCard.cs b/src/LibHac/Fs/Shim/SdCard.cs index 18ff1fd5..63413f00 100644 --- a/src/LibHac/Fs/Shim/SdCard.cs +++ b/src/LibHac/Fs/Shim/SdCard.cs @@ -1,7 +1,9 @@ using System; using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; +using LibHac.FsSrv.Sf; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { @@ -41,31 +43,44 @@ namespace LibHac.Fs.Shim IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - rc = fsProxy.OpenSdCardFileSystem(out IFileSystem fileSystem); + rc = fsProxy.OpenSdCardFileSystem(out ReferenceCountedDisposable fileSystem); if (rc.IsFailure()) return rc; - return fs.Register(mountName, fileSystem); + using (fileSystem) + { + var fileSystemAdapter = new FileSystemServiceObjectAdapter(fileSystem); + + return fs.Register(mountName, fileSystemAdapter); + } } } public static bool IsSdCardInserted(this FileSystemClient fs) { - IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); + ReferenceCountedDisposable deviceOperator = null; + try + { + IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.OpenDeviceOperator(out IDeviceOperator deviceOperator); - if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + Result rc = fsProxy.OpenDeviceOperator(out deviceOperator); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); - rc = deviceOperator.IsSdCardInserted(out bool isInserted); - if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); + rc = deviceOperator.Target.IsSdCardInserted(out bool isInserted); + if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); - return isInserted; + return isInserted; + } + finally + { + deviceOperator?.Dispose(); + } } - public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, ref EncryptionSeed seed) + public static Result SetSdCardEncryptionSeed(this FileSystemClient fs, in EncryptionSeed seed) { IFileSystemProxy fsProxy = fs.GetFileSystemProxyServiceObject(); - Result rc = fsProxy.SetSdCardEncryptionSeed(ref seed); + Result rc = fsProxy.SetSdCardEncryptionSeed(in seed); if (rc.IsFailure()) throw new HorizonResultException(rc, "Abort"); return Result.Success; diff --git a/src/LibHac/Fs/Shim/SystemSaveData.cs b/src/LibHac/Fs/Shim/SystemSaveData.cs index ae8e3be1..2a84b973 100644 --- a/src/LibHac/Fs/Shim/SystemSaveData.cs +++ b/src/LibHac/Fs/Shim/SystemSaveData.cs @@ -1,15 +1,17 @@ using LibHac.Common; -using LibHac.Fs.Fsa; +using LibHac.Fs.Impl; using LibHac.FsSrv; using LibHac.Ncm; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.Fs.Shim { public static class SystemSaveData { - public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, ulong saveDataId) + public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, SaveDataSpaceId spaceId, + ulong saveDataId) { - return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.Zero); + return MountSystemSaveData(fs, mountName, spaceId, saveDataId, UserId.InvalidId); } public static Result MountSystemSaveData(this FileSystemClient fs, U8Span mountName, @@ -22,10 +24,20 @@ namespace LibHac.Fs.Shim var attribute = new SaveDataAttribute(ProgramId.InvalidId, SaveDataType.System, userId, saveDataId); - rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, spaceId, ref attribute); - if (rc.IsFailure()) return rc; + ReferenceCountedDisposable saveFs = null; - return fs.Register(mountName, fileSystem); + try + { + rc = fsProxy.OpenSaveDataFileSystemBySystemSaveDataId(out saveFs, spaceId, in attribute); + if (rc.IsFailure()) return rc; + + var fileSystemAdapter = new FileSystemServiceObjectAdapter(saveFs); + return fs.Register(mountName, fileSystemAdapter); + } + finally + { + saveFs?.Dispose(); + } } } } diff --git a/src/LibHac/Fs/Shim/UserFileSystem.cs b/src/LibHac/Fs/Shim/UserFileSystem.cs new file mode 100644 index 00000000..57e1bca6 --- /dev/null +++ b/src/LibHac/Fs/Shim/UserFileSystem.cs @@ -0,0 +1,53 @@ +using System; +using LibHac.Common; +using LibHac.Fs.Accessors; +using LibHac.FsSrv.Sf; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; + +namespace LibHac.Fs.Shim +{ + public static class UserFileSystem + { + public static Result Commit(this FileSystemClient fs, ReadOnlySpan mountNames) + { + // Todo: Add access log + + if (mountNames.Length > 10) + return ResultFs.InvalidCommitNameCount.Log(); + + if (mountNames.Length == 0) + return Result.Success; + + ReferenceCountedDisposable commitManager = null; + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = fs.GetFileSystemProxyServiceObject().OpenMultiCommitManager(out commitManager); + if (rc.IsFailure()) return rc; + + for (int i = 0; i < mountNames.Length; i++) + { + rc = fs.MountTable.Find(mountNames[i].ToString(), out FileSystemAccessor accessor); + if (rc.IsFailure()) return rc; + + fileSystem = accessor.GetMultiCommitTarget(); + if (fileSystem is null) + return ResultFs.UnsupportedCommitTarget.Log(); + + rc = commitManager.Target.Add(fileSystem); + if (rc.IsFailure()) return rc; + } + + rc = commitManager.Target.Commit(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + finally + { + commitManager?.Dispose(); + fileSystem?.Dispose(); + } + } + } +} diff --git a/src/LibHac/Fs/UserId.cs b/src/LibHac/Fs/UserId.cs index 0bc21c7c..e4d37178 100644 --- a/src/LibHac/Fs/UserId.cs +++ b/src/LibHac/Fs/UserId.cs @@ -9,7 +9,7 @@ namespace LibHac.Fs [StructLayout(LayoutKind.Sequential, Size = 0x10)] public struct UserId : IEquatable, IComparable, IComparable { - public static UserId Zero => default; + public static UserId InvalidId => default; public readonly Id128 Id; diff --git a/src/LibHac/FsSrv/AccessLogService.cs b/src/LibHac/FsSrv/AccessLogService.cs new file mode 100644 index 00000000..8f2bf4e5 --- /dev/null +++ b/src/LibHac/FsSrv/AccessLogService.cs @@ -0,0 +1,66 @@ +using System; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.Sf; + +namespace LibHac.FsSrv +{ + internal readonly struct AccessLogService + { + private readonly AccessLogServiceImpl _serviceImpl; + private readonly ulong _processId; + + public AccessLogService(AccessLogServiceImpl serviceImpl, ulong processId) + { + _serviceImpl = serviceImpl; + _processId = processId; + } + + public Result SetAccessLogMode(GlobalAccessLogMode mode) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetGlobalAccessLogMode)) + return ResultFs.PermissionDenied.Log(); + + _serviceImpl.SetAccessLogMode(mode); + return Result.Success; + } + + public Result GetAccessLogMode(out GlobalAccessLogMode mode) + { + mode = _serviceImpl.GetAccessLogMode(); + return Result.Success; + } + + public Result OutputAccessLogToSdCard(InBuffer textBuffer) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + return _serviceImpl.OutputAccessLogToSdCard(textBuffer.Buffer, programInfo.ProgramIdValue, _processId); + } + + public Result OutputMultiProgramTagAccessLog() + { + _serviceImpl.OutputAccessLogToSdCard(MultiProgramTag, _processId).IgnoreResult(); + return Result.Success; + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + + private static ReadOnlySpan MultiProgramTag => // FS_ACCESS: { multi_program_tag: true }\n + new[] + { + (byte) 'F', (byte) 'S', (byte) '_', (byte) 'A', (byte) 'C', (byte) 'C', (byte) 'E', (byte) 'S', + (byte) 'S', (byte) ':', (byte) ' ', (byte) '{', (byte) ' ', (byte) 'm', (byte) 'u', (byte) 'l', + (byte) 't', (byte) 'i', (byte) '_', (byte) 'p', (byte) 'r', (byte) 'o', (byte) 'g', (byte) 'r', + (byte) 'a', (byte) 'm', (byte) '_', (byte) 't', (byte) 'a', (byte) 'g', (byte) ':', (byte) ' ', + (byte) 't', (byte) 'r', (byte) 'u', (byte) 'e', (byte) ' ', (byte) '}', (byte) '\n' + }; + } +} diff --git a/src/LibHac/FsSrv/AccessLogServiceImpl.cs b/src/LibHac/FsSrv/AccessLogServiceImpl.cs new file mode 100644 index 00000000..378610fd --- /dev/null +++ b/src/LibHac/FsSrv/AccessLogServiceImpl.cs @@ -0,0 +1,56 @@ +using System; +using LibHac.Fs; +using LibHac.FsSrv.Impl; + +namespace LibHac.FsSrv +{ + public class AccessLogServiceImpl : IDisposable + { + private Configuration _config; + private GlobalAccessLogMode _accessLogMode; + + public AccessLogServiceImpl(in Configuration configuration) + { + _config = configuration; + } + + public void Dispose() + { + + } + + public struct Configuration + { + public ulong MinimumProgramIdForSdCardLog; + + // LibHac additions + public HorizonClient HorizonClient; + public ProgramRegistryImpl ProgramRegistry; + } + + public void SetAccessLogMode(GlobalAccessLogMode mode) + { + _accessLogMode = mode; + } + + public GlobalAccessLogMode GetAccessLogMode() + { + return _accessLogMode; + } + + public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong processId) + { + throw new NotImplementedException(); + } + + public Result OutputAccessLogToSdCard(ReadOnlySpan text, ulong programId, ulong processId) + { + throw new NotImplementedException(); + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + } +} diff --git a/src/LibHac/FsSrv/BaseFileSystemService.cs b/src/LibHac/FsSrv/BaseFileSystemService.cs new file mode 100644 index 00000000..4d6e25c3 --- /dev/null +++ b/src/LibHac/FsSrv/BaseFileSystemService.cs @@ -0,0 +1,257 @@ +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.Sf; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; + +namespace LibHac.FsSrv +{ + public readonly struct BaseFileSystemService + { + private readonly BaseFileSystemServiceImpl _serviceImpl; + private readonly ulong _processId; + + public BaseFileSystemService(BaseFileSystemServiceImpl serviceImpl, ulong processId) + { + _serviceImpl = serviceImpl; + _processId = processId; + } + + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath rootPath, + BisPartitionId partitionId) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + // Get the permissions the caller needs + AccessibilityType requiredAccess = partitionId switch + { + BisPartitionId.CalibrationFile => AccessibilityType.MountBisCalibrationFile, + BisPartitionId.SafeMode => AccessibilityType.MountBisSafeMode, + BisPartitionId.User => AccessibilityType.MountBisUser, + BisPartitionId.System => AccessibilityType.MountBisSystem, + BisPartitionId.SystemProperPartition => AccessibilityType.MountBisSystemProperPartition, + _ => AccessibilityType.NotMount + }; + + // Reject opening invalid partitions + if (requiredAccess == AccessibilityType.NotMount) + return ResultFs.InvalidArgument.Log(); + + // Verify the caller has the required permissions + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(requiredAccess); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + // Normalize the path + var normalizer = new PathNormalizer(rootPath, PathNormalizer.Option.AcceptEmpty); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + ReferenceCountedDisposable fs = null; + + try + { + // Open the file system + rc = _serviceImpl.OpenBisFileSystem(out fs, normalizer.Path, + partitionId); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + + return Result.Success; + } + finally + { + fs?.Dispose(); + } + } + + public Result CreatePaddingFile(long size) + { + // File size must be non-negative + if (size < 0) + return ResultFs.InvalidSize.Log(); + + // Caller must have the FillBis permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.CreatePaddingFile(size); + } + + public Result DeleteAllPaddingFiles() + { + // Caller must have the FillBis permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FillBis)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.DeleteAllPaddingFiles(); + } + + public Result OpenGameCardFileSystem(out ReferenceCountedDisposable fileSystem, GameCardHandle handle, + GameCardPartition partitionId) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountGameCard).CanRead) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable fs = null; + + try + { + rc = _serviceImpl.OpenGameCardFileSystem(out fs, handle, partitionId); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + + return Result.Success; + } + finally + { + fs?.Dispose(); + } + } + + public Result OpenSdCardFileSystem(out ReferenceCountedDisposable fileSystem) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSdCard); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable fs = null; + + try + { + rc = _serviceImpl.OpenSdCardProxyFileSystem(out fs); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + + return Result.Success; + } + finally + { + fs?.Dispose(); + } + } + + public Result FormatSdCardFileSystem() + { + // Caller must have the FormatSdCard permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.FormatSdCard)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.FormatSdCardProxyFileSystem(); + } + + public Result FormatSdCardDryRun() + { + // No permissions are needed to call this method + + return _serviceImpl.FormatSdCardProxyFileSystem(); + } + + public Result IsExFatSupported(out bool isSupported) + { + // No permissions are needed to call this method + + isSupported = _serviceImpl.IsExFatSupported(); + return Result.Success; + } + + public Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, + ImageDirectoryId directoryId) + { + fileSystem = default; + + // Caller must have the MountImageAndVideoStorage permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountImageAndVideoStorage); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + // Get the base file system ID + int id; + switch (directoryId) + { + case ImageDirectoryId.Nand: + id = 0; + break; + case ImageDirectoryId.SdCard: + id = 1; + break; + default: + return ResultFs.InvalidArgument.Log(); + } + ReferenceCountedDisposable fs = null; + + try + { + rc = _serviceImpl.OpenBaseFileSystem(out fs, id); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + + return Result.Success; + } + finally + { + fs?.Dispose(); + } + } + + public Result OpenBisWiper(out ReferenceCountedDisposable bisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize) + { + bisWiper = default; + + // Caller must have the OpenBisWiper permission + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenBisWiper)) + return ResultFs.PermissionDenied.Log(); + + rc = _serviceImpl.OpenBisWiper(out IWiper wiper, transferMemoryHandle, transferMemorySize); + if (rc.IsFailure()) return rc; + + bisWiper = new ReferenceCountedDisposable(wiper); + return Result.Success; + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + } +} diff --git a/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs new file mode 100644 index 00000000..2d28c9ce --- /dev/null +++ b/src/LibHac/FsSrv/BaseFileSystemServiceImpl.cs @@ -0,0 +1,133 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.Sf; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; + +namespace LibHac.FsSrv +{ + public class BaseFileSystemServiceImpl + { + private Configuration _config; + + public delegate Result BisWiperCreator(out IWiper wiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize); + + public BaseFileSystemServiceImpl(in Configuration configuration) + { + _config = configuration; + } + + public struct Configuration + { + public IBuiltInStorageFileSystemCreator BisFileSystemCreator; + public IGameCardFileSystemCreator GameCardFileSystemCreator; + public ISdCardProxyFileSystemCreator SdCardFileSystemCreator; + // CurrentTimeFunction + // FatFileSystemCacheManager + // AlbumDirectoryFileSystemManager + public BisWiperCreator BisWiperCreator; + + // Note: The program registry service is global as of FS 10.0.0 + public ProgramRegistryImpl ProgramRegistry; + } + + public Result OpenBaseFileSystem(out ReferenceCountedDisposable fileSystem, int fileSystemId) + { + throw new NotImplementedException(); + } + + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + BisPartitionId partitionId) + { + return OpenBisFileSystem(out fileSystem, rootPath, partitionId, false); + } + + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + BisPartitionId partitionId, bool caseSensitive) + { + fileSystem = default; + + Result rc = _config.BisFileSystemCreator.Create(out IFileSystem fs, rootPath.ToString(), partitionId); + if (rc.IsFailure()) return rc; + + fileSystem = new ReferenceCountedDisposable(fs); + return Result.Success; + } + + public Result CreatePaddingFile(long size) + { + throw new NotImplementedException(); + } + + public Result DeleteAllPaddingFiles() + { + throw new NotImplementedException(); + } + + public Result OpenGameCardFileSystem(out ReferenceCountedDisposable fileSystem, GameCardHandle handle, + GameCardPartition partitionId) + { + Result rc; + int tries = 0; + + do + { + rc = _config.GameCardFileSystemCreator.Create(out fileSystem, handle, partitionId); + + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + + tries++; + } while (tries < 2); + + return rc; + } + + public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable fileSystem) + { + return OpenSdCardProxyFileSystem(out fileSystem, false); + } + + public Result OpenSdCardProxyFileSystem(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive) + { + fileSystem = default; + + // Todo: Shared + Result rc = _config.SdCardFileSystemCreator.Create(out IFileSystem fs, openCaseSensitive); + if (rc.IsFailure()) return rc; + + fileSystem = new ReferenceCountedDisposable(fs); + return Result.Success; + } + + public Result FormatSdCardProxyFileSystem() + { + return _config.SdCardFileSystemCreator.Format(); + } + + public Result FormatSdCardDryRun() + { + throw new NotImplementedException(); + } + + public bool IsExFatSupported() + { + // Returning false should probably be fine + return false; + } + + public Result OpenBisWiper(out IWiper wiper, NativeHandle transferMemoryHandle, ulong transferMemorySize) + { + return _config.BisWiperCreator(out wiper, transferMemoryHandle, transferMemorySize); + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + } +} diff --git a/src/LibHac/FsSrv/BaseStorageService.cs b/src/LibHac/FsSrv/BaseStorageService.cs new file mode 100644 index 00000000..4e36ab0c --- /dev/null +++ b/src/LibHac/FsSrv/BaseStorageService.cs @@ -0,0 +1,240 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Fs; +using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using IStorage = LibHac.Fs.IStorage; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.FsSrv +{ + public readonly struct BaseStorageService + { + private readonly BaseStorageServiceImpl _serviceImpl; + private readonly ulong _processId; + + public BaseStorageService(BaseStorageServiceImpl serviceImpl, ulong processId) + { + _serviceImpl = serviceImpl; + _processId = processId; + } + + public Result OpenBisStorage(out ReferenceCountedDisposable storage, BisPartitionId id) + { + storage = default; + + var storageFlag = StorageType.Bis; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = GetAccessibilityForOpenBisPartition(out Accessibility accessibility, programInfo, id); + if (rc.IsFailure()) return rc; + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable tempStorage = null; + try + { + rc = _serviceImpl.OpenBisStorage(out tempStorage, id); + if (rc.IsFailure()) return rc; + + tempStorage = StorageLayoutTypeSetStorage.CreateShared(ref tempStorage, storageFlag); + + // Todo: Async storage + + storage = StorageInterfaceAdapter.CreateShared(ref tempStorage); + return Result.Success; + + } + finally + { + tempStorage?.Dispose(); + } + } + + public Result InvalidateBisCache() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.InvalidateBisCache)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.InvalidateBisCache(); + } + + public Result OpenGameCardStorage(out ReferenceCountedDisposable storage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + storage = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.OpenGameCardStorage); + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable tempStorage = null; + try + { + rc = _serviceImpl.OpenGameCardPartition(out tempStorage, handle, partitionId); + if (rc.IsFailure()) return rc; + + // Todo: Async storage + + storage = StorageInterfaceAdapter.CreateShared(ref tempStorage); + return Result.Success; + } + finally + { + tempStorage?.Dispose(); + } + } + + public Result OpenDeviceOperator(out ReferenceCountedDisposable deviceOperator) + { + deviceOperator = _serviceImpl.Config.DeviceOperator.AddReference(); + return Result.Success; + } + + public Result OpenSdCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + eventNotifier = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSdCardDetectionEventNotifier)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } + + public Result OpenGameCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + eventNotifier = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenGameCardDetectionEventNotifier)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } + + public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SimulateDevice)) + return ResultFs.PermissionDenied.Log(); + + throw new NotImplementedException(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + + private Result GetAccessibilityForOpenBisPartition(out Accessibility accessibility, ProgramInfo programInfo, + BisPartitionId partitionId) + { + Unsafe.SkipInit(out accessibility); + + AccessibilityType type = partitionId switch + { + BisPartitionId.BootPartition1Root => AccessibilityType.OpenBisPartitionBootPartition1Root, + BisPartitionId.BootPartition2Root => AccessibilityType.OpenBisPartitionBootPartition2Root, + BisPartitionId.UserDataRoot => AccessibilityType.OpenBisPartitionUserDataRoot, + BisPartitionId.BootConfigAndPackage2Part1 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part1, + BisPartitionId.BootConfigAndPackage2Part2 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part2, + BisPartitionId.BootConfigAndPackage2Part3 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part3, + BisPartitionId.BootConfigAndPackage2Part4 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part4, + BisPartitionId.BootConfigAndPackage2Part5 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part5, + BisPartitionId.BootConfigAndPackage2Part6 => AccessibilityType.OpenBisPartitionBootConfigAndPackage2Part6, + BisPartitionId.CalibrationBinary => AccessibilityType.OpenBisPartitionCalibrationBinary, + BisPartitionId.CalibrationFile => AccessibilityType.OpenBisPartitionCalibrationFile, + BisPartitionId.SafeMode => AccessibilityType.OpenBisPartitionSafeMode, + BisPartitionId.User => AccessibilityType.OpenBisPartitionUser, + BisPartitionId.System => AccessibilityType.OpenBisPartitionSystem, + BisPartitionId.SystemProperEncryption => AccessibilityType.OpenBisPartitionSystemProperEncryption, + BisPartitionId.SystemProperPartition => AccessibilityType.OpenBisPartitionSystemProperPartition, + _ => (AccessibilityType)(-1) + }; + + if (type == (AccessibilityType)(-1)) + return ResultFs.InvalidArgument.Log(); + + accessibility = programInfo.AccessControl.GetAccessibilityFor(type); + return Result.Success; + } + } + + public class BaseStorageServiceImpl + { + internal Configuration Config; + + public BaseStorageServiceImpl(in Configuration configuration) + { + Config = configuration; + } + + public struct Configuration + { + public IBuiltInStorageCreator BisStorageCreator; + public IGameCardStorageCreator GameCardStorageCreator; + + // LibHac additions + public ProgramRegistryImpl ProgramRegistry; + // Todo: The DeviceOperator in FS uses mostly global state. Decide how to handle this. + public ReferenceCountedDisposable DeviceOperator; + } + + public Result OpenBisStorage(out ReferenceCountedDisposable storage, BisPartitionId partitionId) + { + return Config.BisStorageCreator.Create(out storage, partitionId); + } + + public Result InvalidateBisCache() + { + return Config.BisStorageCreator.InvalidateCache(); + } + + public Result OpenGameCardPartition(out ReferenceCountedDisposable storage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + switch (partitionId) + { + case GameCardPartitionRaw.NormalReadOnly: + return Config.GameCardStorageCreator.CreateReadOnly(handle, out storage); + case GameCardPartitionRaw.SecureReadOnly: + return Config.GameCardStorageCreator.CreateSecureReadOnly(handle, out storage); + case GameCardPartitionRaw.RootWriteOnly: + return Config.GameCardStorageCreator.CreateWriteOnly(handle, out storage); + default: + storage = default; + return ResultFs.InvalidArgument.Log(); + } + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return Config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + } +} diff --git a/src/LibHac/FsSrv/Creators/EmulatedBisFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/EmulatedBisFileSystemCreator.cs index d9261d59..66575804 100644 --- a/src/LibHac/FsSrv/Creators/EmulatedBisFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/EmulatedBisFileSystemCreator.cs @@ -1,6 +1,8 @@ using System.Diagnostics; +using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; +using LibHac.FsSrv.Impl; namespace LibHac.FsSrv.Creators { @@ -85,6 +87,53 @@ namespace LibHac.FsSrv.Creators return Util.CreateSubFileSystemImpl(out fileSystem, subFileSystem, rootPath); } + // Todo: Make case sensitive + public Result Create(out ReferenceCountedDisposable fileSystem, U8Span rootPath, + BisPartitionId partitionId, bool caseSensitive) + { + fileSystem = default; + + if (!IsValidPartitionId(partitionId)) return ResultFs.InvalidArgument.Log(); + if (rootPath.IsNull()) return ResultFs.NullptrArgument.Log(); + + if (Config.TryGetFileSystem(out IFileSystem fs, partitionId)) + { + fileSystem = new ReferenceCountedDisposable(fs); + return Result.Success; + } + + if (Config.RootFileSystem == null) + { + return ResultFs.PreconditionViolation.Log(); + } + + var partitionPath = GetPartitionPath(partitionId).ToU8String(); + + ReferenceCountedDisposable partitionFileSystem = null; + ReferenceCountedDisposable sharedRootFs = null; + try + { + sharedRootFs = new ReferenceCountedDisposable(Config.RootFileSystem); + + Result rc = Utility.WrapSubDirectory(out partitionFileSystem, ref sharedRootFs, partitionPath, 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); + } + finally + { + partitionFileSystem?.Dispose(); + sharedRootFs?.Dispose(); + } + } + public Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId) { fileSystem = default; diff --git a/src/LibHac/FsSrv/Creators/EmulatedGameCardFsCreator.cs b/src/LibHac/FsSrv/Creators/EmulatedGameCardFsCreator.cs index f483b731..39312d05 100644 --- a/src/LibHac/FsSrv/Creators/EmulatedGameCardFsCreator.cs +++ b/src/LibHac/FsSrv/Creators/EmulatedGameCardFsCreator.cs @@ -14,6 +14,7 @@ namespace LibHac.FsSrv.Creators StorageCreator = storageCreator; GameCard = gameCard; } + public Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType) { // Use the old xci code temporarily @@ -31,5 +32,24 @@ namespace LibHac.FsSrv.Creators fileSystem = xci.OpenPartition((XciPartitionType)partitionType); return Result.Success; } + + public Result Create(out ReferenceCountedDisposable fileSystem, GameCardHandle handle, GameCardPartition partitionType) + { + // Use the old xci code temporarily + + fileSystem = default; + + Result rc = GameCard.GetXci(out Xci xci, handle); + if (rc.IsFailure()) return rc; + + if (!xci.HasPartition((XciPartitionType)partitionType)) + { + return ResultFs.PartitionNotFound.Log(); + } + + XciPartition fs = xci.OpenPartition((XciPartitionType)partitionType); + fileSystem = new ReferenceCountedDisposable(fs); + return Result.Success; + } } } diff --git a/src/LibHac/FsSrv/Creators/EmulatedGameCardStorageCreator.cs b/src/LibHac/FsSrv/Creators/EmulatedGameCardStorageCreator.cs index 7e246bea..65daa55d 100644 --- a/src/LibHac/FsSrv/Creators/EmulatedGameCardStorageCreator.cs +++ b/src/LibHac/FsSrv/Creators/EmulatedGameCardStorageCreator.cs @@ -12,7 +12,7 @@ namespace LibHac.FsSrv.Creators GameCard = gameCard; } - public Result CreateNormal(GameCardHandle handle, out IStorage storage) + public Result CreateReadOnly(GameCardHandle handle, out ReferenceCountedDisposable storage) { storage = default; @@ -21,16 +21,17 @@ namespace LibHac.FsSrv.Creators return ResultFs.InvalidGameCardHandleOnOpenNormalPartition.Log(); } - var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle); + var baseStorage = new ReferenceCountedDisposable(new ReadOnlyGameCardStorage(GameCard, handle)); Result rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); if (rc.IsFailure()) return rc; - storage = new SubStorage(baseStorage, 0, cardInfo.SecureAreaOffset); + storage = new ReferenceCountedDisposable( + new SubStorage(baseStorage, 0, cardInfo.SecureAreaOffset)); return Result.Success; } - public Result CreateSecure(GameCardHandle handle, out IStorage storage) + public Result CreateSecureReadOnly(GameCardHandle handle, out ReferenceCountedDisposable storage) { storage = default; @@ -48,16 +49,19 @@ namespace LibHac.FsSrv.Creators rc = GameCard.GetGameCardImageHash(imageHash); if (rc.IsFailure()) return rc; - var baseStorage = new ReadOnlyGameCardStorage(GameCard, handle, deviceId, imageHash); + var baseStorage = + new ReferenceCountedDisposable(new ReadOnlyGameCardStorage(GameCard, handle, deviceId, + imageHash)); rc = GameCard.GetCardInfo(out GameCardInfo cardInfo, handle); if (rc.IsFailure()) return rc; - storage = new SubStorage(baseStorage, cardInfo.SecureAreaOffset, cardInfo.SecureAreaSize); + storage = new ReferenceCountedDisposable(new SubStorage(baseStorage, cardInfo.SecureAreaOffset, + cardInfo.SecureAreaSize)); return Result.Success; } - public Result CreateWritable(GameCardHandle handle, out IStorage storage) + public Result CreateWriteOnly(GameCardHandle handle, out ReferenceCountedDisposable storage) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs similarity index 73% rename from src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs rename to src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs index a861c57f..022ce7ae 100644 --- a/src/LibHac/FsSrv/Creators/EmulatedSdFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/EmulatedSdCardFileSystemCreator.cs @@ -4,7 +4,7 @@ using LibHac.Fs.Fsa; namespace LibHac.FsSrv.Creators { - public class EmulatedSdFileSystemCreator : ISdFileSystemCreator + public class EmulatedSdCardFileSystemCreator : ISdCardProxyFileSystemCreator { private const string DefaultPath = "/sdcard"; @@ -14,20 +14,20 @@ namespace LibHac.FsSrv.Creators private IFileSystem SdCardFileSystem { get; set; } - public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem) + public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem) { SdCard = sdCard; RootFileSystem = rootFileSystem; } - public EmulatedSdFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path) + public EmulatedSdCardFileSystemCreator(EmulatedSdCard sdCard, IFileSystem rootFileSystem, string path) { SdCard = sdCard; RootFileSystem = rootFileSystem; Path = path; } - public Result Create(out IFileSystem fileSystem) + public Result Create(out IFileSystem fileSystem, bool isCaseSensitive) { fileSystem = default; @@ -61,7 +61,12 @@ namespace LibHac.FsSrv.Creators return Result.Success; } - public Result Format(bool closeOpenEntries) + public Result Format(bool removeFromFatFsCache) + { + throw new NotImplementedException(); + } + + public Result Format() { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs index b92be85c..923bf0dd 100644 --- a/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/EncryptedFileSystemCreator.cs @@ -29,7 +29,27 @@ namespace LibHac.FsSrv.Creators KeySet.SetSdSeed(encryptionSeed.ToArray()); encryptedFileSystem = new AesXtsFileSystem(baseFileSystem, - KeySet.SdCardEncryptionKeys[(int) keyId].DataRo.ToArray(), 0x4000); + KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000); + + return Result.Success; + } + + public Result Create(out ReferenceCountedDisposable encryptedFileSystem, ReferenceCountedDisposable baseFileSystem, + EncryptedFsKeyId keyId, in EncryptionSeed encryptionSeed) + { + encryptedFileSystem = default; + + if (keyId < EncryptedFsKeyId.Save || keyId > EncryptedFsKeyId.CustomStorage) + { + return ResultFs.InvalidArgument.Log(); + } + + // todo: "proper" key generation instead of a lazy hack + KeySet.SetSdSeed(encryptionSeed.Value); + + // Todo: pass ReferenceCountedDisposable to AesXtsFileSystem + var fs = new AesXtsFileSystem(baseFileSystem, KeySet.SdCardEncryptionKeys[(int)keyId].DataRo.ToArray(), 0x4000); + encryptedFileSystem = new ReferenceCountedDisposable(fs); return Result.Success; } diff --git a/src/LibHac/FsSrv/Creators/FileSystemCreators.cs b/src/LibHac/FsSrv/Creators/FileSystemCreators.cs index 1e15191f..22ece1f3 100644 --- a/src/LibHac/FsSrv/Creators/FileSystemCreators.cs +++ b/src/LibHac/FsSrv/Creators/FileSystemCreators.cs @@ -17,6 +17,6 @@ public IEncryptedFileSystemCreator EncryptedFileSystemCreator { get; set; } public IMemoryStorageCreator MemoryStorageCreator { get; set; } public IBuiltInStorageFileSystemCreator BuiltInStorageFileSystemCreator { get; set; } - public ISdFileSystemCreator SdFileSystemCreator { get; set; } + public ISdCardProxyFileSystemCreator SdCardFileSystemCreator { get; set; } } } diff --git a/src/LibHac/FsSrv/Creators/IBuiltInStorageCreator.cs b/src/LibHac/FsSrv/Creators/IBuiltInStorageCreator.cs index 47dc7756..2b58c738 100644 --- a/src/LibHac/FsSrv/Creators/IBuiltInStorageCreator.cs +++ b/src/LibHac/FsSrv/Creators/IBuiltInStorageCreator.cs @@ -4,6 +4,7 @@ namespace LibHac.FsSrv.Creators { public interface IBuiltInStorageCreator { - Result Create(out IStorage storage, BisPartitionId partitionId); + Result Create(out ReferenceCountedDisposable storage, BisPartitionId partitionId); + Result InvalidateCache(); } } diff --git a/src/LibHac/FsSrv/Creators/IBuiltInStorageFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/IBuiltInStorageFileSystemCreator.cs index 5a17aa23..c36ab875 100644 --- a/src/LibHac/FsSrv/Creators/IBuiltInStorageFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/IBuiltInStorageFileSystemCreator.cs @@ -1,11 +1,14 @@ -using LibHac.Fs; +using LibHac.Common; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.Creators { public interface IBuiltInStorageFileSystemCreator { + // Todo: Remove raw IFileSystem overload Result Create(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId); + Result Create(out ReferenceCountedDisposable fileSystem, U8Span rootPath, BisPartitionId partitionId, bool caseSensitive); Result CreateFatFileSystem(out IFileSystem fileSystem, BisPartitionId partitionId); Result SetBisRootForHost(BisPartitionId partitionId, string rootPath); } diff --git a/src/LibHac/FsSrv/Creators/IEncryptedFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/IEncryptedFileSystemCreator.cs index cc1eeac0..0db77318 100644 --- a/src/LibHac/FsSrv/Creators/IEncryptedFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/IEncryptedFileSystemCreator.cs @@ -1,12 +1,18 @@ using System; +using LibHac.Fs; using LibHac.Fs.Fsa; namespace LibHac.FsSrv.Creators { public interface IEncryptedFileSystemCreator { + // Todo: remove the function using raw IFileSystems Result Create(out IFileSystem encryptedFileSystem, IFileSystem baseFileSystem, EncryptedFsKeyId keyId, ReadOnlySpan encryptionSeed); + + Result Create(out ReferenceCountedDisposable encryptedFileSystem, + ReferenceCountedDisposable baseFileSystem, EncryptedFsKeyId keyId, + in EncryptionSeed encryptionSeed); } public enum EncryptedFsKeyId diff --git a/src/LibHac/FsSrv/Creators/IGameCardFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/IGameCardFileSystemCreator.cs index 4d04e574..d8f8cc18 100644 --- a/src/LibHac/FsSrv/Creators/IGameCardFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/IGameCardFileSystemCreator.cs @@ -6,5 +6,6 @@ namespace LibHac.FsSrv.Creators public interface IGameCardFileSystemCreator { Result Create(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionType); + Result Create(out ReferenceCountedDisposable fileSystem, GameCardHandle handle, GameCardPartition partitionType); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Creators/IGameCardStorageCreator.cs b/src/LibHac/FsSrv/Creators/IGameCardStorageCreator.cs index 2da142fd..a6874e41 100644 --- a/src/LibHac/FsSrv/Creators/IGameCardStorageCreator.cs +++ b/src/LibHac/FsSrv/Creators/IGameCardStorageCreator.cs @@ -4,8 +4,8 @@ namespace LibHac.FsSrv.Creators { public interface IGameCardStorageCreator { - Result CreateNormal(GameCardHandle handle, out IStorage storage); - Result CreateSecure(GameCardHandle handle, out IStorage storage); - Result CreateWritable(GameCardHandle handle, out IStorage storage); + Result CreateReadOnly(GameCardHandle handle, out ReferenceCountedDisposable storage); + Result CreateSecureReadOnly(GameCardHandle handle, out ReferenceCountedDisposable storage); + Result CreateWriteOnly(GameCardHandle handle, out ReferenceCountedDisposable storage); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Creators/IPartitionFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/IPartitionFileSystemCreator.cs index 3c5d6e76..31a3e5c1 100644 --- a/src/LibHac/FsSrv/Creators/IPartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/IPartitionFileSystemCreator.cs @@ -5,6 +5,8 @@ namespace LibHac.FsSrv.Creators { public interface IPartitionFileSystemCreator { + // Todo: Remove non-shared overload Result Create(out IFileSystem fileSystem, IStorage pFsStorage); + Result Create(out ReferenceCountedDisposable fileSystem, ReferenceCountedDisposable pFsStorage); } } diff --git a/src/LibHac/FsSrv/Creators/IRomFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/IRomFileSystemCreator.cs index 1d7ebdca..16ce296b 100644 --- a/src/LibHac/FsSrv/Creators/IRomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/IRomFileSystemCreator.cs @@ -5,6 +5,6 @@ namespace LibHac.FsSrv.Creators { public interface IRomFileSystemCreator { - Result Create(out IFileSystem fileSystem, IStorage romFsStorage); + Result Create(out ReferenceCountedDisposable fileSystem, ReferenceCountedDisposable romFsStorage); } } diff --git a/src/LibHac/FsSrv/Creators/ISaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISaveDataFileSystemCreator.cs index 78895e1c..6c9abfb0 100644 --- a/src/LibHac/FsSrv/Creators/ISaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/ISaveDataFileSystemCreator.cs @@ -2,7 +2,7 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -using LibHac.FsSystem.Save; +using LibHac.FsSystem; namespace LibHac.FsSrv.Creators { @@ -10,9 +10,10 @@ namespace LibHac.FsSrv.Creators { Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode); - Result Create(out IFileSystem fileSystem, out ISaveDataExtraDataAccessor extraDataAccessor, - IFileSystem sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, - SaveDataType type, ITimeStampGenerator timeStampGenerator); + Result Create(out IFileSystem fileSystem, + out ReferenceCountedDisposable extraDataAccessor, IFileSystem sourceFileSystem, + ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, SaveDataType type, + ITimeStampGenerator timeStampGenerator); void SetSdCardEncryptionSeed(ReadOnlySpan seed); } diff --git a/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs new file mode 100644 index 00000000..6a8c978b --- /dev/null +++ b/src/LibHac/FsSrv/Creators/ISdCardProxyFileSystemCreator.cs @@ -0,0 +1,23 @@ +using LibHac.Fs.Fsa; + +namespace LibHac.FsSrv.Creators +{ + public interface ISdCardProxyFileSystemCreator + { + Result Create(out IFileSystem fileSystem, bool isCaseSensitive); + + /// + /// Formats the SD card. + /// + /// Should the SD card file system be removed from the + /// FAT file system cache? + /// The of the operation. + Result Format(bool removeFromFatFsCache); + + /// + /// Automatically closes all open proxy file system entries and formats the SD card. + /// + /// The of the operation. + Result Format(); + } +} diff --git a/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs deleted file mode 100644 index 08167cdc..00000000 --- a/src/LibHac/FsSrv/Creators/ISdFileSystemCreator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using LibHac.Fs.Fsa; - -namespace LibHac.FsSrv.Creators -{ - public interface ISdFileSystemCreator - { - Result Create(out IFileSystem fileSystem); - Result Format(bool closeOpenEntries); - } -} diff --git a/src/LibHac/FsSrv/Creators/IStorageOnNcaCreator.cs b/src/LibHac/FsSrv/Creators/IStorageOnNcaCreator.cs index c8955cfb..f312a3ad 100644 --- a/src/LibHac/FsSrv/Creators/IStorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/Creators/IStorageOnNcaCreator.cs @@ -6,8 +6,8 @@ namespace LibHac.FsSrv.Creators { public interface IStorageOnNcaCreator { - Result Create(out IStorage storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs); - Result CreateWithPatch(out IStorage storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs); + Result Create(out ReferenceCountedDisposable storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs); + Result CreateWithPatch(out ReferenceCountedDisposable storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs); Result OpenNca(out Nca nca, IStorage ncaStorage); Result VerifyAcidSignature(IFileSystem codeFileSystem, Nca nca); } diff --git a/src/LibHac/FsSrv/Creators/ISubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ISubDirectoryFileSystemCreator.cs index 0e6a6673..2fd5b50f 100644 --- a/src/LibHac/FsSrv/Creators/ISubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/ISubDirectoryFileSystemCreator.cs @@ -5,7 +5,7 @@ namespace LibHac.FsSrv.Creators { public interface ISubDirectoryFileSystemCreator { - Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path); - Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path, bool preserveUnc); + Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path); + Result Create(out ReferenceCountedDisposable subDirFileSystem, ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Creators/ITargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/ITargetManagerFileSystemCreator.cs index b0f7183e..e65e9fa4 100644 --- a/src/LibHac/FsSrv/Creators/ITargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/ITargetManagerFileSystemCreator.cs @@ -5,7 +5,9 @@ namespace LibHac.FsSrv.Creators { public interface ITargetManagerFileSystemCreator { + // Todo: Remove raw IFilesystem function Result Create(out IFileSystem fileSystem, bool openCaseSensitive); - Result GetCaseSensitivePath(out bool isSuccess, Span path); + Result Create(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive); + Result NormalizeCaseOfPath(out bool isSupported, Span path); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Creators/PartitionFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/PartitionFileSystemCreator.cs index a9b3f0fc..d6506994 100644 --- a/src/LibHac/FsSrv/Creators/PartitionFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/PartitionFileSystemCreator.cs @@ -21,5 +21,20 @@ namespace LibHac.FsSrv.Creators fileSystem = partitionFs; return Result.Success; } + + public Result Create(out ReferenceCountedDisposable fileSystem, ReferenceCountedDisposable pFsStorage) + { + var partitionFs = new PartitionFileSystemCore(); + + Result rc = partitionFs.Initialize(pFsStorage); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + fileSystem = new ReferenceCountedDisposable(partitionFs); + return Result.Success; + } } } diff --git a/src/LibHac/FsSrv/Creators/RomFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/RomFileSystemCreator.cs index dc180241..fa8a042e 100644 --- a/src/LibHac/FsSrv/Creators/RomFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/RomFileSystemCreator.cs @@ -7,9 +7,10 @@ namespace LibHac.FsSrv.Creators public class RomFileSystemCreator : IRomFileSystemCreator { // todo: Implement properly - public Result Create(out IFileSystem fileSystem, IStorage romFsStorage) + public Result Create(out ReferenceCountedDisposable fileSystem, ReferenceCountedDisposable romFsStorage) { - fileSystem = new RomFsFileSystem(romFsStorage); + // Todo: Properly use shared references + fileSystem = new ReferenceCountedDisposable(new RomFsFileSystem(romFsStorage.AddReference().Target)); return Result.Success; } } diff --git a/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs index 96a337d0..6bcc3d01 100644 --- a/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/SaveDataFileSystemCreator.cs @@ -22,9 +22,10 @@ namespace LibHac.FsSrv.Creators throw new NotImplementedException(); } - public Result Create(out IFileSystem fileSystem, out ISaveDataExtraDataAccessor extraDataAccessor, - IFileSystem sourceFileSystem, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, - SaveDataType type, ITimeStampGenerator timeStampGenerator) + public Result Create(out IFileSystem fileSystem, + out ReferenceCountedDisposable extraDataAccessor, IFileSystem sourceFileSystem, + ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, SaveDataType type, + ITimeStampGenerator timeStampGenerator) { fileSystem = default; extraDataAccessor = default; @@ -42,13 +43,15 @@ namespace LibHac.FsSrv.Creators case DirectoryEntryType.Directory: if (!allowDirectorySaveData) return ResultFs.InvalidSaveDataEntryType.Log(); - rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subDirFs, sourceFileSystem, saveDataPath); + rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subDirFs, sourceFileSystem, + saveDataPath); if (rc.IsFailure()) return rc; bool isPersistentSaveData = type != SaveDataType.Temporary; bool isUserSaveData = type == SaveDataType.Account || type == SaveDataType.Device; - rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs, isPersistentSaveData, isUserSaveData); + rc = DirectorySaveDataFileSystem.CreateNew(out DirectorySaveDataFileSystem saveFs, subDirFs, + isPersistentSaveData, isUserSaveData); if (rc.IsFailure()) return rc; fileSystem = saveFs; @@ -62,7 +65,8 @@ namespace LibHac.FsSrv.Creators if (rc.IsFailure()) return rc; var saveDataStorage = new DisposingFileStorage(saveDataFile); - fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, false); + fileSystem = new SaveDataFileSystem(KeySet, saveDataStorage, IntegrityCheckLevel.ErrorOnInvalid, + false); // Todo: ISaveDataExtraDataAccessor diff --git a/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs b/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs index 422d91e9..d8b5b49c 100644 --- a/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs +++ b/src/LibHac/FsSrv/Creators/StorageOnNcaCreator.cs @@ -20,7 +20,8 @@ namespace LibHac.FsSrv.Creators } // todo: Implement NcaReader and other Nca classes - public Result Create(out IStorage storage, out NcaFsHeader fsHeader, Nca nca, int fsIndex, bool isCodeFs) + public Result Create(out ReferenceCountedDisposable storage, out NcaFsHeader fsHeader, Nca nca, + int fsIndex, bool isCodeFs) { storage = default; fsHeader = default; @@ -40,13 +41,14 @@ namespace LibHac.FsSrv.Creators } } - storage = storageTemp; + storage = new ReferenceCountedDisposable(storageTemp); fsHeader = nca.GetFsHeader(fsIndex); return Result.Success; } - public Result CreateWithPatch(out IStorage storage, out NcaFsHeader fsHeader, Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs) + public Result CreateWithPatch(out ReferenceCountedDisposable storage, out NcaFsHeader fsHeader, + Nca baseNca, Nca patchNca, int fsIndex, bool isCodeFs) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/Creators/SubDirectoryFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/SubDirectoryFileSystemCreator.cs index b76de263..3d837daf 100644 --- a/src/LibHac/FsSrv/Creators/SubDirectoryFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/SubDirectoryFileSystemCreator.cs @@ -6,21 +6,30 @@ namespace LibHac.FsSrv.Creators { public class SubDirectoryFileSystemCreator : ISubDirectoryFileSystemCreator { - public Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path) + public Result Create(out ReferenceCountedDisposable subDirFileSystem, + ref ReferenceCountedDisposable baseFileSystem, U8Span path) { - return Create(out subDirFileSystem, baseFileSystem, path, false); + return Create(out subDirFileSystem, ref baseFileSystem, path, false); } - public Result Create(out IFileSystem subDirFileSystem, IFileSystem baseFileSystem, U8Span path, bool preserveUnc) + public Result Create(out ReferenceCountedDisposable subDirFileSystem, + ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool preserveUnc) { subDirFileSystem = default; - Result rc = baseFileSystem.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); + // Verify the sub-path exists + Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory _, path, OpenDirectoryMode.Directory); if (rc.IsFailure()) return rc; - rc = SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem fs, baseFileSystem, path.ToU8String(), preserveUnc); - subDirFileSystem = fs; - return rc; + // Initialize the SubdirectoryFileSystem + var subDir = new SubdirectoryFileSystem(ref baseFileSystem, preserveUnc); + using var subDirShared = new ReferenceCountedDisposable(subDir); + + rc = subDirShared.Target.Initialize(path); + if (rc.IsFailure()) return rc; + + subDirFileSystem = subDirShared.AddReference(); + return Result.Success; } } } diff --git a/src/LibHac/FsSrv/Creators/TargetManagerFileSystemCreator.cs b/src/LibHac/FsSrv/Creators/TargetManagerFileSystemCreator.cs index f9212171..e76866b1 100644 --- a/src/LibHac/FsSrv/Creators/TargetManagerFileSystemCreator.cs +++ b/src/LibHac/FsSrv/Creators/TargetManagerFileSystemCreator.cs @@ -10,7 +10,12 @@ namespace LibHac.FsSrv.Creators throw new NotImplementedException(); } - public Result GetCaseSensitivePath(out bool isSuccess, Span path) + public Result Create(out ReferenceCountedDisposable fileSystem, bool openCaseSensitive) + { + throw new NotImplementedException(); + } + + public Result NormalizeCaseOfPath(out bool isSupported, Span path) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index d9f0e7e0..d3658800 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -1,6 +1,7 @@ using LibHac.Common.Keys; -using LibHac.Fs.Fsa; using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Sf; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; namespace LibHac.FsSrv { @@ -29,7 +30,7 @@ namespace LibHac.FsSrv creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); creators.BuiltInStorageFileSystemCreator = new EmulatedBisFileSystemCreator(rootFileSystem); - creators.SdFileSystemCreator = new EmulatedSdFileSystemCreator(sdCard, rootFileSystem); + creators.SdCardFileSystemCreator = new EmulatedSdCardFileSystemCreator(sdCard, rootFileSystem); var deviceOperator = new EmulatedDeviceOperator(gameCard, sdCard); diff --git a/src/LibHac/FsSrv/Delegates.cs b/src/LibHac/FsSrv/Delegates.cs new file mode 100644 index 00000000..f8a64109 --- /dev/null +++ b/src/LibHac/FsSrv/Delegates.cs @@ -0,0 +1,12 @@ +using System; + +namespace LibHac.FsSrv +{ + public delegate Result RandomDataGenerator(Span buffer); + + public delegate Result SaveTransferAesKeyGenerator(Span key, + SaveDataTransferCryptoConfiguration.KeyIndex index, ReadOnlySpan keySource, int keyGeneration); + + public delegate Result SaveTransferCmacGenerator(Span mac, ReadOnlySpan data, + SaveDataTransferCryptoConfiguration.KeyIndex index, int keyGeneration); +} diff --git a/src/LibHac/FsSrv/EmulatedDeviceOperator.cs b/src/LibHac/FsSrv/EmulatedDeviceOperator.cs index 767186e9..1870e2eb 100644 --- a/src/LibHac/FsSrv/EmulatedDeviceOperator.cs +++ b/src/LibHac/FsSrv/EmulatedDeviceOperator.cs @@ -1,4 +1,5 @@ using LibHac.Fs; +using LibHac.FsSrv.Sf; namespace LibHac.FsSrv { @@ -13,6 +14,8 @@ namespace LibHac.FsSrv SdCard = sdCard; } + public void Dispose() { } + public Result IsSdCardInserted(out bool isInserted) { isInserted = SdCard.IsSdCardInserted(); diff --git a/src/LibHac/FsSrv/FileSystemProxy.cs b/src/LibHac/FsSrv/FileSystemProxy.cs deleted file mode 100644 index 75d266be..00000000 --- a/src/LibHac/FsSrv/FileSystemProxy.cs +++ /dev/null @@ -1,1294 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using LibHac.Common; -using LibHac.Diag; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSrv.Impl; -using LibHac.FsSrv.Sf; -using LibHac.FsSystem; -using LibHac.Kvdb; -using LibHac.Ncm; -using LibHac.Spl; -using LibHac.Util; - -namespace LibHac.FsSrv -{ - public class FileSystemProxy : IFileSystemProxy, IFileSystemProxyForLoader - { - private FileSystemProxyCore FsProxyCore { get; } - internal HorizonClient Hos { get; } - - public ulong CurrentProcess { get; private set; } - - public long SaveDataSize { get; private set; } - public long SaveDataJournalSize { get; private set; } - public FsPath SaveDataRootPath { get; } - public bool AutoCreateSaveData { get; private set; } - - internal FileSystemProxy(HorizonClient horizonClient, FileSystemProxyCore fsProxyCore) - { - FsProxyCore = fsProxyCore; - Hos = horizonClient; - - CurrentProcess = ulong.MaxValue; - SaveDataSize = 0x2000000; - SaveDataJournalSize = 0x1000000; - AutoCreateSaveData = true; - } - - private ProgramRegistryService GetProgramRegistryService() - { - return new ProgramRegistryService(FsProxyCore.Config.ProgramRegistryServiceImpl, CurrentProcess); - } - - public Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, ulong id, FileSystemProxyType type) - { - fileSystem = default; - - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - AccessControl ac = programInfo.AccessControl; - - switch (type) - { - case FileSystemProxyType.Logo: - if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Control: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Manual: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Meta: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Data: - if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - case FileSystemProxyType.Package: - if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead) - return ResultFs.PermissionDenied.Log(); - break; - default: - return ResultFs.InvalidArgument.Log(); - } - - if (type == FileSystemProxyType.Meta) - { - id = ulong.MaxValue; - } - else if (id == ulong.MaxValue) - { - return ResultFs.InvalidArgument.Log(); - } - - bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; - - var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); - if (normalizer.Result.IsFailure()) return normalizer.Result; - - return FsProxyCore.OpenFileSystem(out fileSystem, normalizer.Path, type, canMountSystemDataPrivate, id); - - // Missing speed emulation storage type wrapper, async wrapper, and FileSystemInterfaceAdapter - } - - private PathNormalizer.Option GetPathNormalizerOptions(U8Span path) - { - int hostMountLength = StringUtils.GetLength(CommonMountNames.HostRootFileSystemMountName, - PathTools.MountNameLengthMax); - - bool isHostPath = StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, hostMountLength) == 0; - - PathNormalizer.Option hostOption = isHostPath ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None; - return PathNormalizer.Option.HasMountName | PathNormalizer.Option.PreserveTailSeparator | hostOption; - } - - public Result OpenFileSystemWithPatch(out IFileSystem fileSystem, ProgramId programId, FileSystemProxyType type) - { - throw new NotImplementedException(); - } - - public Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData, in FspPath path, - ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result IsArchivedProgram(out bool isArchived, ulong processId) - { - throw new NotImplementedException(); - } - - public Result SetCurrentProcess(ulong processId) - { - CurrentProcess = processId; - - return Result.Success; - } - - public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) - { - throw new NotImplementedException(); - } - - public Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem) - { - throw new NotImplementedException(); - } - - public Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageByCurrentProcess(out IStorage storage) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageByProgramId(out IStorage storage, ProgramId programId) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageByDataId(out IStorage storage, DataId dataId, StorageId storageId) - { - throw new NotImplementedException(); - } - - public Result OpenPatchDataStorageByCurrentProcess(out IStorage storage) - { - throw new NotImplementedException(); - } - - public Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex) - { - throw new NotImplementedException(); - } - - public Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex) - { - throw new NotImplementedException(); - } - - public Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan saveDataIds) - { - throw new NotImplementedException(); - } - - public Result DeleteSaveDataFileSystem(ulong saveDataId) - { - return DeleteSaveDataFileSystemImpl(SaveDataSpaceId.System, saveDataId); - } - - private Result DeleteSaveDataFileSystemImpl(SaveDataSpaceId spaceId, ulong saveDataId) - { - Result rc = GetProgramInfo(out ProgramInfo programInfo); - if (rc.IsFailure()) return rc; - - SaveDataIndexerAccessor accessor = null; - - try - { - SaveDataType saveType; - - if (saveDataId == FileSystemServer.SaveIndexerId) - { - if (!IsCurrentProcess(CurrentProcess)) - return ResultFs.PermissionDenied.Log(); - - saveType = SaveDataType.System; - } - else - { - rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); - if (rc.IsFailure()) return rc; - - if (spaceId != SaveDataSpaceId.ProperSystem && spaceId != SaveDataSpaceId.SafeMode) - { - rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; - - spaceId = value.SpaceId; - } - - rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId); - if (rc.IsFailure()) return rc; - - if (key.Type == SaveDataType.System || key.Type == SaveDataType.SystemBcat) - { - if (!programInfo.AccessControl.CanCall(OperationType.DeleteSystemSaveData)) - return ResultFs.PermissionDenied.Log(); - } - else - { - if (!programInfo.AccessControl.CanCall(OperationType.DeleteSaveData)) - return ResultFs.PermissionDenied.Log(); - } - - saveType = key.Type; - - rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Creating); - if (rc.IsFailure()) return rc; - - rc = accessor.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - rc = DeleteSaveDataFileSystemImpl2(spaceId, saveDataId, saveType, false); - if (rc.IsFailure()) return rc; - - if (saveDataId != FileSystemServer.SaveIndexerId) - { - // ReSharper disable once PossibleNullReferenceException - rc = accessor.Indexer.Delete(saveDataId); - if (rc.IsFailure()) return rc; - - rc = accessor.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - return Result.Success; - } - finally { accessor?.Dispose(); } - } - - // ReSharper disable once UnusedParameter.Local - private Result DeleteSaveDataFileSystemImpl2(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataType type, bool shouldWipe) - { - // missing: Check extra data flags for this value. Bit 3 - bool doSecureDelete = shouldWipe; - - Result rc = FsProxyCore.DeleteSaveDataMetaFiles(saveDataId, spaceId); - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - rc = FsProxyCore.DeleteSaveDataFileSystem(spaceId, saveDataId, doSecureDelete); - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; - - return Result.Success; - } - - public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) - { - return DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(spaceId, saveDataId); - } - - private Result DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(SaveDataSpaceId spaceId, ulong saveDataId) - { - if (saveDataId != FileSystemServer.SaveIndexerId) - { - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); - if (rc.IsFailure()) return rc; - - using (accessor) - { - rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - if (rc.IsFailure()) return rc; - - if (value.SpaceId != GetSpaceIdForIndexer(spaceId)) - return ResultFs.TargetNotFound.Log(); - } - } - - return DeleteSaveDataFileSystemImpl(spaceId, saveDataId); - } - - private Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) - { - Unsafe.SkipInit(out info); - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); - if (rc.IsFailure()) return rc; - - using (accessor) - { - rc = accessor.Indexer.Get(out SaveDataIndexerValue value, in attribute); - if (rc.IsFailure()) return rc; - - SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); - return Result.Success; - } - } - - public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute) - { - Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, in attribute); - if (rs.IsFailure()) return rs; - - return DeleteSaveDataFileSystemBySaveDataSpaceIdImpl(spaceId, info.SaveDataId); - } - - public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - private Result CreateSaveDataFileSystemImpl(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, - ref SaveMetaCreateInfo metaCreateInfo, ref OptionalHashSalt hashSalt, bool something) - { - ulong saveDataId = 0; - bool isDeleteNeeded = false; - Result rc; - - SaveDataIndexerAccessor accessor = null; - - // Missing permission checks - - try - { - if (attribute.StaticSaveDataId == FileSystemServer.SaveIndexerId) - { - saveDataId = FileSystemServer.SaveIndexerId; - rc = FsProxyCore.DoesSaveDataExist(out bool saveExists, creationInfo.SpaceId, saveDataId); - - if (rc.IsSuccess() && saveExists) - { - return ResultFs.PathAlreadyExists.Log(); - } - - isDeleteNeeded = true; - } - else - { - rc = OpenSaveDataIndexerAccessor(out accessor, creationInfo.SpaceId); - if (rc.IsFailure()) return rc; - - SaveDataAttribute indexerKey = attribute; - - if (attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.Zero) - { - saveDataId = attribute.StaticSaveDataId; - - rc = accessor.Indexer.PutStaticSaveDataIdIndex(in indexerKey); - } - else - { - if (attribute.Type != SaveDataType.System && - attribute.Type != SaveDataType.SystemBcat) - { - if (accessor.Indexer.IsRemainedReservedOnly()) - { - return ResultKvdb.OutOfKeyResource.Log(); - } - } - - rc = accessor.Indexer.Publish(out saveDataId, in indexerKey); - } - - if (ResultFs.SaveDataPathAlreadyExists.Includes(rc)) - { - return ResultFs.PathAlreadyExists.LogConverted(rc); - } - - isDeleteNeeded = true; - - rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Creating); - if (rc.IsFailure()) return rc; - - SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(creationInfo.SpaceId); - - rc = accessor.Indexer.SetSpaceId(saveDataId, indexerSpaceId); - if (rc.IsFailure()) return rc; - - // todo: calculate size - long size = 0; - - rc = accessor.Indexer.SetSize(saveDataId, size); - if (rc.IsFailure()) return rc; - - rc = accessor.Indexer.Commit(); - if (rc.IsFailure()) return rc; - } - - rc = FsProxyCore.CreateSaveDataFileSystem(saveDataId, ref attribute, ref creationInfo, SaveDataRootPath, - hashSalt, false); - - if (rc.IsFailure()) - { - if (!ResultFs.PathAlreadyExists.Includes(rc)) return rc; - - rc = DeleteSaveDataFileSystemImpl2(creationInfo.SpaceId, saveDataId, attribute.Type, false); - if (rc.IsFailure()) return rc; - - rc = FsProxyCore.CreateSaveDataFileSystem(saveDataId, ref attribute, ref creationInfo, SaveDataRootPath, - hashSalt, false); - if (rc.IsFailure()) return rc; - } - - if (metaCreateInfo.Type != SaveDataMetaType.None) - { - rc = FsProxyCore.CreateSaveDataMetaFile(saveDataId, creationInfo.SpaceId, metaCreateInfo.Type, - metaCreateInfo.Size); - if (rc.IsFailure()) return rc; - - if (metaCreateInfo.Type == SaveDataMetaType.Thumbnail) - { - rc = FsProxyCore.OpenSaveDataMetaFile(out IFile metaFile, saveDataId, creationInfo.SpaceId, - metaCreateInfo.Type); - - using (metaFile) - { - if (rc.IsFailure()) return rc; - - ReadOnlySpan metaFileData = stackalloc byte[0x20]; - - rc = metaFile.Write(0, metaFileData, WriteOption.Flush); - if (rc.IsFailure()) return rc; - } - } - } - - if (attribute.StaticSaveDataId == FileSystemServer.SaveIndexerId || something) - { - isDeleteNeeded = false; - - return Result.Success; - } - - // accessor shouldn't ever be null, but checking makes the analyzers happy - Abort.DoAbortUnless(accessor != null); - - rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Normal); - if (rc.IsFailure()) return rc; - - rc = accessor.Indexer.Commit(); - if (rc.IsFailure()) return rc; - - isDeleteNeeded = false; - - return Result.Success; - } - finally - { - // Revert changes if an error happened in the middle of creation - if (isDeleteNeeded) - { - DeleteSaveDataFileSystemImpl2(creationInfo.SpaceId, saveDataId, attribute.Type, false).IgnoreResult(); - - if (accessor != null && saveDataId != FileSystemServer.SaveIndexerId) - { - rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); - - if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) - { - accessor.Indexer.Delete(saveDataId).IgnoreResult(); - accessor.Indexer.Commit().IgnoreResult(); - } - } - } - - accessor?.Dispose(); - } - } - - public Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, - ref SaveMetaCreateInfo metaCreateInfo) - { - OptionalHashSalt hashSalt = default; - - return CreateUserSaveDataFileSystem(ref attribute, ref creationInfo, ref metaCreateInfo, ref hashSalt); - } - - public Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, - ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt) - { - var hashSaltCopy = new OptionalHashSalt - { - IsSet = true, - HashSalt = hashSalt - }; - - return CreateUserSaveDataFileSystem(ref attribute, ref creationInfo, ref metaCreateInfo, ref hashSaltCopy); - } - - private Result CreateUserSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, - ref SaveMetaCreateInfo metaCreateInfo, ref OptionalHashSalt hashSalt) - { - return CreateSaveDataFileSystemImpl(ref attribute, ref creationInfo, ref metaCreateInfo, ref hashSalt, false); - } - - public Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo) - { - if (!IsSystemSaveDataId(attribute.StaticSaveDataId)) - return ResultFs.InvalidArgument.Log(); - - SaveDataCreationInfo newCreationInfo = creationInfo; - - if (creationInfo.OwnerId == 0) - { - // todo: Assign the current program's ID - // throw new NotImplementedException(); - } - - // Missing permission checks - - if (attribute.Type == SaveDataType.SystemBcat) - { - newCreationInfo.OwnerId = SystemProgramId.Bcat.Value; - } - - SaveMetaCreateInfo metaCreateInfo = default; - OptionalHashSalt optionalHashSalt = default; - - return CreateSaveDataFileSystemImpl(ref attribute, ref newCreationInfo, ref metaCreateInfo, - ref optionalHashSalt, false); - } - - public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize) - { - throw new NotImplementedException(); - } - - private Result OpenSaveDataFileSystemImpl(out IFileSystem fileSystem, out ulong saveDataId, - SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, bool openReadOnly, bool cacheExtraData) - { - fileSystem = default; - saveDataId = default; - - bool hasFixedId = attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.Zero; - - if (hasFixedId) - { - saveDataId = attribute.StaticSaveDataId; - } - else - { - SaveDataAttribute indexerKey = attribute; - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor tempAccessor, spaceId); - using SaveDataIndexerAccessor accessor = tempAccessor; - if (rc.IsFailure()) return rc; - - rc = accessor.Indexer.Get(out SaveDataIndexerValue indexerValue, in indexerKey); - if (rc.IsFailure()) return rc; - - SaveDataSpaceId indexerSpaceId = GetSpaceIdForIndexer(spaceId); - - if (indexerValue.SpaceId != indexerSpaceId) - return ResultFs.TargetNotFound.Log(); - - if (indexerValue.State == SaveDataState.Extending) - return ResultFs.SaveDataIsExtending.Log(); - - saveDataId = indexerValue.SaveDataId; - } - - Result saveFsResult = FsProxyCore.OpenSaveDataFileSystem(out fileSystem, spaceId, saveDataId, - SaveDataRootPath.ToString(), openReadOnly, attribute.Type, cacheExtraData); - - if (saveFsResult.IsSuccess()) return Result.Success; - - if (!ResultFs.PathNotFound.Includes(saveFsResult) && !ResultFs.TargetNotFound.Includes(saveFsResult)) return saveFsResult; - - if (saveDataId != FileSystemServer.SaveIndexerId) - { - if (hasFixedId) - { - // todo: remove save indexer entry - } - } - - if (ResultFs.PathNotFound.Includes(saveFsResult)) - { - return ResultFs.TargetNotFound.LogConverted(saveFsResult); - } - - return saveFsResult; - } - - private Result OpenSaveDataFileSystem3(out IFileSystem fileSystem, SaveDataSpaceId spaceId, - ref SaveDataAttribute attribute, bool openReadOnly) - { - // Missing check if the open limit has been hit - - Result rc = OpenSaveDataFileSystemImpl(out fileSystem, out _, spaceId, ref attribute, openReadOnly, true); - - // Missing permission check based on the save's owner ID, - // speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return rc; - } - - private Result OpenSaveDataFileSystem2(out IFileSystem fileSystem, SaveDataSpaceId spaceId, - ref SaveDataAttribute attribute, bool openReadOnly) - { - fileSystem = default; - - // Missing permission checks - - SaveDataAttribute attributeCopy; - - if (attribute.ProgramId == ProgramId.InvalidId) - { - throw new NotImplementedException(); - } - else - { - attributeCopy = attribute; - } - - SaveDataSpaceId actualSpaceId; - - if (attributeCopy.Type == SaveDataType.Cache) - { - // Check whether the save is on the SD card or the BIS - Result rc = GetSpaceIdForCacheStorage(out actualSpaceId, attributeCopy.ProgramId); - if (rc.IsFailure()) return rc; - } - else - { - actualSpaceId = spaceId; - } - - return OpenSaveDataFileSystem3(out fileSystem, actualSpaceId, ref attributeCopy, openReadOnly); - } - - public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute) - { - return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, false); - } - - public Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, - ref SaveDataAttribute attribute) - { - return OpenSaveDataFileSystem2(out fileSystem, spaceId, ref attribute, true); - } - - public Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, - ref SaveDataAttribute attribute) - { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - fileSystem = default; - - if (!IsSystemSaveDataId(attribute.StaticSaveDataId)) return ResultFs.InvalidArgument.Log(); - - Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, spaceId, - ref attribute, false, true); - if (rc.IsFailure()) return rc; - - // Missing check if the current title owns the save data or can open it - - fileSystem = saveFs; - - return Result.Success; - } - - public Result ReadSaveDataFileSystemExtraData(Span extraDataBuffer, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span extraDataBuffer, SaveDataSpaceId spaceId, - ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span extraDataBuffer, SaveDataSpaceId spaceId, - ref SaveDataAttribute attribute) - { - throw new NotImplementedException(); - } - - public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer) - { - throw new NotImplementedException(); - } - - public Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute attribute, SaveDataSpaceId spaceId, - ReadOnlySpan extraDataBuffer, ReadOnlySpan maskBuffer) - { - throw new NotImplementedException(); - } - - public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer, - ReadOnlySpan maskBuffer) - { - throw new NotImplementedException(); - } - - public Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId dirId) - { - throw new NotImplementedException(); - } - - public Result RegisterProgramIndexMapInfo(ReadOnlySpan programIndexMapInfoBuffer, int programCount) - { - return GetProgramRegistryService().RegisterProgramIndexMapInfo(programIndexMapInfoBuffer, programCount); - } - - public Result SetBisRootForHost(BisPartitionId partitionId, ref FsPath path) - { - throw new NotImplementedException(); - } - - public Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId) - { - fileSystem = default; - - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - Result rc = PathTools.Normalize(out U8Span normalizedPath, rootPath); - if (rc.IsFailure()) return rc; - - return FsProxyCore.OpenBisFileSystem(out fileSystem, normalizedPath.ToString(), partitionId); - } - - public Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId) - { - throw new NotImplementedException(); - } - - public Result InvalidateBisCache() - { - throw new NotImplementedException(); - } - - public Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option) - { - // Missing permission check - - return FsProxyCore.OpenHostFileSystem(out fileSystem, new U8Span(path.Str), option.HasFlag(MountHostOption.PseudoCaseSensitive)); - } - - public Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path) - { - return OpenHostFileSystemWithOption(out fileSystem, ref path, MountHostOption.None); - } - - public Result OpenSdCardFileSystem(out IFileSystem fileSystem) - { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenSdCardFileSystem(out fileSystem); - } - - public Result FormatSdCardFileSystem() - { - throw new NotImplementedException(); - } - - public Result FormatSdCardDryRun() - { - throw new NotImplementedException(); - } - - public Result IsExFatSupported(out bool isSupported) - { - throw new NotImplementedException(); - } - - public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId) - { - // Missing permission check and StorageInterfaceAdapter - - return FsProxyCore.OpenGameCardStorage(out storage, handle, partitionId); - } - - public Result OpenDeviceOperator(out IDeviceOperator deviceOperator) - { - // Missing permission check - - return FsProxyCore.OpenDeviceOperator(out deviceOperator); - } - - public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) - { - infoReader = default; - - // Missing permission check - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId.System); - if (rc.IsFailure()) return rc; - - using (accessor) - { - return accessor.Indexer.OpenSaveDataInfoReader(out infoReader); - } - } - - public Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId) - { - infoReader = default; - - // Missing permission check - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); - if (rc.IsFailure()) return rc; - - using (accessor) - { - rc = accessor.Indexer.OpenSaveDataInfoReader( - out ReferenceCountedDisposable baseInfoReader); - if (rc.IsFailure()) return rc; - - var filter = new SaveDataFilterInternal - { - FilterBySaveDataSpaceId = true, - SpaceId = GetSpaceIdForIndexer(spaceId) - }; - - var filterReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter); - infoReader = new ReferenceCountedDisposable(filterReader); - - return Result.Success; - } - } - - public Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId, - ref SaveDataFilter filter) - { - infoReader = default; - - // Missing permission check - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); - if (rc.IsFailure()) return rc; - - using (accessor) - { - rc = accessor.Indexer.OpenSaveDataInfoReader( - out ReferenceCountedDisposable baseInfoReader); - if (rc.IsFailure()) return rc; - - var filterInternal = new SaveDataFilterInternal(ref filter, spaceId); - - var filterReader = new SaveDataInfoFilterReader(baseInfoReader, ref filterInternal); - infoReader = new ReferenceCountedDisposable(filterReader); - - return Result.Success; - } - } - - public Result FindSaveDataWithFilter(out long count, Span saveDataInfoBuffer, SaveDataSpaceId spaceId, - ref SaveDataFilter filter) - { - count = default; - - if (saveDataInfoBuffer.Length != Unsafe.SizeOf()) - { - return ResultFs.InvalidArgument.Log(); - } - - // Missing permission check - - var internalFilter = new SaveDataFilterInternal(ref filter, GetSpaceIdForIndexer(spaceId)); - - ref SaveDataInfo saveDataInfo = ref Unsafe.As(ref saveDataInfoBuffer[0]); - - return FindSaveDataWithFilterImpl(out count, out saveDataInfo, spaceId, ref internalFilter); - } - - private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId, - ref SaveDataFilterInternal filter) - { - count = default; - info = default; - - - Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); - if (rc.IsFailure()) return rc; - - using (accessor) - { - rc = accessor.Indexer.OpenSaveDataInfoReader( - out ReferenceCountedDisposable baseInfoReader); - if (rc.IsFailure()) return rc; - - using (var infoReader = new SaveDataInfoFilterReader(baseInfoReader, ref filter)) - { - return infoReader.Read(out count, SpanHelpers.AsByteSpan(ref info)); - } - } - } - - public Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable infoReader) - { - throw new NotImplementedException(); - } - - public Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, - SaveDataMetaType type) - { - throw new NotImplementedException(); - } - - private Result GetSpaceIdForCacheStorage(out SaveDataSpaceId spaceId, ProgramId programId) - { - spaceId = default; - - if (FsProxyCore.IsSdCardAccessible) - { - var filter = new SaveDataFilterInternal(); - - filter.SetSaveDataSpaceId(SaveDataSpaceId.SdCache); - filter.SetProgramId(programId); - filter.SetSaveDataType(SaveDataType.Cache); - - Result rc = FindSaveDataWithFilterImpl(out long count, out _, SaveDataSpaceId.SdCache, ref filter); - if (rc.IsFailure()) return rc; - - if (count > 0) - { - spaceId = SaveDataSpaceId.SdCache; - return Result.Success; - } - } - - { - var filter = new SaveDataFilterInternal(); - - filter.SetSaveDataSpaceId(SaveDataSpaceId.User); - filter.SetProgramId(programId); - filter.SetSaveDataType(SaveDataType.Cache); - - Result rc = FindSaveDataWithFilterImpl(out long count, out _, SaveDataSpaceId.User, ref filter); - if (rc.IsFailure()) return rc; - - if (count > 0) - { - spaceId = SaveDataSpaceId.User; - return Result.Success; - } - } - - return ResultFs.TargetNotFound.Log(); - } - - public Result DeleteCacheStorage(short index) - { - throw new NotImplementedException(); - } - - public Result GetCacheStorageSize(out long dataSize, out long journalSize, short index) - { - throw new NotImplementedException(); - } - - public Result ListAccessibleSaveDataOwnerId(out int readCount, Span idBuffer, ProgramId programId, int startIndex, - int bufferIdCount) - { - throw new NotImplementedException(); - } - - public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) - { - if (saveDataSize < 0 || saveDataJournalSize < 0) - { - return ResultFs.InvalidSize.Log(); - } - - SaveDataSize = saveDataSize; - SaveDataJournalSize = saveDataJournalSize; - - return Result.Success; - } - public Result SetSaveDataRootPath(ref FsPath path) - { - // Missing permission check - - if (StringUtils.GetLength(path.Str, FsPath.MaxLength + 1) > FsPath.MaxLength) - { - return ResultFs.TooLongPath.Log(); - } - - StringUtils.Copy(SaveDataRootPath.Str, path.Str); - - return Result.Success; - } - - public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId) - { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenContentStorageFileSystem(out fileSystem, storageId); - } - - public Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId) - { - throw new NotImplementedException(); - } - - public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId) - { - // Missing permission check, speed emulation storage type wrapper, and FileSystemInterfaceAdapter - - return FsProxyCore.OpenCustomStorageFileSystem(out fileSystem, storageId); - } - - public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, - GameCardPartition partitionId) - { - // Missing permission check and FileSystemInterfaceAdapter - - return FsProxyCore.OpenGameCardFileSystem(out fileSystem, handle, partitionId); - } - - public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) - { - // todo: Implement properly - - totalSize = 0; - - return Result.Success; - } - - public Result SetCurrentPosixTimeWithTimeDifference(long time, int difference) - { - throw new NotImplementedException(); - } - - public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) - { - throw new NotImplementedException(); - } - - public Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path) - { - throw new NotImplementedException(); - } - - public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path) - { - throw new NotImplementedException(); - } - - public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey) - { - // Missing permission check - - return FsProxyCore.RegisterExternalKey(ref rightsId, ref externalKey); - } - - public Result UnregisterExternalKey(ref RightsId rightsId) - { - // Missing permission check - - return FsProxyCore.UnregisterExternalKey(ref rightsId); - } - - public Result UnregisterAllExternalKey() - { - // Missing permission check - - return FsProxyCore.UnregisterAllExternalKey(); - } - - public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed) - { - // Missing permission check - - Result rc = FsProxyCore.SetSdCardEncryptionSeed(ref seed); - if (rc.IsFailure()) return rc; - - return Result.Success; - } - - public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span readBuffer) - { - throw new NotImplementedException(); - } - - public Result VerifySaveDataFileSystem(ulong saveDataId, Span readBuffer) - { - throw new NotImplementedException(); - } - - public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) - { - throw new NotImplementedException(); - } - - public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result CorruptSaveDataFileSystem(ulong saveDataId) - { - throw new NotImplementedException(); - } - - public Result CreatePaddingFile(long size) - { - throw new NotImplementedException(); - } - - public Result DeleteAllPaddingFiles() - { - throw new NotImplementedException(); - } - - public Result DisableAutoSaveDataCreation() - { - AutoCreateSaveData = false; - - return Result.Success; - } - - public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode) - { - // Missing permission check - - return FsProxyCore.SetGlobalAccessLogMode(mode); - } - - public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode) - { - return FsProxyCore.GetGlobalAccessLogMode(out mode); - } - - public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) - { - return GetProgramRegistryService().GetProgramIndexForAccessLog(out programIndex, out programCount); - } - - public Result OutputAccessLogToSdCard(U8Span logString) - { - throw new NotImplementedException(); - } - - public Result RegisterUpdatePartition() - { - throw new NotImplementedException(); - } - - public Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem) - { - throw new NotImplementedException(); - } - - public Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan key) - { - throw new NotImplementedException(); - } - - public Result CleanUpTemporaryStorage() - { - Result rc = FsProxyCore.OpenSaveDataDirectory(out IFileSystem saveDirFs, SaveDataSpaceId.Temporary, - string.Empty, false); - if (rc.IsFailure()) return rc; - - rc = saveDirFs.CleanDirectoryRecursively("/".ToU8Span()); - if (rc.IsFailure()) return rc; - - FsProxyCore.SaveDataIndexerManager.ResetTemporaryStorageIndexer(SaveDataSpaceId.Temporary); - - return Result.Success; - } - - public Result SetSdCardAccessibility(bool isAccessible) - { - // Missing permission check - - FsProxyCore.IsSdCardAccessible = isAccessible; - return Result.Success; - } - - public Result IsSdCardAccessible(out bool isAccessible) - { - isAccessible = FsProxyCore.IsSdCardAccessible; - return Result.Success; - } - - public Result OpenMultiCommitManager(out IMultiCommitManager commitManager) - { - commitManager = new MultiCommitManager(this); - return Result.Success; - } - - internal Result OpenMultiCommitContextSaveData(out IFileSystem fileSystem) - { - fileSystem = default; - - var attribute = new SaveDataAttribute(new ProgramId(MultiCommitManager.ProgramId), SaveDataType.System, - UserId.Zero, MultiCommitManager.SaveDataId); - - Result rc = OpenSaveDataFileSystemImpl(out IFileSystem saveFs, out _, SaveDataSpaceId.System, ref attribute, - false, true); - if (rc.IsFailure()) return rc; - - fileSystem = saveFs; - return Result.Success; - } - - // todo: split the FileSystemProxy classes - // nn::fssrv::SaveDataFileSystemService::GetSaveDataIndexerAccessor - private Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId spaceId) - { - accessor = default; - - Result rc = FsProxyCore.OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessorTemp, - out bool neededInit, spaceId); - if (rc.IsFailure()) return rc; - - try - { - if (neededInit) - { - // todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore - // nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore - } - - accessor = accessorTemp; - accessorTemp = null; - - return Result.Success; - } - finally - { - accessorTemp?.Dispose(); - } - } - - private Result GetProgramInfo(out ProgramInfo programInfo) - { - return FsProxyCore.ProgramRegistry.GetProgramInfo(out programInfo, CurrentProcess); - } - - internal bool IsCurrentProcess(ulong processId) - { - ulong currentId = Hos.Os.GetCurrentProcessId().Value; - - return processId == currentId; - } - - private static bool IsSystemSaveDataId(ulong id) - { - return (long)id < 0; - } - - private static SaveDataSpaceId GetSpaceIdForIndexer(SaveDataSpaceId spaceId) - { - return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode - ? SaveDataSpaceId.System - : spaceId; - } - } -} diff --git a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs index ed15e0fd..d7de11da 100644 --- a/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs +++ b/src/LibHac/FsSrv/FileSystemProxyConfiguration.cs @@ -5,6 +5,12 @@ namespace LibHac.FsSrv public class FileSystemProxyConfiguration { public FileSystemCreators FsCreatorInterfaces { get; set; } - public ProgramRegistryServiceImpl ProgramRegistryServiceImpl { get; set; } + public BaseStorageServiceImpl BaseStorageService { get; set; } + public BaseFileSystemServiceImpl BaseFileSystemService { get; set; } + public NcaFileSystemServiceImpl NcaFileSystemService { get; set; } + public SaveDataFileSystemServiceImpl SaveDataFileSystemService { get; set; } + public TimeServiceImpl TimeService { get; set; } + public ProgramRegistryServiceImpl ProgramRegistryService { get; set; } + public AccessLogServiceImpl AccessLogService { get; set; } } } diff --git a/src/LibHac/FsSrv/FileSystemProxyCore.cs b/src/LibHac/FsSrv/FileSystemProxyCore.cs deleted file mode 100644 index a216178b..00000000 --- a/src/LibHac/FsSrv/FileSystemProxyCore.cs +++ /dev/null @@ -1,1086 +0,0 @@ -using System; -using System.Buffers.Text; -using System.Runtime.CompilerServices; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Fs.Shim; -using LibHac.FsSystem; -using LibHac.FsSrv.Creators; -using LibHac.FsSystem.NcaUtils; -using LibHac.Spl; -using LibHac.Util; -using RightsId = LibHac.Fs.RightsId; - -namespace LibHac.FsSrv -{ - public class FileSystemProxyCore - { - internal FileSystemProxyConfiguration Config { get; } - private FileSystemCreators FsCreators => Config.FsCreatorInterfaces; - internal ProgramRegistryImpl ProgramRegistry { get; } - - private ExternalKeySet ExternalKeys { get; } - private IDeviceOperator DeviceOperator { get; } - - private byte[] SdEncryptionSeed { get; } = new byte[0x10]; - - private const string NintendoDirectoryName = "Nintendo"; - private const string ContentDirectoryName = "Contents"; - - private GlobalAccessLogMode LogMode { get; set; } - public bool IsSdCardAccessible { get; set; } - - internal ISaveDataIndexerManager SaveDataIndexerManager { get; private set; } - - public FileSystemProxyCore(FileSystemProxyConfiguration config, ExternalKeySet externalKeys, IDeviceOperator deviceOperator) - { - Config = config; - ProgramRegistry = new ProgramRegistryImpl(Config.ProgramRegistryServiceImpl); - ExternalKeys = externalKeys ?? new ExternalKeySet(); - DeviceOperator = deviceOperator; - } - - public Result OpenFileSystem(out IFileSystem fileSystem, U8Span path, FileSystemProxyType type, - bool canMountSystemDataPrivate, ulong programId) - { - fileSystem = default; - - // Get a reference to the path that will be advanced as each part of the path is parsed - U8Span currentPath = path.Slice(0, StringUtils.GetLength(path)); - - // Open the root filesystem based on the path's mount name - Result rc = OpenFileSystemFromMountName(ref currentPath, out IFileSystem baseFileSystem, out bool shouldContinue, - out MountNameInfo mountNameInfo); - if (rc.IsFailure()) return rc; - - // Don't continue if the rest of the path is empty - if (!shouldContinue) - return ResultFs.InvalidArgument.Log(); - - if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard) - { - rc = OpenGameCardFileSystem(out fileSystem, new GameCardHandle(mountNameInfo.GcHandle), - GameCardPartition.Logo); - - if (rc.IsSuccess()) - return Result.Success; - - if (!ResultFs.PartitionNotFound.Includes(rc)) - return rc; - } - - rc = IsContentPathDir(ref currentPath, out bool isDirectory); - if (rc.IsFailure()) return rc; - - if (isDirectory) - { - if (!mountNameInfo.IsHostFs) - return ResultFs.PermissionDenied.Log(); - - if (type == FileSystemProxyType.Manual) - { - rc = TryOpenCaseSensitiveContentDirectory(out IFileSystem manualFileSystem, baseFileSystem, currentPath); - if (rc.IsFailure()) return rc; - - fileSystem = new ReadOnlyFileSystem(manualFileSystem); - return Result.Success; - } - - return TryOpenContentDirectory(currentPath, out fileSystem, baseFileSystem, type, true); - } - - rc = TryOpenNsp(ref currentPath, out IFileSystem nspFileSystem, baseFileSystem); - - if (rc.IsSuccess()) - { - // Must be the end of the path to open Application Package FS type - if (currentPath.Length == 0 || currentPath[0] == 0) - { - if (type == FileSystemProxyType.Package) - { - fileSystem = nspFileSystem; - return Result.Success; - } - - return ResultFs.InvalidArgument.Log(); - } - - baseFileSystem = nspFileSystem; - } - - if (!mountNameInfo.CanMountNca) - { - return ResultFs.InvalidNcaMountPoint.Log(); - } - - ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : programId; - - rc = TryOpenNca(ref currentPath, out Nca nca, baseFileSystem, openProgramId); - if (rc.IsFailure()) return rc; - - rc = OpenNcaStorage(out IStorage ncaSectionStorage, nca, out NcaFormatType fsType, type, - mountNameInfo.IsGameCard, canMountSystemDataPrivate); - if (rc.IsFailure()) return rc; - - switch (fsType) - { - case NcaFormatType.Romfs: - return FsCreators.RomFileSystemCreator.Create(out fileSystem, ncaSectionStorage); - case NcaFormatType.Pfs0: - return FsCreators.PartitionFileSystemCreator.Create(out fileSystem, ncaSectionStorage); - default: - return ResultFs.InvalidNcaFsType.Log(); - } - } - - /// - /// Stores info obtained by parsing a common mount name. - /// - private struct MountNameInfo - { - public bool IsGameCard; - public int GcHandle; - public bool IsHostFs; - public bool CanMountNca; - } - - private Result OpenFileSystemFromMountName(ref U8Span path, out IFileSystem fileSystem, out bool shouldContinue, - out MountNameInfo info) - { - fileSystem = default; - - info = new MountNameInfo(); - shouldContinue = true; - - if (StringUtils.Compare(path, CommonMountNames.GameCardFileSystemMountName, - CommonMountNames.GameCardFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.GameCardFileSystemMountName.Length); - - if (StringUtils.GetLength(path.Value, 9) < 9) - return ResultFs.InvalidPath.Log(); - - GameCardPartition partition; - switch ((char)path[0]) - { - case CommonMountNames.GameCardFileSystemMountNameUpdateSuffix: - partition = GameCardPartition.Update; - break; - case CommonMountNames.GameCardFileSystemMountNameNormalSuffix: - partition = GameCardPartition.Normal; - break; - case CommonMountNames.GameCardFileSystemMountNameSecureSuffix: - partition = GameCardPartition.Secure; - break; - default: - return ResultFs.InvalidPath.Log(); - } - - path = path.Slice(1); - bool handleParsed = Utf8Parser.TryParse(path, out int handle, out int bytesConsumed); - - if (!handleParsed || handle == -1 || bytesConsumed != 8) - return ResultFs.InvalidPath.Log(); - - path = path.Slice(8); - - Result rc = OpenGameCardFileSystem(out fileSystem, new GameCardHandle(handle), partition); - if (rc.IsFailure()) return rc; - - info.GcHandle = handle; - info.IsGameCard = true; - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSystemMountName, - CommonMountNames.ContentStorageSystemMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.ContentStorageSystemMountName.Length); - - Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.System); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonMountNames.ContentStorageUserMountName, - CommonMountNames.ContentStorageUserMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.ContentStorageUserMountName.Length); - - Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.User); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonMountNames.ContentStorageSdCardMountName, - CommonMountNames.ContentStorageSdCardMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.ContentStorageSdCardMountName.Length); - - Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.SdCard); - if (rc.IsFailure()) return rc; - - info.CanMountNca = true; - } - - else if (StringUtils.Compare(path, CommonMountNames.BisCalibrationFilePartitionMountName, - CommonMountNames.BisCalibrationFilePartitionMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.BisCalibrationFilePartitionMountName.Length); - - Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.CalibrationFile); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.BisSafeModePartitionMountName, - CommonMountNames.BisSafeModePartitionMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.BisSafeModePartitionMountName.Length); - - Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.SafeMode); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.BisUserPartitionMountName, - CommonMountNames.BisUserPartitionMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.BisUserPartitionMountName.Length); - - Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.User); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.BisSystemPartitionMountName, - CommonMountNames.BisSystemPartitionMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.BisSystemPartitionMountName.Length); - - Result rc = OpenBisFileSystem(out fileSystem, string.Empty, BisPartitionId.System); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.SdCardFileSystemMountName, - CommonMountNames.SdCardFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.SdCardFileSystemMountName.Length); - - Result rc = OpenSdCardFileSystem(out fileSystem); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.HostRootFileSystemMountName, - CommonMountNames.HostRootFileSystemMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.HostRootFileSystemMountName.Length); - - info.IsHostFs = true; - info.CanMountNca = true; - - Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false); - if (rc.IsFailure()) return rc; - } - - else if (StringUtils.Compare(path, CommonMountNames.RegisteredUpdatePartitionMountName, - CommonMountNames.RegisteredUpdatePartitionMountName.Length) == 0) - { - path = path.Slice(CommonMountNames.RegisteredUpdatePartitionMountName.Length); - - info.CanMountNca = true; - - throw new NotImplementedException(); - } - - else - { - return ResultFs.PathNotFound.Log(); - } - - if (StringUtils.GetLength(path, FsPath.MaxLength) == 0) - { - shouldContinue = false; - } - - return Result.Success; - } - - private Result IsContentPathDir(ref U8Span path, out bool isDirectory) - { - isDirectory = default; - - ReadOnlySpan mountSeparator = new[] { (byte)':', (byte)'/' }; - - if (StringUtils.Compare(mountSeparator, path, mountSeparator.Length) != 0) - { - return ResultFs.PathNotFound.Log(); - } - - path = path.Slice(1); - int pathLen = StringUtils.GetLength(path); - - if (path[pathLen - 1] == '/') - { - isDirectory = true; - return Result.Success; - } - - // Now make sure the path has a content file extension - if (pathLen < 5) - return ResultFs.PathNotFound.Log(); - - ReadOnlySpan fileExtension = path.Value.Slice(pathLen - 4); - - ReadOnlySpan ncaExtension = new[] { (byte)'.', (byte)'n', (byte)'c', (byte)'a' }; - ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; - - if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0 || - StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0) - { - isDirectory = false; - return Result.Success; - } - - return ResultFs.PathNotFound.Log(); - } - - private Result TryOpenContentDirectory(U8Span path, out IFileSystem contentFileSystem, - IFileSystem baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) - { - contentFileSystem = default; - - FsPath fullPath; - unsafe { _ = &fullPath; } // workaround for CS0165 - - Result rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs, - baseFileSystem, path, preserveUnc); - if (rc.IsFailure()) return rc; - - return OpenSubDirectoryForFsType(out contentFileSystem, subDirFs, fsType); - } - - private Result TryOpenCaseSensitiveContentDirectory(out IFileSystem contentFileSystem, - IFileSystem baseFileSystem, U8Span path) - { - contentFileSystem = default; - FsPath fullPath; - unsafe { _ = &fullPath; } // workaround for CS0165 - - var sb = new U8StringBuilder(fullPath.Str); - sb.Append(path) - .Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }); - - if (sb.Overflowed) - return ResultFs.TooLongPath.Log(); - - Result rc = FsCreators.TargetManagerFileSystemCreator.GetCaseSensitivePath(out bool success, fullPath.Str); - if (rc.IsFailure()) return rc; - - // Reopen the host filesystem as case sensitive - if (success) - { - baseFileSystem.Dispose(); - - rc = OpenHostFileSystem(out baseFileSystem, U8Span.Empty, openCaseSensitive: true); - if (rc.IsFailure()) return rc; - } - - return FsCreators.SubDirectoryFileSystemCreator.Create(out contentFileSystem, baseFileSystem, fullPath); - } - - private Result OpenSubDirectoryForFsType(out IFileSystem fileSystem, IFileSystem baseFileSystem, - FileSystemProxyType fsType) - { - fileSystem = default; - ReadOnlySpan dirName; - - // Get the name of the subdirectory for the filesystem type - switch (fsType) - { - case FileSystemProxyType.Package: - fileSystem = baseFileSystem; - return Result.Success; - - case FileSystemProxyType.Code: - dirName = new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'d', (byte)'e', (byte)'/' }; - break; - case FileSystemProxyType.Rom: - case FileSystemProxyType.Control: - case FileSystemProxyType.Manual: - case FileSystemProxyType.Meta: - case FileSystemProxyType.RegisteredUpdate: - dirName = new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }; - break; - case FileSystemProxyType.Logo: - dirName = new[] { (byte)'/', (byte)'l', (byte)'o', (byte)'g', (byte)'o', (byte)'/' }; - break; - - default: - return ResultFs.InvalidArgument.Log(); - } - - // Open the subdirectory filesystem - Result rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs, baseFileSystem, - new U8Span(dirName)); - if (rc.IsFailure()) return rc; - - if (fsType == FileSystemProxyType.Code) - { - rc = FsCreators.StorageOnNcaCreator.VerifyAcidSignature(subDirFs, null); - if (rc.IsFailure()) return rc; - } - - fileSystem = subDirFs; - return Result.Success; - } - - private Result TryOpenNsp(ref U8Span path, out IFileSystem outFileSystem, IFileSystem baseFileSystem) - { - outFileSystem = default; - - ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; - - // Search for the end of the nsp part of the path - int nspPathLen = 0; - - while (true) - { - U8Span currentSpan; - - while (true) - { - currentSpan = path.Slice(nspPathLen); - if (StringUtils.CompareCaseInsensitive(nspExtension, currentSpan, 4) == 0) - break; - - if (currentSpan.Length == 0 || currentSpan[0] == 0) - { - return ResultFs.PathNotFound.Log(); - } - - nspPathLen++; - } - - // The nsp filename must be the end of the entire path or the end of a path segment - if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/') - break; - - nspPathLen += 4; - } - - nspPathLen += 4; - - if (nspPathLen > FsPath.MaxLength + 1) - return ResultFs.TooLongPath.Log(); - - Result rc = FsPath.FromSpan(out FsPath nspPath, path.Slice(0, nspPathLen)); - if (rc.IsFailure()) return rc; - - rc = FileStorageBasedFileSystem.CreateNew(out FileStorageBasedFileSystem nspFileStorage, baseFileSystem, - new U8Span(nspPath.Str), OpenMode.Read); - if (rc.IsFailure()) return rc; - - rc = FsCreators.PartitionFileSystemCreator.Create(out outFileSystem, nspFileStorage); - - if (rc.IsSuccess()) - { - path = path.Slice(nspPathLen); - } - - return rc; - } - - private Result TryOpenNca(ref U8Span path, out Nca nca, IFileSystem baseFileSystem, ulong ncaId) - { - nca = default; - - Result rc = FileStorageBasedFileSystem.CreateNew(out FileStorageBasedFileSystem ncaFileStorage, - baseFileSystem, path, OpenMode.Read); - if (rc.IsFailure()) return rc; - - rc = FsCreators.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage); - if (rc.IsFailure()) return rc; - - if (ncaId == ulong.MaxValue) - { - ulong ncaProgramId = ncaTemp.Header.TitleId; - - if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId) - { - return ResultFs.InvalidNcaProgramId.Log(); - } - } - - nca = ncaTemp; - return Result.Success; - } - - private Result OpenNcaStorage(out IStorage ncaStorage, Nca nca, out NcaFormatType fsType, - FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate) - { - ncaStorage = default; - fsType = default; - - NcaContentType contentType = nca.Header.ContentType; - - switch (fsProxyType) - { - case FileSystemProxyType.Code: - case FileSystemProxyType.Rom: - case FileSystemProxyType.Logo: - case FileSystemProxyType.RegisteredUpdate: - if (contentType != NcaContentType.Program) - return ResultFs.PreconditionViolation.Log(); - - break; - - case FileSystemProxyType.Control: - if (contentType != NcaContentType.Control) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Manual: - if (contentType != NcaContentType.Manual) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Meta: - if (contentType != NcaContentType.Meta) - return ResultFs.PreconditionViolation.Log(); - - break; - case FileSystemProxyType.Data: - if (contentType != NcaContentType.Data && contentType != NcaContentType.PublicData) - return ResultFs.PreconditionViolation.Log(); - - if (contentType == NcaContentType.Data && !canMountSystemDataPrivate) - return ResultFs.PermissionDenied.Log(); - - break; - default: - return ResultFs.InvalidArgument.Log(); - } - - if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard) - return ResultFs.PermissionDenied.Log(); - - Result rc = SetNcaExternalKey(nca); - if (rc.IsFailure()) return rc; - - rc = GetNcaSectionIndex(out int sectionIndex, fsProxyType); - if (rc.IsFailure()) return rc; - - rc = FsCreators.StorageOnNcaCreator.Create(out ncaStorage, out NcaFsHeader fsHeader, nca, - sectionIndex, fsProxyType == FileSystemProxyType.Code); - if (rc.IsFailure()) return rc; - - fsType = fsHeader.FormatType; - return Result.Success; - } - - private Result SetNcaExternalKey(Nca nca) - { - var rightsId = new RightsId(nca.Header.RightsId); - var zero = new RightsId(0, 0); - - if (Crypto.CryptoUtil.IsSameBytes(rightsId.AsBytes(), zero.AsBytes(), Unsafe.SizeOf())) - return Result.Success; - - // ReSharper disable once UnusedVariable - Result rc = ExternalKeys.Get(rightsId, out AccessKey accessKey); - if (rc.IsFailure()) return rc; - - // todo: Set key in nca reader - - return Result.Success; - } - - private Result GetNcaSectionIndex(out int index, FileSystemProxyType fspType) - { - switch (fspType) - { - case FileSystemProxyType.Code: - case FileSystemProxyType.Control: - case FileSystemProxyType.Manual: - case FileSystemProxyType.Meta: - case FileSystemProxyType.Data: - index = 0; - return Result.Success; - case FileSystemProxyType.Rom: - case FileSystemProxyType.RegisteredUpdate: - index = 1; - return Result.Success; - case FileSystemProxyType.Logo: - index = 2; - return Result.Success; - default: - index = default; - return ResultFs.InvalidArgument.Log(); - } - } - - public Result OpenBisFileSystem(out IFileSystem fileSystem, string rootPath, BisPartitionId partitionId) - { - return FsCreators.BuiltInStorageFileSystemCreator.Create(out fileSystem, rootPath, partitionId); - } - - public Result OpenSdCardFileSystem(out IFileSystem fileSystem) - { - return FsCreators.SdFileSystemCreator.Create(out fileSystem); - } - - public Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId) - { - switch (partitionId) - { - case GameCardPartitionRaw.NormalReadOnly: - return FsCreators.GameCardStorageCreator.CreateNormal(handle, out storage); - case GameCardPartitionRaw.SecureReadOnly: - return FsCreators.GameCardStorageCreator.CreateSecure(handle, out storage); - case GameCardPartitionRaw.RootWriteOnly: - return FsCreators.GameCardStorageCreator.CreateWritable(handle, out storage); - default: - throw new ArgumentOutOfRangeException(nameof(partitionId), partitionId, null); - } - } - - public Result OpenDeviceOperator(out IDeviceOperator deviceOperator) - { - deviceOperator = DeviceOperator; - return Result.Success; - } - - public Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId) - { - fileSystem = default; - - U8String contentDirPath = default; - IFileSystem baseFileSystem = default; - bool isEncrypted = false; - Result rc; - - switch (storageId) - { - case ContentStorageId.System: - rc = OpenBisFileSystem(out baseFileSystem, string.Empty, BisPartitionId.System); - contentDirPath = $"/{ContentDirectoryName}".ToU8String(); - break; - case ContentStorageId.User: - rc = OpenBisFileSystem(out baseFileSystem, string.Empty, BisPartitionId.User); - contentDirPath = $"/{ContentDirectoryName}".ToU8String(); - break; - case ContentStorageId.SdCard: - rc = OpenSdCardFileSystem(out baseFileSystem); - contentDirPath = $"/{NintendoDirectoryName}/{ContentDirectoryName}".ToU8String(); - isEncrypted = true; - break; - default: - rc = ResultFs.InvalidArgument.Log(); - break; - } - - if (rc.IsFailure()) return rc; - - rc = baseFileSystem.EnsureDirectoryExists(contentDirPath.ToString()); - if (rc.IsFailure()) return rc; - - rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFileSystem, - baseFileSystem, contentDirPath); - if (rc.IsFailure()) return rc; - - if (!isEncrypted) - { - fileSystem = subDirFileSystem; - return Result.Success; - } - - return FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, subDirFileSystem, - EncryptedFsKeyId.Content, SdEncryptionSeed); - } - - public Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId) - { - fileSystem = default; - - switch (storageId) - { - case CustomStorageId.SdCard: - { - Result rc = FsCreators.SdFileSystemCreator.Create(out IFileSystem sdFs); - if (rc.IsFailure()) return rc; - - string customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard); - string subDirName = $"/{NintendoDirectoryName}/{customStorageDir}"; - - rc = Util.CreateSubFileSystem(out IFileSystem subFs, sdFs, subDirName, true); - if (rc.IsFailure()) return rc; - - rc = FsCreators.EncryptedFileSystemCreator.Create(out IFileSystem encryptedFs, subFs, - EncryptedFsKeyId.CustomStorage, SdEncryptionSeed); - if (rc.IsFailure()) return rc; - - fileSystem = encryptedFs; - return Result.Success; - } - case CustomStorageId.System: - { - Result rc = FsCreators.BuiltInStorageFileSystemCreator.Create(out IFileSystem userFs, string.Empty, - BisPartitionId.User); - if (rc.IsFailure()) return rc; - - string customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System); - string subDirName = $"/{customStorageDir}"; - - rc = Util.CreateSubFileSystem(out IFileSystem subFs, userFs, subDirName, true); - if (rc.IsFailure()) return rc; - - fileSystem = subFs; - return Result.Success; - } - default: - return ResultFs.InvalidArgument.Log(); - } - } - - public Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, - GameCardPartition partitionId) - { - Result rc; - int tries = 0; - - do - { - rc = FsCreators.GameCardFileSystemCreator.Create(out fileSystem, handle, partitionId); - - if (!ResultFs.DataCorrupted.Includes(rc)) - break; - - tries++; - } while (tries < 2); - - return rc; - } - - public Result OpenHostFileSystem(out IFileSystem fileSystem, U8Span path, bool openCaseSensitive) - { - fileSystem = default; - Result rc; - - if (!path.IsEmpty()) - { - rc = Util.VerifyHostPath(path); - if (rc.IsFailure()) return rc; - } - - rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, openCaseSensitive); - if (rc.IsFailure()) return rc; - - if (path.IsEmpty()) - { - ReadOnlySpan rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' }; - rc = hostFs.GetEntryType(out _, new U8Span(rootHostPath)); - - // Nintendo ignores all results other than this one - if (ResultFs.TargetNotFound.Includes(rc)) - return rc; - - fileSystem = hostFs; - return Result.Success; - } - - rc = FsCreators.SubDirectoryFileSystemCreator.Create(out IFileSystem subDirFs, hostFs, path, preserveUnc: true); - if (rc.IsFailure()) return rc; - - fileSystem = subDirFs; - return Result.Success; - } - - public Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey) - { - return ExternalKeys.Add(rightsId, externalKey); - } - - public Result UnregisterExternalKey(ref RightsId rightsId) - { - ExternalKeys.Remove(rightsId); - - return Result.Success; - } - - public Result UnregisterAllExternalKey() - { - ExternalKeys.Clear(); - - return Result.Success; - } - - public Result SetSdCardEncryptionSeed(ref EncryptionSeed seed) - { - seed.Value.CopyTo(SdEncryptionSeed); - // todo: FsCreators.SaveDataFileSystemCreator.SetSdCardEncryptionSeed(seed); - - SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdSystem); - SaveDataIndexerManager.InvalidateSdCardIndexer(SaveDataSpaceId.SdCache); - - return Result.Success; - } - - public bool AllowDirectorySaveData(SaveDataSpaceId spaceId, string saveDataRootPath) - { - return spaceId == SaveDataSpaceId.User && !string.IsNullOrWhiteSpace(saveDataRootPath); - } - - public Result DoesSaveDataExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId) - { - exists = false; - - Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, spaceId, string.Empty, true); - if (rc.IsFailure()) return rc; - - string saveDataPath = $"/{saveDataId:x16}"; - - rc = fileSystem.GetEntryType(out _, saveDataPath.ToU8Span()); - - if (rc.IsFailure()) - { - if (ResultFs.PathNotFound.Includes(rc)) - { - return Result.Success; - } - - return rc; - } - - exists = true; - return Result.Success; - } - - public Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, - string saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) - { - fileSystem = default; - - Result rc = OpenSaveDataDirectory(out IFileSystem saveDirFs, spaceId, saveDataRootPath, true); - if (rc.IsFailure()) return rc; - - // ReSharper disable once RedundantAssignment - bool allowDirectorySaveData = AllowDirectorySaveData(spaceId, saveDataRootPath); - bool useDeviceUniqueMac = Util.UseDeviceUniqueSaveMac(spaceId); - - // Always allow directory savedata because we don't support transaction with file savedata yet - allowDirectorySaveData = true; - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (allowDirectorySaveData) - { - rc = saveDirFs.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId)); - if (rc.IsFailure()) return rc; - } - - // Missing save FS cache lookup - - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - rc = FsCreators.SaveDataFileSystemCreator.Create(out IFileSystem saveFs, out _, saveDirFs, saveDataId, - allowDirectorySaveData, useDeviceUniqueMac, type, null); - - if (rc.IsFailure()) return rc; - - if (cacheExtraData) - { - // todo: Missing extra data caching - } - - fileSystem = openReadOnly ? new ReadOnlyFileSystem(saveFs) : saveFs; - - return Result.Success; - } - - public Result OpenSaveDataDirectory(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDataRootPath, bool openOnHostFs) - { - if (openOnHostFs && AllowDirectorySaveData(spaceId, saveDataRootPath)) - { - Result rc = FsCreators.TargetManagerFileSystemCreator.Create(out IFileSystem hostFs, false); - - if (rc.IsFailure()) - { - fileSystem = default; - return rc; - } - - return Util.CreateSubFileSystem(out fileSystem, hostFs, saveDataRootPath, true); - } - - string dirName = spaceId == SaveDataSpaceId.Temporary ? "/temp" : "/save"; - - return OpenSaveDataDirectoryImpl(out fileSystem, spaceId, dirName, true); - } - - public Result OpenSaveDataDirectoryImpl(out IFileSystem fileSystem, SaveDataSpaceId spaceId, string saveDirName, bool createIfMissing) - { - fileSystem = default; - Result rc; - - switch (spaceId) - { - case SaveDataSpaceId.System: - rc = OpenBisFileSystem(out IFileSystem sysFs, string.Empty, BisPartitionId.System); - if (rc.IsFailure()) return rc; - - return Util.CreateSubFileSystem(out fileSystem, sysFs, saveDirName, createIfMissing); - - case SaveDataSpaceId.User: - case SaveDataSpaceId.Temporary: - rc = OpenBisFileSystem(out IFileSystem userFs, string.Empty, BisPartitionId.User); - if (rc.IsFailure()) return rc; - - return Util.CreateSubFileSystem(out fileSystem, userFs, saveDirName, createIfMissing); - - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.SdCache: - rc = OpenSdCardFileSystem(out IFileSystem sdFs); - if (rc.IsFailure()) return rc; - - string sdSaveDirPath = $"/{NintendoDirectoryName}{saveDirName}"; - - rc = Util.CreateSubFileSystem(out IFileSystem sdSubFs, sdFs, sdSaveDirPath, createIfMissing); - if (rc.IsFailure()) return rc; - - return FsCreators.EncryptedFileSystemCreator.Create(out fileSystem, sdSubFs, - EncryptedFsKeyId.Save, SdEncryptionSeed); - - case SaveDataSpaceId.ProperSystem: - rc = OpenBisFileSystem(out IFileSystem sysProperFs, string.Empty, BisPartitionId.SystemProperPartition); - if (rc.IsFailure()) return rc; - - return Util.CreateSubFileSystem(out fileSystem, sysProperFs, saveDirName, createIfMissing); - - case SaveDataSpaceId.SafeMode: - rc = OpenBisFileSystem(out IFileSystem safeFs, string.Empty, BisPartitionId.SafeMode); - if (rc.IsFailure()) return rc; - - return Util.CreateSubFileSystem(out fileSystem, safeFs, saveDirName, createIfMissing); - - default: - return ResultFs.InvalidArgument.Log(); - } - } - - public Result OpenSaveDataMetaFile(out IFile file, ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType type) - { - file = default; - - string metaDirPath = $"/saveMeta/{saveDataId:x16}"; - - Result rc = OpenSaveDataDirectoryImpl(out IFileSystem tmpMetaDirFs, spaceId, metaDirPath, true); - using IFileSystem metaDirFs = tmpMetaDirFs; - if (rc.IsFailure()) return rc; - - string metaFilePath = $"/{(int)type:x8}.meta"; - - return metaDirFs.OpenFile(out file, metaFilePath.ToU8Span(), OpenMode.ReadWrite); - } - - public Result DeleteSaveDataMetaFiles(ulong saveDataId, SaveDataSpaceId spaceId) - { - Result rc = OpenSaveDataDirectoryImpl(out IFileSystem metaDirFs, spaceId, "/saveMeta", false); - - using (metaDirFs) - { - if (rc.IsFailure()) return rc; - - rc = metaDirFs.DeleteDirectoryRecursively($"/{saveDataId:x16}".ToU8Span()); - - if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) - return rc; - - return Result.Success; - } - } - - public Result CreateSaveDataMetaFile(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType type, long size) - { - string metaDirPath = $"/saveMeta/{saveDataId:x16}"; - - Result rc = OpenSaveDataDirectoryImpl(out IFileSystem tmpMetaDirFs, spaceId, metaDirPath, true); - using IFileSystem metaDirFs = tmpMetaDirFs; - if (rc.IsFailure()) return rc; - - string metaFilePath = $"/{(int)type:x8}.meta"; - - if (size < 0) return ResultFs.OutOfRange.Log(); - - return metaDirFs.CreateFile(metaFilePath.ToU8Span(), size, CreateFileOptions.None); - } - - public Result CreateSaveDataFileSystem(ulong saveDataId, ref SaveDataAttribute attribute, - ref SaveDataCreationInfo creationInfo, U8Span rootPath, OptionalHashSalt hashSalt, bool something) - { - // Use directory save data for now - - Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, creationInfo.SpaceId, string.Empty, false); - if (rc.IsFailure()) return rc; - - return fileSystem.EnsureDirectoryExists(GetSaveDataIdPath(saveDataId)); - } - - public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool doSecureDelete) - { - Result rc = OpenSaveDataDirectory(out IFileSystem fileSystem, spaceId, string.Empty, false); - - using (fileSystem) - { - if (rc.IsFailure()) return rc; - - var saveDataPath = GetSaveDataIdPath(saveDataId).ToU8Span(); - - rc = fileSystem.GetEntryType(out DirectoryEntryType entryType, saveDataPath); - if (rc.IsFailure()) return rc; - - if (entryType == DirectoryEntryType.Directory) - { - rc = fileSystem.DeleteDirectoryRecursively(saveDataPath); - } - else - { - if (doSecureDelete) - { - // Overwrite file with garbage before deleting - throw new NotImplementedException(); - } - - rc = fileSystem.DeleteFile(saveDataPath); - } - - return rc; - } - } - - public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode) - { - LogMode = mode; - return Result.Success; - } - - public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode) - { - mode = LogMode; - return Result.Success; - } - - internal void SetSaveDataIndexerManager(ISaveDataIndexerManager manager) - { - SaveDataIndexerManager = manager; - } - - internal Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId) - { - return SaveDataIndexerManager.OpenAccessor(out accessor, out neededInit, spaceId); - } - - private string GetSaveDataIdPath(ulong saveDataId) - { - return $"/{saveDataId:x16}"; - } - } -} diff --git a/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs new file mode 100644 index 00000000..ea962aa5 --- /dev/null +++ b/src/LibHac/FsSrv/FileSystemProxyCoreImpl.cs @@ -0,0 +1,128 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Fs.Shim; +using LibHac.FsSrv.Creators; + +namespace LibHac.FsSrv +{ + public class FileSystemProxyCoreImpl + { + internal FileSystemProxyConfiguration Config { get; } + private FileSystemCreators FsCreators => Config.FsCreatorInterfaces; + internal ProgramRegistryImpl ProgramRegistry { get; } + + private byte[] SdEncryptionSeed { get; } = new byte[0x10]; + + private const string NintendoDirectoryName = "Nintendo"; + + public FileSystemProxyCoreImpl(FileSystemProxyConfiguration config) + { + Config = config; + ProgramRegistry = new ProgramRegistryImpl(Config.ProgramRegistryService); + } + + public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable fileSystem, + CustomStorageId storageId) + { + fileSystem = default; + + switch (storageId) + { + case CustomStorageId.SdCard: + { + Result rc = FsCreators.SdCardFileSystemCreator.Create(out IFileSystem sdFs, false); + if (rc.IsFailure()) return rc; + + string customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.SdCard); + string subDirName = $"/{NintendoDirectoryName}/{customStorageDir}"; + + rc = Util.CreateSubFileSystem(out IFileSystem subFs, sdFs, subDirName, true); + if (rc.IsFailure()) return rc; + + rc = FsCreators.EncryptedFileSystemCreator.Create(out IFileSystem encryptedFs, subFs, + EncryptedFsKeyId.CustomStorage, SdEncryptionSeed); + if (rc.IsFailure()) return rc; + + fileSystem = new ReferenceCountedDisposable(encryptedFs); + return Result.Success; + } + case CustomStorageId.System: + { + Result rc = FsCreators.BuiltInStorageFileSystemCreator.Create(out IFileSystem userFs, string.Empty, + BisPartitionId.User); + if (rc.IsFailure()) return rc; + + string customStorageDir = CustomStorage.GetCustomStorageDirectoryName(CustomStorageId.System); + string subDirName = $"/{customStorageDir}"; + + rc = Util.CreateSubFileSystem(out IFileSystem subFs, userFs, subDirName, true); + if (rc.IsFailure()) return rc; + + // Todo: Get shared object from earlier functions + fileSystem = new ReferenceCountedDisposable(subFs); + return Result.Success; + } + default: + return ResultFs.InvalidArgument.Log(); + } + } + + public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, + bool openCaseSensitive) + { + fileSystem = default; + 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()) + { + ReadOnlySpan rootHostPath = new[] { (byte)'C', (byte)':', (byte)'/' }; + rc = sharedHostFs.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 sharedHostFs); + return Result.Success; + } + + rc = FsCreators.SubDirectoryFileSystemCreator.Create(out subDirFs, ref sharedHostFs, path, + preserveUnc: true); + if (rc.IsFailure()) return rc; + + fileSystem = subDirFs; + return Result.Success; + } + finally + { + sharedHostFs?.Dispose(); + subDirFs?.Dispose(); + } + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + seed.Value.CopyTo(SdEncryptionSeed); + return Result.Success; + } + } +} diff --git a/src/LibHac/FsSrv/FileSystemProxyImpl.cs b/src/LibHac/FsSrv/FileSystemProxyImpl.cs new file mode 100644 index 00000000..d57e9c39 --- /dev/null +++ b/src/LibHac/FsSrv/FileSystemProxyImpl.cs @@ -0,0 +1,1030 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Spl; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.FsSrv +{ + public class FileSystemProxyImpl : IFileSystemProxy, IFileSystemProxyForLoader + { + private FileSystemProxyCoreImpl FsProxyCore { get; } + private ReferenceCountedDisposable NcaFsService { get; set; } + private ReferenceCountedDisposable SaveFsService { get; set; } + private ulong CurrentProcess { get; set; } + + internal FileSystemProxyImpl(FileSystemProxyCoreImpl fsProxyCore) + { + FsProxyCore = fsProxyCore; + CurrentProcess = ulong.MaxValue; + } + + public Result OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in FspPath path, + ulong id, FileSystemProxyType fsType) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenFileSystemWithId(out fileSystem, in path, id, fsType); + } + + public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, + ProgramId programId, FileSystemProxyType fsType) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenFileSystemWithPatch(out fileSystem, programId, fsType); + } + + public Result OpenCodeFileSystem(out ReferenceCountedDisposable fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId) + { + Unsafe.SkipInit(out verificationData); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenCodeFileSystem(out fileSystem, out verificationData, in path, programId); + } + + public Result IsArchivedProgram(out bool isArchived, ulong processId) + { + Unsafe.SkipInit(out isArchived); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.IsArchivedProgram(out isArchived, processId); + } + + public Result SetCurrentProcess(ulong processId) + { + CurrentProcess = processId; + + // Initialize the NCA file system service + NcaFsService = NcaFileSystemService.CreateShared(FsProxyCore.Config.NcaFileSystemService, processId); + + SaveFsService = SaveDataFileSystemService.CreateShared(FsProxyCore.Config.SaveDataFileSystemService, processId); + + return Result.Success; + } + + public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + { + Unsafe.SkipInit(out freeSpaceSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetFreeSpaceSizeForSaveData(out freeSpaceSize, spaceId); + } + + public Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable fileSystem) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenDataFileSystemByCurrentProcess(out fileSystem); + } + + public Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable fileSystem, + ProgramId programId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenDataFileSystemByProgramId(out fileSystem, programId); + } + + public Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable storage) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + storage = default; + return rc; + } + + return ncaFsService.OpenDataStorageByCurrentProcess(out storage); + } + + public Result OpenDataStorageByProgramId(out ReferenceCountedDisposable storage, ProgramId programId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + storage = default; + return rc; + } + + return ncaFsService.OpenDataStorageByProgramId(out storage, programId); + } + + public Result OpenDataStorageByDataId(out ReferenceCountedDisposable storage, DataId dataId, StorageId storageId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + storage = default; + return rc; + } + + return ncaFsService.OpenDataStorageByDataId(out storage, dataId, storageId); + } + + public Result OpenPatchDataStorageByCurrentProcess(out ReferenceCountedDisposable storage) + { + storage = default; + return ResultFs.TargetNotFound.Log(); + } + + public Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable fileSystem, + byte programIndex) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenDataFileSystemWithProgramIndex(out fileSystem, programIndex); + } + + public Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable storage, byte programIndex) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + storage = default; + return rc; + } + + return ncaFsService.OpenDataStorageWithProgramIndex(out storage, programIndex); + } + + public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.RegisterSaveDataFileSystemAtomicDeletion(saveDataIds); + } + + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystem(saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteSaveDataFileSystemBySaveDataAttribute(spaceId, in attribute); + } + + public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.UpdateSaveDataMacForDebug(spaceId, saveDataId); + } + + public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystem(in attribute, in creationInfo, in metaInfo); + } + + public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystemWithHashSalt(in attribute, in creationInfo, in metaInfo, + in hashSalt); + } + + public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CreateSaveDataFileSystemBySystemSaveDataId(in attribute, in creationInfo); + } + + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, + long journalSize) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ExtendSaveDataFileSystem(spaceId, saveDataId, dataSize, journalSize); + } + + public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return saveFsService.OpenSaveDataFileSystem(out fileSystem, spaceId, in attribute); + } + + public Result OpenReadOnlySaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return saveFsService.OpenReadOnlySaveDataFileSystem(out fileSystem, spaceId, in attribute); + } + + public Result OpenSaveDataFileSystemBySystemSaveDataId(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return saveFsService.OpenSaveDataFileSystemBySystemSaveDataId(out fileSystem, spaceId, in attribute); + } + + public Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraData(extraDataBuffer, saveDataId); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataAttribute(extraDataBuffer, spaceId, + in attribute); + } + + public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(extraDataBuffer, spaceId, + in attribute, maskBuffer); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(extraDataBuffer, spaceId, saveDataId); + } + + public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraDataBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraData(saveDataId, spaceId, extraDataBuffer); + } + + public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, + SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in attribute, spaceId, + extraDataBuffer, maskBuffer); + } + + public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraDataBuffer, InBuffer maskBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.WriteSaveDataFileSystemExtraDataWithMask(saveDataId, spaceId, extraDataBuffer, + maskBuffer); + } + + public Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, + ImageDirectoryId directoryId) + { + return GetBaseFileSystemService().OpenImageDirectoryFileSystem(out fileSystem, directoryId); + } + + public Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath rootPath, + BisPartitionId partitionId) + { + return GetBaseFileSystemService().OpenBisFileSystem(out fileSystem, in rootPath, partitionId); + } + + public Result OpenBisStorage(out ReferenceCountedDisposable storage, BisPartitionId partitionId) + { + return GetBaseStorageService().OpenBisStorage(out storage, partitionId); + } + + public Result InvalidateBisCache() + { + return GetBaseStorageService().InvalidateBisCache(); + } + + public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath path) + { + return OpenHostFileSystemWithOption(out fileSystem, in path, MountHostOption.None); + } + + public Result OpenHostFileSystemWithOption(out ReferenceCountedDisposable fileSystem, + in FspPath path, MountHostOption option) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountHost); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable hostFs = null; + try + { + rc = FsProxyCore.OpenHostFileSystem(out hostFs, new U8Span(path.Str), + option.HasFlag(MountHostOption.PseudoCaseSensitive)); + if (rc.IsFailure()) return rc; + + bool isRootPath = path.Str[0] == 0; + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref hostFs, isRootPath); + + if (fileSystem is null) + return ResultFs.AllocationFailureInCreateShared.Log(); + + return Result.Success; + } + finally + { + hostFs?.Dispose(); + } + } + + public Result OpenSdCardFileSystem(out ReferenceCountedDisposable fileSystem) + { + return GetBaseFileSystemService().OpenSdCardFileSystem(out fileSystem); + } + + public Result FormatSdCardFileSystem() + { + return GetBaseFileSystemService().FormatSdCardFileSystem(); + } + + public Result FormatSdCardDryRun() + { + return GetBaseFileSystemService().FormatSdCardDryRun(); + } + + public Result IsExFatSupported(out bool isSupported) + { + return GetBaseFileSystemService().IsExFatSupported(out isSupported); + } + + public Result OpenGameCardStorage(out ReferenceCountedDisposable storage, GameCardHandle handle, + GameCardPartitionRaw partitionId) + { + return GetBaseStorageService().OpenGameCardStorage(out storage, handle, partitionId); + } + + public Result OpenDeviceOperator(out ReferenceCountedDisposable deviceOperator) + { + return GetBaseStorageService().OpenDeviceOperator(out deviceOperator); + } + + public Result OpenSdCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + return GetBaseStorageService().OpenSdCardDetectionEventNotifier(out eventNotifier); + } + + public Result OpenGameCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + return GetBaseStorageService().OpenGameCardDetectionEventNotifier(out eventNotifier); + } + + public Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent) + { + return GetBaseStorageService().SimulateDeviceDetectionEvent(port, mode, signalEvent); + } + + public Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + eventNotifier = null; + return rc; + } + + return ncaFsService.OpenSystemDataUpdateEventNotifier(out eventNotifier); + } + + public Result NotifySystemDataUpdateEvent() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.NotifySystemDataUpdateEvent(); + } + + public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + infoReader = default; + return rc; + } + + return saveFsService.OpenSaveDataInfoReader(out infoReader); + } + + public Result OpenSaveDataInfoReaderBySaveDataSpaceId( + out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + infoReader = default; + return rc; + } + + return saveFsService.OpenSaveDataInfoReaderBySaveDataSpaceId(out infoReader, spaceId); + } + + public Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + infoReader = default; + return rc; + } + + return saveFsService.OpenSaveDataInfoReaderWithFilter(out infoReader, spaceId, in filter); + } + + public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, + in SaveDataFilter filter) + { + Unsafe.SkipInit(out count); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.FindSaveDataWithFilter(out count, saveDataInfoBuffer, spaceId, in filter); + } + + public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return saveFsService.OpenSaveDataInternalStorageFileSystem(out fileSystem, spaceId, saveDataId); + } + + public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) + { + Unsafe.SkipInit(out size); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.QuerySaveDataInternalStorageTotalSize(out size, spaceId, saveDataId); + } + + public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + Unsafe.SkipInit(out commitId); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetSaveDataCommitId(out commitId, spaceId, saveDataId); + } + + public Result OpenSaveDataInfoReaderOnlyCacheStorage( + out ReferenceCountedDisposable infoReader) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + infoReader = default; + return rc; + } + + return saveFsService.OpenSaveDataInfoReaderOnlyCacheStorage(out infoReader); + } + + public Result OpenSaveDataMetaFile(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType type) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + file = default; + return rc; + } + + return saveFsService.OpenSaveDataMetaFile(out file, spaceId, in attribute, type); + } + + public Result DeleteCacheStorage(ushort index) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.DeleteCacheStorage(index); + } + + public Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index) + { + Unsafe.SkipInit(out dataSize); + Unsafe.SkipInit(out journalSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.GetCacheStorageSize(out dataSize, out journalSize, index); + } + + public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, + int startIndex, int bufferIdCount) + { + Unsafe.SkipInit(out readCount); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.ListAccessibleSaveDataOwnerId(out readCount, idBuffer, programId, startIndex, + bufferIdCount); + } + + public Result OpenSaveDataMover(out ReferenceCountedDisposable saveMover, + SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, + ulong workBufferSize) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) + { + saveMover = default; + return rc; + } + + return saveFsService.OpenSaveDataMover(out saveMover, sourceSpaceId, destinationSpaceId, workBufferHandle, + workBufferSize); + } + + public Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize) + { + return Result.Success; + } + + public Result SetSaveDataRootPath(in FspPath path) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.SetSaveDataRootPath(in path); + } + + public Result UnsetSaveDataRootPath() + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.UnsetSaveDataRootPath(); + } + + public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, ContentStorageId storageId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = null; + return rc; + } + + return ncaFsService.OpenContentStorageFileSystem(out fileSystem, storageId); + } + + public Result OpenCloudBackupWorkStorageFileSystem(out ReferenceCountedDisposable fileSystem, + CloudBackupWorkStorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable fileSystem, CustomStorageId storageId) + { + fileSystem = default; + var storageFlag = StorageType.NonGameCard; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + AccessibilityType accessType = storageId > CustomStorageId.SdCard + ? AccessibilityType.NotMount + : AccessibilityType.MountCustomStorage; + + Accessibility accessibility = programInfo.AccessControl.GetAccessibilityFor(accessType); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable customFs = null; + try + { + rc = FsProxyCore.OpenCustomStorageFileSystem(out customFs, storageId); + if (rc.IsFailure()) return rc; + + customFs = StorageLayoutTypeSetFileSystem.CreateShared(ref customFs, storageFlag); + customFs = AsynchronousAccessFileSystem.CreateShared(ref customFs); + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref customFs); + + return Result.Success; + } + finally + { + customFs?.Dispose(); + } + } + + public Result OpenGameCardFileSystem(out ReferenceCountedDisposable fileSystem, + GameCardHandle handle, GameCardPartition partitionId) + { + return GetBaseFileSystemService().OpenGameCardFileSystem(out fileSystem, handle, partitionId); + } + + public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + { + Unsafe.SkipInit(out totalSize); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.QuerySaveDataTotalSize(out totalSize, dataSize, journalSize); + } + + public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) + { + return GetTimeService().SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); + } + + public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) + { + Unsafe.SkipInit(out rightsId); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.GetRightsId(out rightsId, programId, storageId); + } + + public Result GetRightsIdByPath(out RightsId rightsId, in FspPath path) + { + return GetRightsIdAndKeyGenerationByPath(out rightsId, out _, in path); + } + + public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path) + { + Unsafe.SkipInit(out rightsId); + Unsafe.SkipInit(out keyGeneration); + + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.GetRightsIdAndKeyGenerationByPath(out rightsId, out keyGeneration, in path); + } + + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.RegisterExternalKey(in rightsId, in externalKey); + } + + public Result UnregisterExternalKey(in RightsId rightsId) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.UnregisterExternalKey(in rightsId); + } + + public Result UnregisterAllExternalKey() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.UnregisterAllExternalKey(); + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) + return ResultFs.PermissionDenied.Log(); + + rc = FsProxyCore.SetSdCardEncryptionSeed(in seed); + if (rc.IsFailure()) return rc; + + rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + rc = saveFsService.SetSdCardEncryptionSeed(in seed); + if (rc.IsFailure()) return rc; + + rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.SetSdCardEncryptionSeed(in seed); + } + + public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount) + { + return GetProgramIndexRegistryService() + .RegisterProgramIndexMapInfo(programIndexMapInfoBuffer, programCount); + } + + public Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path) + { + throw new NotImplementedException(); + } + + public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, + OutBuffer readBuffer) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.VerifySaveDataFileSystemBySaveDataSpaceId(spaceId, saveDataId, readBuffer); + } + + public Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer) + { + return VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId, readBuffer); + } + + public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, offset); + } + + public Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + // Corrupt both of the save data headers + Result rc = CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0); + if (rc.IsFailure()) return rc; + + return CorruptSaveDataFileSystemByOffset(spaceId, saveDataId, 0x4000); + } + + public Result CorruptSaveDataFileSystem(ulong saveDataId) + { + return CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId.System, saveDataId); + } + + public Result CreatePaddingFile(long size) + { + return GetBaseFileSystemService().CreatePaddingFile(size); + } + + public Result DeleteAllPaddingFiles() + { + return GetBaseFileSystemService().DeleteAllPaddingFiles(); + } + + public Result DisableAutoSaveDataCreation() + { + return Result.Success; + } + + public Result SetGlobalAccessLogMode(GlobalAccessLogMode mode) + { + return GetAccessLogService().SetAccessLogMode(mode); + + } + + public Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode) + { + return GetAccessLogService().GetAccessLogMode(out mode); + } + + public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) + { + return GetProgramIndexRegistryService().GetProgramIndex(out programIndex, out programCount); + } + + public Result OutputAccessLogToSdCard(InBuffer textBuffer) + { + return GetAccessLogService().OutputAccessLogToSdCard(textBuffer); + } + + public Result OutputMultiProgramTagAccessLog() + { + return GetAccessLogService().OutputMultiProgramTagAccessLog(); + } + + public Result RegisterUpdatePartition() + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) return rc; + + return ncaFsService.RegisterUpdatePartition(); + } + + public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem) + { + Result rc = GetNcaFileSystemService(out NcaFileSystemService ncaFsService); + if (rc.IsFailure()) + { + fileSystem = default; + return rc; + } + + return ncaFsService.OpenRegisteredUpdatePartition(out fileSystem); + } + + public Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key) + { + throw new NotImplementedException(); + } + + public Result SetSdCardAccessibility(bool isAccessible) + { + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.SetSdCardAccessibility(isAccessible); + } + + public Result IsSdCardAccessible(out bool isAccessible) + { + Unsafe.SkipInit(out isAccessible); + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.IsSdCardAccessible(out isAccessible); + } + + public Result OpenMultiCommitManager(out ReferenceCountedDisposable commitManager) + { + commitManager = null; + + Result rc = GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService); + if (rc.IsFailure()) return rc; + + return saveFsService.OpenMultiCommitManager(out commitManager); + } + + public Result OpenBisWiper(out ReferenceCountedDisposable bisWiper, NativeHandle transferMemoryHandle, + ulong transferMemorySize) + { + return GetBaseFileSystemService().OpenBisWiper(out bisWiper, transferMemoryHandle, transferMemorySize); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return FsProxyCore.ProgramRegistry.GetProgramInfo(out programInfo, CurrentProcess); + } + + private Result GetNcaFileSystemService(out NcaFileSystemService ncaFsService) + { + if (NcaFsService is null) + { + ncaFsService = null; + return ResultFs.PreconditionViolation.Log(); + } + + ncaFsService = NcaFsService.Target; + return Result.Success; + } + + private Result GetSaveDataFileSystemService(out SaveDataFileSystemService saveFsService) + { + if (SaveFsService is null) + { + saveFsService = null; + return ResultFs.PreconditionViolation.Log(); + } + + saveFsService = SaveFsService.Target; + return Result.Success; + } + + private BaseStorageService GetBaseStorageService() + { + return new BaseStorageService(FsProxyCore.Config.BaseStorageService, CurrentProcess); + } + + private BaseFileSystemService GetBaseFileSystemService() + { + return new BaseFileSystemService(FsProxyCore.Config.BaseFileSystemService, CurrentProcess); + } + + private TimeService GetTimeService() + { + return new TimeService(FsProxyCore.Config.TimeService, CurrentProcess); + } + + private ProgramIndexRegistryService GetProgramIndexRegistryService() + { + return new ProgramIndexRegistryService(FsProxyCore.Config.ProgramRegistryService, CurrentProcess); + } + + private AccessLogService GetAccessLogService() + { + return new AccessLogService(FsProxyCore.Config.AccessLogService, CurrentProcess); + } + } +} diff --git a/src/LibHac/FsSrv/FileSystemServer.cs b/src/LibHac/FsSrv/FileSystemServer.cs index cebda0a8..3ac30b7d 100644 --- a/src/LibHac/FsSrv/FileSystemServer.cs +++ b/src/LibHac/FsSrv/FileSystemServer.cs @@ -3,6 +3,8 @@ using LibHac.Fs; using LibHac.Fs.Impl; using LibHac.Fs.Shim; using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; using LibHac.Sm; namespace LibHac.FsSrv @@ -11,7 +13,10 @@ namespace LibHac.FsSrv { internal const ulong SaveIndexerId = 0x8000000000000000; - private FileSystemProxyCore FsProxyCore { get; } + private const ulong SpeedEmulationProgramIdMinimum = 0x100000000000000; + private const ulong SpeedEmulationProgramIdMaximum = 0x100000000001FFF; + + private FileSystemProxyCoreImpl FsProxyCore { get; } /// The client instance to be used for internal operations like save indexer access. public HorizonClient Hos { get; } @@ -36,23 +41,23 @@ namespace LibHac.FsSrv IsDebugMode = false; - ExternalKeySet externalKeySet = config.ExternalKeySet ?? new ExternalKeySet(); Timer = config.TimeSpanGenerator ?? new StopWatchTimeSpanGenerator(); - var fspConfig = new FileSystemProxyConfiguration - { - FsCreatorInterfaces = config.FsCreators, - ProgramRegistryServiceImpl = new ProgramRegistryServiceImpl(this) - }; + FileSystemProxyConfiguration fspConfig = InitializeFileSystemProxyConfiguration(config); - FsProxyCore = new FileSystemProxyCore(fspConfig, externalKeySet, config.DeviceOperator); + FsProxyCore = new FileSystemProxyCoreImpl(fspConfig); - FsProxyCore.SetSaveDataIndexerManager(new SaveDataIndexerManager(Hos.Fs, SaveIndexerId, - new ArrayPoolMemoryResource(), new SdHandleManager(), false)); + FileSystemProxyImpl fsProxy = GetFileSystemProxyServiceObject(); + ulong processId = Hos.Os.GetCurrentProcessId().Value; + fsProxy.SetCurrentProcess(processId).IgnoreResult(); - FileSystemProxy fsProxy = GetFileSystemProxyServiceObject(); - fsProxy.SetCurrentProcess(Hos.Os.GetCurrentProcessId().Value).IgnoreResult(); - fsProxy.CleanUpTemporaryStorage().IgnoreResult(); + var saveService = new SaveDataFileSystemService(fspConfig.SaveDataFileSystemService, processId); + + saveService.CleanUpTemporaryStorage().IgnoreResult(); + saveService.CleanUpSaveData().IgnoreResult(); + saveService.CompleteSaveDataExtension().IgnoreResult(); + saveService.FixSaveData().IgnoreResult(); + saveService.RecoverMultiCommit().IgnoreResult(); Hos.Sm.RegisterService(new FileSystemProxyService(this), "fsp-srv").IgnoreResult(); Hos.Sm.RegisterService(new FileSystemProxyForLoaderService(this), "fsp-ldr").IgnoreResult(); @@ -81,19 +86,100 @@ namespace LibHac.FsSrv return new FileSystemClient(Hos); } - private FileSystemProxy GetFileSystemProxyServiceObject() + private FileSystemProxyConfiguration InitializeFileSystemProxyConfiguration(FileSystemServerConfig config) { - return new FileSystemProxy(Hos, FsProxyCore); + var saveDataIndexerManager = new SaveDataIndexerManager(Hos.Fs, SaveIndexerId, + new ArrayPoolMemoryResource(), new SdHandleManager(), false); + + var programRegistryService = new ProgramRegistryServiceImpl(this); + var programRegistry = new ProgramRegistryImpl(programRegistryService); + + var baseStorageConfig = new BaseStorageServiceImpl.Configuration(); + baseStorageConfig.BisStorageCreator = config.FsCreators.BuiltInStorageCreator; + baseStorageConfig.GameCardStorageCreator = config.FsCreators.GameCardStorageCreator; + baseStorageConfig.ProgramRegistry = programRegistry; + baseStorageConfig.DeviceOperator = new ReferenceCountedDisposable(config.DeviceOperator); + var baseStorageService = new BaseStorageServiceImpl(in baseStorageConfig); + + var timeServiceConfig = new TimeServiceImpl.Configuration(); + timeServiceConfig.HorizonClient = Hos; + timeServiceConfig.ProgramRegistry = programRegistry; + var timeService = new TimeServiceImpl(in timeServiceConfig); + + var baseFsServiceConfig = new BaseFileSystemServiceImpl.Configuration(); + baseFsServiceConfig.BisFileSystemCreator = config.FsCreators.BuiltInStorageFileSystemCreator; + baseFsServiceConfig.GameCardFileSystemCreator = config.FsCreators.GameCardFileSystemCreator; + baseFsServiceConfig.SdCardFileSystemCreator = config.FsCreators.SdCardFileSystemCreator; + baseFsServiceConfig.BisWiperCreator = BisWiper.CreateWiper; + baseFsServiceConfig.ProgramRegistry = programRegistry; + var baseFsService = new BaseFileSystemServiceImpl(in baseFsServiceConfig); + + var ncaFsServiceConfig = new NcaFileSystemServiceImpl.Configuration(); + ncaFsServiceConfig.BaseFsService = baseFsService; + ncaFsServiceConfig.HostFsCreator = config.FsCreators.HostFileSystemCreator; + ncaFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; + ncaFsServiceConfig.PartitionFsCreator = config.FsCreators.PartitionFileSystemCreator; + ncaFsServiceConfig.RomFsCreator = config.FsCreators.RomFileSystemCreator; + ncaFsServiceConfig.StorageOnNcaCreator = config.FsCreators.StorageOnNcaCreator; + ncaFsServiceConfig.SubDirectoryFsCreator = config.FsCreators.SubDirectoryFileSystemCreator; + ncaFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; + ncaFsServiceConfig.ProgramRegistryService = programRegistryService; + ncaFsServiceConfig.HorizonClient = Hos; + ncaFsServiceConfig.ProgramRegistry = programRegistry; + ncaFsServiceConfig.SpeedEmulationRange = + new InternalProgramIdRangeForSpeedEmulation(SpeedEmulationProgramIdMinimum, + SpeedEmulationProgramIdMaximum); + + var ncaFsService = new NcaFileSystemServiceImpl(in ncaFsServiceConfig, config.ExternalKeySet); + + var saveFsServiceConfig = new SaveDataFileSystemServiceImpl.Configuration(); + saveFsServiceConfig.BaseFsService = baseFsService; + saveFsServiceConfig.HostFsCreator = config.FsCreators.HostFileSystemCreator; + saveFsServiceConfig.TargetManagerFsCreator = config.FsCreators.TargetManagerFileSystemCreator; + saveFsServiceConfig.SaveFsCreator = config.FsCreators.SaveDataFileSystemCreator; + saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; + saveFsServiceConfig.ProgramRegistryService = programRegistryService; + saveFsServiceConfig.ShouldCreateDirectorySaveData = () => true; + saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; + saveFsServiceConfig.HorizonClient = Hos; + saveFsServiceConfig.ProgramRegistry = programRegistry; + + var saveFsService = new SaveDataFileSystemServiceImpl(in saveFsServiceConfig); + + var accessLogServiceConfig = new AccessLogServiceImpl.Configuration(); + accessLogServiceConfig.MinimumProgramIdForSdCardLog = 0x0100000000003000; + accessLogServiceConfig.HorizonClient = Hos; + accessLogServiceConfig.ProgramRegistry = programRegistry; + var accessLogService = new AccessLogServiceImpl(in accessLogServiceConfig); + + var fspConfig = new FileSystemProxyConfiguration + { + FsCreatorInterfaces = config.FsCreators, + BaseStorageService = baseStorageService, + BaseFileSystemService = baseFsService, + NcaFileSystemService = ncaFsService, + SaveDataFileSystemService = saveFsService, + TimeService = timeService, + ProgramRegistryService = programRegistryService, + AccessLogService = accessLogService + }; + + return fspConfig; } - private FileSystemProxy GetFileSystemProxyForLoaderServiceObject() + private FileSystemProxyImpl GetFileSystemProxyServiceObject() { - return new FileSystemProxy(Hos, FsProxyCore); + return new FileSystemProxyImpl(FsProxyCore); + } + + private FileSystemProxyImpl GetFileSystemProxyForLoaderServiceObject() + { + return new FileSystemProxyImpl(FsProxyCore); } private ProgramRegistryImpl GetProgramRegistryServiceObject() { - return new ProgramRegistryImpl(FsProxyCore.Config.ProgramRegistryServiceImpl); + return new ProgramRegistryImpl(FsProxyCore.Config.ProgramRegistryService); } private class FileSystemProxyService : IServiceObject diff --git a/src/LibHac/FsSrv/IFileSystemProxy.cs b/src/LibHac/FsSrv/IFileSystemProxy.cs index fe466db5..d05035b8 100644 --- a/src/LibHac/FsSrv/IFileSystemProxy.cs +++ b/src/LibHac/FsSrv/IFileSystemProxy.cs @@ -1,107 +1,119 @@ -using System; -using LibHac.Common; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.FsSystem; +using LibHac.Fs; +using LibHac.FsSrv.Sf; using LibHac.Ncm; +using LibHac.Sf; using LibHac.Spl; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; namespace LibHac.FsSrv { public interface IFileSystemProxy { Result SetCurrentProcess(ulong processId); - Result OpenDataFileSystemByCurrentProcess(out IFileSystem fileSystem); - Result OpenFileSystemWithPatch(out IFileSystem fileSystem, ProgramId programId, FileSystemProxyType type); - Result OpenFileSystemWithId(out IFileSystem fileSystem, ref FsPath path, ulong id, FileSystemProxyType type); - Result OpenDataFileSystemByProgramId(out IFileSystem fileSystem, ProgramId programId); - Result OpenBisFileSystem(out IFileSystem fileSystem, ref FsPath rootPath, BisPartitionId partitionId); - Result OpenBisStorage(out IStorage storage, BisPartitionId partitionId); + Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable fileSystem); + Result OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, ProgramId programId, FileSystemProxyType fsType); + Result OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in FspPath path, ulong id, FileSystemProxyType fsType); + Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable fileSystem, ProgramId programId); + Result OpenBisFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath rootPath, BisPartitionId partitionId); + Result OpenBisStorage(out ReferenceCountedDisposable storage, BisPartitionId partitionId); Result InvalidateBisCache(); - Result OpenHostFileSystemWithOption(out IFileSystem fileSystem, ref FsPath path, MountHostOption option); - Result OpenHostFileSystem(out IFileSystem fileSystem, ref FsPath path); - Result OpenSdCardFileSystem(out IFileSystem fileSystem); + Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, in FspPath path); + Result OpenSdCardFileSystem(out ReferenceCountedDisposable fileSystem); Result FormatSdCardFileSystem(); Result DeleteSaveDataFileSystem(ulong saveDataId); - Result CreateSaveDataFileSystem(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, ref SaveMetaCreateInfo metaCreateInfo); - Result CreateSaveDataFileSystemBySystemSaveDataId(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo); - Result RegisterSaveDataFileSystemAtomicDeletion(ReadOnlySpan saveDataIds); + Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo); + Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo); + Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds); Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); Result FormatSdCardDryRun(); Result IsExFatSupported(out bool isSupported); - Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, ref SaveDataAttribute attribute); - Result OpenGameCardStorage(out IStorage storage, GameCardHandle handle, GameCardPartitionRaw partitionId); - Result OpenGameCardFileSystem(out IFileSystem fileSystem, GameCardHandle handle, GameCardPartition partitionId); + Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenGameCardStorage(out ReferenceCountedDisposable storage, GameCardHandle handle, GameCardPartitionRaw partitionId); + Result OpenGameCardFileSystem(out ReferenceCountedDisposable fileSystem, GameCardHandle handle, GameCardPartition partitionId); Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); - Result DeleteCacheStorage(short index); - Result GetCacheStorageSize(out long dataSize, out long journalSize, short index); - Result CreateSaveDataFileSystemWithHashSalt(ref SaveDataAttribute attribute, ref SaveDataCreationInfo creationInfo, ref SaveMetaCreateInfo metaCreateInfo, ref HashSalt hashSalt); - Result OpenSaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute); - Result OpenSaveDataFileSystemBySystemSaveDataId(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute); - Result OpenReadOnlySaveDataFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute); - Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(Span extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId); - Result ReadSaveDataFileSystemExtraData(Span extraDataBuffer, ulong saveDataId); - Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer); + Result DeleteCacheStorage(ushort index); + Result GetCacheStorageSize(out long dataSize, out long journalSize, ushort index); + Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt); + Result OpenHostFileSystemWithOption(out ReferenceCountedDisposable fileSystem, in FspPath path, MountHostOption option); + Result OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenSaveDataFileSystemBySystemSaveDataId(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result OpenReadOnlySaveDataFileSystem(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, ulong saveDataId); + Result ReadSaveDataFileSystemExtraData(OutBuffer extraDataBuffer, ulong saveDataId); + Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer); Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader); Result OpenSaveDataInfoReaderBySaveDataSpaceId(out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId); Result OpenSaveDataInfoReaderOnlyCacheStorage(out ReferenceCountedDisposable infoReader); - Result OpenSaveDataInternalStorageFileSystem(out IFileSystem fileSystem, SaveDataSpaceId spaceId, ulong saveDataId); + Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId); Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId); - Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer, ReadOnlySpan maskBuffer); - Result FindSaveDataWithFilter(out long count, Span saveDataInfoBuffer, SaveDataSpaceId spaceId, ref SaveDataFilter filter); - Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId, ref SaveDataFilter filter); - Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(Span extraDataBuffer, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute); - Result WriteSaveDataFileSystemExtraDataBySaveDataAttribute(ref SaveDataAttribute attribute, SaveDataSpaceId spaceId, ReadOnlySpan extraDataBuffer, ReadOnlySpan maskBuffer); - Result OpenSaveDataMetaFile(out IFile file, SaveDataSpaceId spaceId, ref SaveDataAttribute attribute, SaveDataMetaType type); + Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); + Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, in SaveDataFilter filter); + Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId, in SaveDataFilter filter); + Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, SaveDataSpaceId spaceId, InBuffer extraDataBuffer, InBuffer maskBuffer); + Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraDataBuffer, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer maskBuffer); + Result OpenSaveDataMetaFile(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType type); - Result ListAccessibleSaveDataOwnerId(out int readCount, Span idBuffer, ProgramId programId, int startIndex, int bufferIdCount); - Result OpenImageDirectoryFileSystem(out IFileSystem fileSystem, ImageDirectoryId dirId); - Result OpenContentStorageFileSystem(out IFileSystem fileSystem, ContentStorageId storageId); - Result OpenCloudBackupWorkStorageFileSystem(out IFileSystem fileSystem, CloudBackupWorkStorageId storageId); - Result OpenCustomStorageFileSystem(out IFileSystem fileSystem, CustomStorageId storageId); - Result OpenDataStorageByCurrentProcess(out IStorage storage); - Result OpenDataStorageByProgramId(out IStorage storage, ProgramId programId); - Result OpenDataStorageByDataId(out IStorage storage, DataId dataId, StorageId storageId); - Result OpenPatchDataStorageByCurrentProcess(out IStorage storage); - Result OpenDataFileSystemWithProgramIndex(out IFileSystem fileSystem, byte programIndex); - Result OpenDataStorageWithProgramIndex(out IStorage storage, byte programIndex); - Result OpenDeviceOperator(out IDeviceOperator deviceOperator); + Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, int startIndex, int bufferIdCount); + Result OpenSaveDataMover(out ReferenceCountedDisposable saveMover, SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, ulong workBufferSize); + Result OpenImageDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, ImageDirectoryId directoryId); + Result OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, ContentStorageId storageId); + Result OpenCloudBackupWorkStorageFileSystem(out ReferenceCountedDisposable fileSystem, CloudBackupWorkStorageId storageId); + Result OpenCustomStorageFileSystem(out ReferenceCountedDisposable fileSystem, CustomStorageId storageId); + Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable storage); + Result OpenDataStorageByProgramId(out ReferenceCountedDisposable storage, ProgramId programId); + Result OpenDataStorageByDataId(out ReferenceCountedDisposable storage, DataId dataId, StorageId storageId); + Result OpenPatchDataStorageByCurrentProcess(out ReferenceCountedDisposable storage); + Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable fileSystem, byte programIndex); + Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable storage, byte programIndex); + Result OpenDeviceOperator(out ReferenceCountedDisposable deviceOperator); + + Result OpenSdCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier); + Result OpenGameCardDetectionEventNotifier(out ReferenceCountedDisposable eventNotifier); + Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable eventNotifier); + Result NotifySystemDataUpdateEvent(); + Result SimulateDeviceDetectionEvent(SdmmcPort port, SimulatingDeviceDetectionMode mode, bool signalEvent); Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); - Result VerifySaveDataFileSystem(ulong saveDataId, Span readBuffer); + Result VerifySaveDataFileSystem(ulong saveDataId, OutBuffer readBuffer); Result CorruptSaveDataFileSystem(ulong saveDataId); Result CreatePaddingFile(long size); Result DeleteAllPaddingFiles(); Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId); - Result RegisterExternalKey(ref RightsId rightsId, ref AccessKey externalKey); + Result RegisterExternalKey(in RightsId rightsId, in AccessKey externalKey); Result UnregisterAllExternalKey(); - Result GetRightsIdByPath(out RightsId rightsId, ref FsPath path); - Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, ref FsPath path); - Result SetCurrentPosixTimeWithTimeDifference(long time, int difference); + Result GetRightsIdByPath(out RightsId rightsId, in FspPath path); + Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path); + Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference); Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); - Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, Span readBuffer); + Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, OutBuffer readBuffer); Result CorruptSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId); Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId); - Result UnregisterExternalKey(ref RightsId rightsId); - Result SetSdCardEncryptionSeed(ref EncryptionSeed seed); + Result UnregisterExternalKey(in RightsId rightsId); + Result SetSdCardEncryptionSeed(in EncryptionSeed seed); Result SetSdCardAccessibility(bool isAccessible); Result IsSdCardAccessible(out bool isAccessible); - Result RegisterProgramIndexMapInfo(ReadOnlySpan programIndexMapInfoBuffer, int programCount); - Result SetBisRootForHost(BisPartitionId partitionId, ref FsPath path); + Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfoBuffer, int programCount); + Result SetBisRootForHost(BisPartitionId partitionId, in FspPath path); Result SetSaveDataSize(long saveDataSize, long saveDataJournalSize); - Result SetSaveDataRootPath(ref FsPath path); + Result SetSaveDataRootPath(in FspPath path); Result DisableAutoSaveDataCreation(); Result SetGlobalAccessLogMode(GlobalAccessLogMode mode); Result GetGlobalAccessLogMode(out GlobalAccessLogMode mode); - Result OutputAccessLogToSdCard(U8Span logString); + Result OutputAccessLogToSdCard(InBuffer textBuffer); Result RegisterUpdatePartition(); - Result OpenRegisteredUpdatePartition(out IFileSystem fileSystem); + Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem); Result GetProgramIndexForAccessLog(out int programIndex, out int programCount); - Result OverrideSaveDataTransferTokenSignVerificationKey(ReadOnlySpan key); + Result UnsetSaveDataRootPath(); + Result OutputMultiProgramTagAccessLog(); + Result OverrideSaveDataTransferTokenSignVerificationKey(InBuffer key); Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset); - Result OpenMultiCommitManager(out IMultiCommitManager commitManager); + Result OpenMultiCommitManager(out ReferenceCountedDisposable commitManager); + Result OpenBisWiper(out ReferenceCountedDisposable bisWiper, NativeHandle transferMemoryHandle, ulong transferMemorySize); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/IMultiCommitManager.cs b/src/LibHac/FsSrv/IMultiCommitManager.cs deleted file mode 100644 index 2730e066..00000000 --- a/src/LibHac/FsSrv/IMultiCommitManager.cs +++ /dev/null @@ -1,10 +0,0 @@ -using LibHac.Fs.Fsa; - -namespace LibHac.FsSrv -{ - public interface IMultiCommitManager - { - Result Add(IFileSystem fileSystem); - Result Commit(); - } -} diff --git a/src/LibHac/FsSrv/ISaveDataIndexer.cs b/src/LibHac/FsSrv/ISaveDataIndexer.cs index e8aebabe..aa5b819e 100644 --- a/src/LibHac/FsSrv/ISaveDataIndexer.cs +++ b/src/LibHac/FsSrv/ISaveDataIndexer.cs @@ -139,10 +139,10 @@ namespace LibHac.FsSrv int GetIndexCount(); /// - /// Returns an that iterates through the . + /// Returns an that iterates through the . /// - /// If the method returns successfully, contains the created . + /// If the method returns successfully, contains the created . /// The of the operation. - Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader); + Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs index aaca8502..45652278 100644 --- a/src/LibHac/FsSrv/ISaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/ISaveDataIndexerManager.cs @@ -4,8 +4,8 @@ namespace LibHac.FsSrv { public interface ISaveDataIndexerManager { - Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId); - void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId); - void InvalidateSdCardIndexer(SaveDataSpaceId spaceId); + Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId); + void ResetIndexer(SaveDataSpaceId spaceId); + void InvalidateIndexer(SaveDataSpaceId spaceId); } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/AccessControl.cs b/src/LibHac/FsSrv/Impl/AccessControl.cs index 67f5bb00..1122a49d 100644 --- a/src/LibHac/FsSrv/Impl/AccessControl.cs +++ b/src/LibHac/FsSrv/Impl/AccessControl.cs @@ -830,6 +830,7 @@ namespace LibHac.FsSrv.Impl RegisterProgramIndexMapInfo, ChallengeCardExistence, CreateOwnSaveData, + DeleteOwnSaveData, ReadOwnSaveDataFileSystemExtraData, ExtendOwnSaveData, OpenOwnSaveDataTransferProhibiter, diff --git a/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs new file mode 100644 index 00000000..98283ad9 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/AsynchronousAccessFileSystem.cs @@ -0,0 +1,27 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; + +namespace LibHac.FsSrv.Impl +{ + public class AsynchronousAccessFileSystem : ForwardingFileSystem + { + protected AsynchronousAccessFileSystem(ref ReferenceCountedDisposable baseFileSystem) : base( + ref baseFileSystem) + { } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable fileSystem) + { + return new ReferenceCountedDisposable(new AsynchronousAccessFileSystem(ref fileSystem)); + } + + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(out file, path, mode); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/BisWiper.cs b/src/LibHac/FsSrv/Impl/BisWiper.cs new file mode 100644 index 00000000..fe04a44f --- /dev/null +++ b/src/LibHac/FsSrv/Impl/BisWiper.cs @@ -0,0 +1,33 @@ +using System; +using LibHac.FsSrv.Sf; +using LibHac.Sf; + +namespace LibHac.FsSrv.Impl +{ + internal class BisWiper : IWiper + { + // ReSharper disable UnusedParameter.Local + public BisWiper(NativeHandle memoryHandle, ulong memorySize) { } + // ReSharper restore UnusedParameter.Local + + public Result Startup(out long spaceToWipe) + { + throw new NotImplementedException(); + } + + public Result Process(out long remainingSpaceToWipe) + { + throw new NotImplementedException(); + } + + public static Result CreateWiper(out IWiper wiper, NativeHandle memoryHandle, ulong memorySize) + { + wiper = new BisWiper(memoryHandle, memorySize); + return Result.Success; + } + + public void Dispose() + { + } + } +} diff --git a/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs new file mode 100644 index 00000000..70193623 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/DeepRetryFileSystem.cs @@ -0,0 +1,58 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; + +namespace LibHac.FsSrv.Impl +{ + public class DeepRetryFileSystem : ForwardingFileSystem + { + private ReferenceCountedDisposable.WeakReference SelfReference { get; set; } + private ReferenceCountedDisposable AccessFailureManager { get; set; } + + protected DeepRetryFileSystem(ref ReferenceCountedDisposable baseFileSystem, + ref ReferenceCountedDisposable accessFailureManager) : base( + ref baseFileSystem) + { + AccessFailureManager = Shared.Move(ref accessFailureManager); + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable fileSystem, + ref ReferenceCountedDisposable accessFailureManager) + { + ReferenceCountedDisposable sharedRetryFileSystem = null; + try + { + var retryFileSystem = new DeepRetryFileSystem(ref fileSystem, ref accessFailureManager); + sharedRetryFileSystem = new ReferenceCountedDisposable(retryFileSystem); + + retryFileSystem.SelfReference = + new ReferenceCountedDisposable.WeakReference(sharedRetryFileSystem); + + return sharedRetryFileSystem.AddReference(); + } + finally + { + sharedRetryFileSystem?.Dispose(); + } + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + AccessFailureManager?.Dispose(); + } + + base.Dispose(disposing); + } + + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(out file, path, mode); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs new file mode 100644 index 00000000..8f17291e --- /dev/null +++ b/src/LibHac/FsSrv/Impl/DirectoryInterfaceAdapter.cs @@ -0,0 +1,65 @@ +using System; +using System.Runtime.InteropServices; +using LibHac.Fs; +using LibHac.Sf; +using IDirectory = LibHac.Fs.Fsa.IDirectory; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; + +namespace LibHac.FsSrv.Impl +{ + internal class DirectoryInterfaceAdapter : IDirectorySf + { + private ReferenceCountedDisposable ParentFs { get; } + private IDirectory BaseDirectory { get; } + + public DirectoryInterfaceAdapter(IDirectory baseDirectory, + ref ReferenceCountedDisposable parentFileSystem) + { + BaseDirectory = baseDirectory; + ParentFs = parentFileSystem; + parentFileSystem = null; + } + + public Result Read(out long entriesRead, OutBuffer entryBuffer) + { + const int maxTryCount = 2; + entriesRead = default; + + Span entries = MemoryMarshal.Cast(entryBuffer.Buffer); + + Result rc = Result.Success; + long tmpEntriesRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseDirectory.Read(out tmpEntriesRead, entries); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + entriesRead = tmpEntriesRead; + return Result.Success; + } + + public Result GetEntryCount(out long entryCount) + { + entryCount = default; + + Result rc = BaseDirectory.GetEntryCount(out long tmpEntryCount); + if (rc.IsFailure()) return rc; + + entryCount = tmpEntryCount; + return Result.Success; + } + + public void Dispose() + { + BaseDirectory?.Dispose(); + ParentFs?.Dispose(); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs new file mode 100644 index 00000000..56a574f5 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/FileInterfaceAdapter.cs @@ -0,0 +1,131 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using IFile = LibHac.Fs.Fsa.IFile; +using IFileSf = LibHac.FsSrv.Sf.IFile; + +namespace LibHac.FsSrv.Impl +{ + internal class FileInterfaceAdapter : IFileSf + { + private ReferenceCountedDisposable ParentFs { get; } + private IFile BaseFile { get; } + + public FileInterfaceAdapter(IFile baseFile, + ref ReferenceCountedDisposable parentFileSystem) + { + BaseFile = baseFile; + ParentFs = parentFileSystem; + parentFileSystem = null; + } + + public Result Read(out long bytesRead, long offset, Span destination, ReadOption option) + { + const int maxTryCount = 2; + bytesRead = default; + + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (destination.Length < 0) + return ResultFs.InvalidSize.Log(); + + Result rc = Result.Success; + long tmpBytesRead = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseFile.Read(out tmpBytesRead, offset, destination, option); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + bytesRead = tmpBytesRead; + return Result.Success; + } + + public Result Write(long offset, ReadOnlySpan source, WriteOption option) + { + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (source.Length < 0) + return ResultFs.InvalidSize.Log(); + + // Note: Thread priority is temporarily when writing in FS + + return BaseFile.Write(offset, source, option); + } + + public Result Flush() + { + return BaseFile.Flush(); + } + + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + return BaseFile.SetSize(size); + } + + public Result GetSize(out long size) + { + const int maxTryCount = 2; + size = default; + + Result rc = Result.Success; + long tmpSize = 0; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseFile.GetSize(out tmpSize); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + size = tmpSize; + return Result.Success; + } + + public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + rangeInfo = new QueryRangeInfo(); + + if (operationId == (int)OperationId.InvalidateCache) + { + Result rc = BaseFile.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + } + else if (operationId == (int)OperationId.QueryRange) + { + Unsafe.SkipInit(out QueryRangeInfo info); + + Result rc = BaseFile.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, offset, size, + ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + + rangeInfo.Merge(in info); + } + + return Result.Success; + } + + public void Dispose() + { + BaseFile?.Dispose(); + ParentFs?.Dispose(); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs new file mode 100644 index 00000000..7b128643 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/FileSystemInterfaceAdapter.cs @@ -0,0 +1,292 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.Sf; +using LibHac.Util; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; + +namespace LibHac.FsSrv.Impl +{ + internal class FileSystemInterfaceAdapter : IFileSystemSf + { + private ReferenceCountedDisposable BaseFileSystem { get; } + private bool IsHostFsRoot { get; } + + // In FS, FileSystemInterfaceAdapter is derived from ISharedObject, so that's used for ref-counting when + // creating files and directories. We don't have an ISharedObject, so a self-reference is used instead. + private ReferenceCountedDisposable.WeakReference _selfReference; + + /// + /// Initializes a new by creating + /// a new reference to . + /// + /// The base file system. + /// Does the base file system come from the root directory of a host file system? + private FileSystemInterfaceAdapter(ReferenceCountedDisposable fileSystem, + bool isHostFsRoot = false) + { + BaseFileSystem = fileSystem.AddReference(); + IsHostFsRoot = isHostFsRoot; + } + + /// + /// Initializes a new by moving the file system object. + /// Avoids allocations from incrementing and then decrementing the ref-count. + /// + /// The base file system. Will be null upon returning. + /// Does the base file system come from the root directory of a host file system? + private FileSystemInterfaceAdapter(ref ReferenceCountedDisposable fileSystem, + bool isHostFsRoot = false) + { + BaseFileSystem = fileSystem; + fileSystem = null; + IsHostFsRoot = isHostFsRoot; + } + + /// + /// Initializes a new , creating a copy of the input file system object. + /// + /// The base file system. + /// Does the base file system come from the root directory of a host file system? + public static ReferenceCountedDisposable CreateShared( + ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + { + var adapter = new FileSystemInterfaceAdapter(baseFileSystem, isHostFsRoot); + + return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); + } + + /// + /// Initializes a new cast to an + /// by moving the input file system object. Avoids allocations from incrementing and then decrementing the ref-count. + /// + /// The base file system. Will be null upon returning. + /// Does the base file system come from the root directory of a host file system? + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, bool isHostFsRoot = false) + { + var adapter = new FileSystemInterfaceAdapter(ref baseFileSystem, isHostFsRoot); + + return ReferenceCountedDisposable.Create(adapter, out adapter._selfReference); + } + + private static ReadOnlySpan RootDir => new[] { (byte)'/' }; + + public Result GetImpl(out ReferenceCountedDisposable fileSystem) + { + fileSystem = BaseFileSystem.AddReference(); + return Result.Success; + } + + public Result CreateFile(in Path path, long size, int option) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.CreateFile(normalizer.Path, size, (CreateFileOptions)option); + } + + public Result DeleteFile(in Path path) + { + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.DeleteFile(normalizer.Path); + } + + public Result CreateDirectory(in Path path) + { + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + if (StringUtils.Compare(RootDir, normalizer.Path) != 0) + return ResultFs.PathAlreadyExists.Log(); + + return BaseFileSystem.Target.CreateDirectory(normalizer.Path); + } + + public Result DeleteDirectory(in Path path) + { + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + if (StringUtils.Compare(RootDir, normalizer.Path) != 0) + return ResultFs.DirectoryNotDeletable.Log(); + + return BaseFileSystem.Target.DeleteDirectory(normalizer.Path); + } + + public Result DeleteDirectoryRecursively(in Path path) + { + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + if (StringUtils.Compare(RootDir, normalizer.Path) != 0) + return ResultFs.DirectoryNotDeletable.Log(); + + return BaseFileSystem.Target.DeleteDirectoryRecursively(normalizer.Path); + } + + public Result RenameFile(in Path oldPath, in Path newPath) + { + var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption()); + if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result; + + var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption()); + if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result; + + return BaseFileSystem.Target.RenameFile(new U8Span(normalizerOldPath.Path), + new U8Span(normalizerNewPath.Path)); + } + + public Result RenameDirectory(in Path oldPath, in Path newPath) + { + var normalizerOldPath = new PathNormalizer(new U8Span(oldPath.Str), GetPathNormalizerOption()); + if (normalizerOldPath.Result.IsFailure()) return normalizerOldPath.Result; + + var normalizerNewPath = new PathNormalizer(new U8Span(newPath.Str), GetPathNormalizerOption()); + if (normalizerNewPath.Result.IsFailure()) return normalizerNewPath.Result; + + if (PathTool.IsSubpath(normalizerOldPath.Path, normalizerNewPath.Path)) + return ResultFs.DirectoryNotRenamable.Log(); + + return BaseFileSystem.Target.RenameDirectory(normalizerOldPath.Path, normalizerNewPath.Path); + } + + public Result GetEntryType(out uint entryType, in Path path) + { + entryType = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + ref DirectoryEntryType type = ref Unsafe.As(ref entryType); + + return BaseFileSystem.Target.GetEntryType(out type, new U8Span(normalizer.Path)); + } + + public Result OpenFile(out ReferenceCountedDisposable file, in Path path, uint mode) + { + const int maxTryCount = 2; + file = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + Result rc = Result.Success; + Fs.Fsa.IFile fileInterface = null; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseFileSystem.Target.OpenFile(out fileInterface, new U8Span(normalizer.Path), (OpenMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable selfReference = _selfReference.TryAddReference(); + var adapter = new FileInterfaceAdapter(fileInterface, ref selfReference); + file = new ReferenceCountedDisposable(adapter); + + return Result.Success; + } + + public Result OpenDirectory(out ReferenceCountedDisposable directory, in Path path, uint mode) + { + const int maxTryCount = 2; + directory = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + Result rc = Result.Success; + Fs.Fsa.IDirectory dirInterface = null; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseFileSystem.Target.OpenDirectory(out dirInterface, new U8Span(normalizer.Path), (OpenDirectoryMode)mode); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable selfReference = _selfReference.TryAddReference(); + var adapter = new DirectoryInterfaceAdapter(dirInterface, ref selfReference); + directory = new ReferenceCountedDisposable(adapter); + + return Result.Success; + } + + public Result Commit() + { + return BaseFileSystem.Target.Commit(); + } + + public Result GetFreeSpaceSize(out long freeSpace, in Path path) + { + freeSpace = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, normalizer.Path); + } + + public Result GetTotalSpaceSize(out long totalSpace, in Path path) + { + totalSpace = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, normalizer.Path); + } + + public Result CleanDirectoryRecursively(in Path path) + { + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.CleanDirectoryRecursively(normalizer.Path); + } + + public Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) + { + timeStamp = default; + + var normalizer = new PathNormalizer(new U8Span(path.Str), GetPathNormalizerOption()); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, normalizer.Path); + } + + public Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path) + { + return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, (QueryId)queryId, new U8Span(path.Str)); + } + + public void Dispose() + { + BaseFileSystem?.Dispose(); + } + + private PathNormalizer.Option GetPathNormalizerOption() + { + return IsHostFsRoot ? PathNormalizer.Option.PreserveUnc : PathNormalizer.Option.None; + } + } +} diff --git a/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs b/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs new file mode 100644 index 00000000..c0701e6c --- /dev/null +++ b/src/LibHac/FsSrv/Impl/IEntryOpenCountSemaphoreManager.cs @@ -0,0 +1,10 @@ +using System; +using LibHac.FsSystem; + +namespace LibHac.FsSrv.Impl +{ + public interface IEntryOpenCountSemaphoreManager : IDisposable + { + Result TryAcquireEntryOpenCountSemaphore(out IUniqueLock semaphore); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs b/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs new file mode 100644 index 00000000..3e5e2963 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/IRomFileSystemAccessFailureManager.cs @@ -0,0 +1,18 @@ +using System; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.Ncm; + +namespace LibHac.FsSrv.Impl +{ + public interface IRomFileSystemAccessFailureManager : IDisposable + { + Result OpenDataStorageCore(out ReferenceCountedDisposable storage, out Hash ncaHeaderDigest, ulong id, + StorageId storageId); + + Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected); + void IncrementRomFsRemountForDataCorruptionCount(); + void IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); + void IncrementRomFsRecoveredByInvalidateCacheCount(); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs new file mode 100644 index 00000000..14869462 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/ISaveDataMultiCommitCoreInterface.cs @@ -0,0 +1,14 @@ +using System; +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSrv.Impl +{ + public interface ISaveDataMultiCommitCoreInterface : IDisposable + { + Result RecoverMultiCommit(); + Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo); + Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback); + Result OpenMultiCommitContext(out ReferenceCountedDisposable contextFileSystem); + } +} diff --git a/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs new file mode 100644 index 00000000..9a03947f --- /dev/null +++ b/src/LibHac/FsSrv/Impl/ISaveDataTransferCoreInterface.cs @@ -0,0 +1,30 @@ +using System; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.Util; +using IFileSf = LibHac.FsSrv.Sf.IFile; + +namespace LibHac.FsSrv.Impl +{ + public interface ISaveDataTransferCoreInterface : IDisposable + { + Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId); + Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize); + Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId); + Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized); + Result GetSaveDataInfo(out SaveDataInfo saveInfo, SaveDataSpaceId spaceId, in SaveDataAttribute attribute); + Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData); + Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp); + Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); + Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId); + Result OpenSaveDataFile(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, SaveDataMetaType metaType); + Result OpenSaveDataMetaFileRaw(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode); + Result OpenSaveDataInternalStorageFileSystemCore(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey); + Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, long journalSize); + Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId); + Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2); + Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state); + Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank); + Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId spaceId); + } +} diff --git a/src/LibHac/FsSrv/Impl/LocationResolverSet.cs b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs new file mode 100644 index 00000000..545172d0 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/LocationResolverSet.cs @@ -0,0 +1,222 @@ +using System; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Lr; +using LibHac.Ncm; + +namespace LibHac.FsSrv.Impl +{ + internal class LocationResolverSet : IDisposable + { + private const int LocationResolverCount = 5; + + private LocationResolver[] _resolvers; + private AddOnContentLocationResolver _aocResolver; + private object _locker; + + private HorizonClient _hos; + + public LocationResolverSet(HorizonClient horizonClient) + { + _resolvers = new LocationResolver[LocationResolverCount]; + _locker = new object(); + _hos = horizonClient; + } + + private Result GetLocationResolver(out LocationResolver resolver, StorageId storageId) + { + resolver = default; + + if (!IsValidStorageId(storageId)) + return ResultLr.LocationResolverNotFound.Log(); + + lock (_locker) + { + int index = GetResolverIndexFromStorageId(storageId); + ref LocationResolver lr = ref _resolvers[index]; + + // Open the location resolver if it hasn't been already + if (lr is null && _hos.Lr.OpenLocationResolver(out lr, storageId).IsFailure()) + return ResultLr.LocationResolverNotFound.Log(); + + resolver = lr; + return Result.Success; + } + } + + private Result GetRegisteredLocationResolver(out RegisteredLocationResolver resolver) + { + Result rc = _hos.Lr.OpenRegisteredLocationResolver(out RegisteredLocationResolver lr); + + if (rc.IsFailure()) + { + lr?.Dispose(); + resolver = default; + return rc; + } + + resolver = lr; + return Result.Success; + } + + private Result GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) + { + lock (_locker) + { + if (_aocResolver is null) + { + Result rc = _hos.Lr.OpenAddOnContentLocationResolver(out AddOnContentLocationResolver lr); + if (rc.IsFailure()) + { + resolver = default; + return rc; + } + + _aocResolver = lr; + } + + resolver = _aocResolver; + return Result.Success; + } + } + + public Result ResolveApplicationControlPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId) + { + Path.InitEmpty(out path); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveApplicationControlPath(out path, applicationId); + if (rc.IsFailure()) return rc; + + PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/'); + return Result.Success; + } + + public Result ResolveApplicationHtmlDocumentPath(out Path path, Ncm.ApplicationId applicationId, StorageId storageId) + { + Path.InitEmpty(out path); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveApplicationHtmlDocumentPath(out path, applicationId); + if (rc.IsFailure()) return rc; + + PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/'); + return Result.Success; + } + + public virtual Result ResolveProgramPath(out Path path, ulong id, StorageId storageId) + { + Path.InitEmpty(out path); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveProgramPath(out path, new ProgramId(id)); + if (rc.IsFailure()) return rc; + + PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/'); + return Result.Success; + } + + public Result ResolveRomPath(out Path path, ulong id, StorageId storageId) + { + Path.InitEmpty(out path); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + rc = resolver.ResolveProgramPath(out path, new ProgramId(id)); + if (rc.IsFailure()) return rc; + + PathUtility.Replace(path.StrMutable, (byte)'\\', (byte)'/'); + return Result.Success; + } + + public Result ResolveAddOnContentPath(out Path path, DataId dataId) + { + Path.InitEmpty(out path); + + Result rc = GetAddOnContentLocationResolver(out AddOnContentLocationResolver resolver); + if (rc.IsFailure()) return rc; + + return resolver.ResolveAddOnContentPath(out path, dataId); + } + + public Result ResolveDataPath(out Path path, DataId dataId, StorageId storageId) + { + Path.InitEmpty(out path); + + if (storageId == StorageId.None) + return ResultFs.InvalidAlignment.Log(); + + Result rc = GetLocationResolver(out LocationResolver resolver, storageId); + if (rc.IsFailure()) return rc; + + return resolver.ResolveDataPath(out path, dataId); + } + + public Result ResolveRegisteredProgramPath(out Path path, ulong id) + { + Path.InitEmpty(out path); + + Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver); + if (rc.IsFailure()) return rc; + + using (resolver) + { + return resolver.ResolveProgramPath(out path, new ProgramId(id)); + } + } + + public Result ResolveRegisteredHtmlDocumentPath(out Path path, ulong id) + { + Path.InitEmpty(out path); + + Result rc = GetRegisteredLocationResolver(out RegisteredLocationResolver resolver); + if (rc.IsFailure()) return rc; + + using (resolver) + { + return resolver.ResolveHtmlDocumentPath(out path, new ProgramId(id)); + } + } + + private static bool IsValidStorageId(StorageId id) + { + return id == StorageId.Host || + id == StorageId.GameCard || + id == StorageId.BuiltInSystem || + id == StorageId.BuiltInUser || + id == StorageId.SdCard; + } + + private static int GetResolverIndexFromStorageId(StorageId id) + { + Assert.AssertTrue(IsValidStorageId(id)); + + return id switch + { + StorageId.Host => 2, + StorageId.GameCard => 4, + StorageId.BuiltInSystem => 0, + StorageId.BuiltInUser => 1, + StorageId.SdCard => 3, + _ => -1 + }; + } + + public void Dispose() + { + foreach (LocationResolver resolver in _resolvers) + { + resolver?.Dispose(); + } + + _aocResolver?.Dispose(); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs index ea44f8e1..32320b21 100644 --- a/src/LibHac/FsSrv/Impl/MultiCommitManager.cs +++ b/src/LibHac/FsSrv/Impl/MultiCommitManager.cs @@ -1,93 +1,157 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.Fs.Shim; +using LibHac.FsSrv.Sf; +using LibHac.Sf; +using IFileSystem = LibHac.Fs.Fsa.IFileSystem; +using IFile = LibHac.Fs.Fsa.IFile; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.FsSrv.Impl { internal class MultiCommitManager : IMultiCommitManager { private const int MaxFileSystemCount = 10; - private const int CurrentContextVersion = 0x10000; - public const ulong ProgramId = 0x100000000000000; + public const ulong ProgramId = 0x0100000000000000; public const ulong SaveDataId = 0x8000000000000001; private const long SaveDataSize = 0xC000; private const long SaveJournalSize = 0xC000; - private const long ContextFileSize = 0x200; + private const int CurrentCommitContextVersion = 0x10000; + private const long CommitContextFileSize = 0x200; // /commitinfo - private static U8Span ContextFileName => + private static U8Span CommitContextFileName => new U8Span(new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'m', (byte)'m', (byte)'i', (byte)'t', (byte)'i', (byte)'n', (byte)'f', (byte)'o' }); + // Todo: Don't use global lock object private static readonly object Locker = new object(); - private FileSystemProxy FsProxy { get; } - private List FileSystems { get; } = new List(MaxFileSystemCount); - private long CommitCount { get; set; } + private ReferenceCountedDisposable MultiCommitInterface { get; } - public MultiCommitManager(FileSystemProxy fsProxy) + private List> FileSystems { get; } = + new List>(MaxFileSystemCount); + + private long Counter { get; set; } + private HorizonClient Hos { get; } + + public MultiCommitManager( + ref ReferenceCountedDisposable multiCommitInterface, + HorizonClient client) { - FsProxy = fsProxy; + Hos = client; + MultiCommitInterface = Shared.Move(ref multiCommitInterface); } - public Result Add(IFileSystem fileSystem) + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable multiCommitInterface, + HorizonClient client) + { + var manager = new MultiCommitManager(ref multiCommitInterface, client); + return new ReferenceCountedDisposable(manager); + } + + public void Dispose() + { + foreach (ReferenceCountedDisposable fs in FileSystems) + { + fs.Dispose(); + } + } + + /// + /// Ensures the save data used to store the commit context exists. + /// + /// The of the operation. + private Result EnsureSaveDataForContext() + { + Result rc = MultiCommitInterface.Target.OpenMultiCommitContext( + out ReferenceCountedDisposable contextFs); + + if (rc.IsFailure()) + { + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; + + rc = Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, SaveDataFlags.None); + if (rc.IsFailure()) return rc; + } + + contextFs?.Dispose(); + return Result.Success; + } + + /// + /// Adds a file system to the list of file systems to be committed. + /// + /// The file system to be committed. + /// : The operation was successful.
+ /// : The maximum number of file systems have been added. + /// file systems may be added to a single multi-commit.
+ /// : The provided file system has already been added.
+ public Result Add(ReferenceCountedDisposable fileSystem) { if (FileSystems.Count >= MaxFileSystemCount) return ResultFs.MultiCommitFileSystemLimit.Log(); - // Check that the file system hasn't already been added - for (int i = 0; i < FileSystems.Count; i++) + ReferenceCountedDisposable fsaFileSystem = null; + try { - if (ReferenceEquals(FileSystems[i], fileSystem)) - return ResultFs.MultiCommitFileSystemAlreadyAdded.Log(); + Result rc = fileSystem.Target.GetImpl(out fsaFileSystem); + if (rc.IsFailure()) return rc; + + // Check that the file system hasn't already been added + foreach (ReferenceCountedDisposable fs in FileSystems) + { + if (ReferenceEquals(fs.Target, fsaFileSystem.Target)) + return ResultFs.MultiCommitFileSystemAlreadyAdded.Log(); + } + + FileSystems.Add(fsaFileSystem); + fsaFileSystem = null; + + return Result.Success; } - - FileSystems.Add(fileSystem); - return Result.Success; - } - - public Result Commit() - { - lock (Locker) + finally { - Result rc = CreateSave(); - if (rc.IsFailure()) return rc; - - rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs); - if (rc.IsFailure()) return rc; - - return CommitImpl(contextFs); + fsaFileSystem?.Dispose(); } } - private Result CommitImpl(IFileSystem contextFileSystem) + /// + /// Commits all added file systems using to + /// store the . + /// + /// The file system where the commit context will be stored. + /// The of the operation. + private Result Commit(IFileSystem contextFileSystem) { - var context = new CommitContextManager(contextFileSystem); + ContextUpdater context = default; try { - CommitCount = 1; + Counter = 1; - Result rc = context.Initialize(CommitCount, FileSystems.Count); + context = new ContextUpdater(contextFileSystem); + Result rc = context.Create(Counter, FileSystems.Count); if (rc.IsFailure()) return rc; - rc = CommitProvisionally(); + rc = CommitProvisionallyFileSystem(Counter); if (rc.IsFailure()) return rc; - rc = context.SetCommittedProvisionally(); + rc = context.CommitProvisionallyDone(); if (rc.IsFailure()) return rc; - foreach (IFileSystem fs in FileSystems) - { - rc = fs.Commit(); - if (rc.IsFailure()) return rc; - } + rc = CommitFileSystem(); + if (rc.IsFailure()) return rc; - rc = context.Close(); + rc = context.CommitDone(); if (rc.IsFailure()) return rc; } finally @@ -98,34 +162,45 @@ namespace LibHac.FsSrv.Impl return Result.Success; } - private Result CreateSave() + /// + /// Commits all added file systems. + /// + /// The of the operation. + public Result Commit() { - Result rc = FsProxy.OpenMultiCommitContextSaveData(out IFileSystem contextFs); - - if (rc.IsFailure()) + lock (Locker) { - if (!ResultFs.TargetNotFound.Includes(rc)) - { - return rc; - } - - rc = FsProxy.Hos.Fs.CreateSystemSaveData(SaveDataId, SaveDataSize, SaveJournalSize, - SaveDataFlags.None); + Result rc = EnsureSaveDataForContext(); if (rc.IsFailure()) return rc; - } - contextFs?.Dispose(); - return Result.Success; + ReferenceCountedDisposable contextFs = null; + try + { + rc = MultiCommitInterface.Target.OpenMultiCommitContext(out contextFs); + if (rc.IsFailure()) return rc; + + return Commit(contextFs.Target); + } + finally + { + contextFs?.Dispose(); + } + } } - private Result CommitProvisionally() + /// + /// Tries to provisionally commit all the added file systems. + /// + /// The provisional commit counter. + /// The of the operation. + private Result CommitProvisionallyFileSystem(long counter) { Result rc = Result.Success; int i; for (i = 0; i < FileSystems.Count; i++) { - rc = FileSystems[i].CommitProvisionally(CommitCount); + rc = FileSystems[i].Target.CommitProvisionally(counter); if (rc.IsFailure()) break; @@ -136,20 +211,294 @@ namespace LibHac.FsSrv.Impl // Rollback all provisional commits including the failed commit for (int j = 0; j <= i; j++) { - FileSystems[j].Rollback().IgnoreResult(); + FileSystems[j].Target.Rollback().IgnoreResult(); } } return rc; } + /// + /// Tries to fully commit all the added file systems. + /// + /// The of the operation. + private Result CommitFileSystem() + { + // All file systems will try to be recovered committed, even if one fails. + // If any commits fail, the result from the first failed recovery will be returned. + Result result = Result.Success; + + foreach (ReferenceCountedDisposable fs in FileSystems) + { + Result rc = fs.Target.Commit(); + + if (result.IsSuccess() && rc.IsFailure()) + { + result = rc; + } + } + + return Result.Success; + } + + /// + /// Recovers a multi-commit that was interrupted after all file systems had been provisionally committed. + /// The recovery will finish committing any file systems that are still provisionally committed. + /// + /// The core interface used for multi-commits. + /// The file system containing the multi-commit context file. + /// The save data service. + /// : The operation was successful.
+ /// : The version of the commit context + /// file isn't supported.
+ /// : The multi-commit hadn't finished + /// provisionally committing all the file systems.
+ private static Result RecoverCommit(ISaveDataMultiCommitCoreInterface multiCommitInterface, + IFileSystem contextFs, SaveDataFileSystemServiceImpl saveService) + { + IFile contextFile = null; + try + { + // Read the multi-commit context + Result rc = contextFs.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); + if (rc.IsFailure()) return rc; + + Unsafe.SkipInit(out Context context); + rc = contextFile.Read(out _, 0, SpanHelpers.AsByteSpan(ref context), ReadOption.None); + if (rc.IsFailure()) return rc; + + // Note: Nintendo doesn't check if the proper amount of bytes were read, but it + // doesn't really matter since the context is validated. + if (context.Version > CurrentCommitContextVersion) + return ResultFs.InvalidMultiCommitContextVersion.Log(); + + // All the file systems in the multi-commit must have been at least provisionally committed + // before we can try to recover the commit. + if (context.State != CommitState.ProvisionallyCommitted) + return ResultFs.InvalidMultiCommitContextState.Log(); + + // Keep track of the first error that occurs during the recovery + Result recoveryResult = Result.Success; + + int saveCount = 0; + Span savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable infoReader = null; + try + { + rc = saveService.OpenSaveDataIndexerAccessor(out accessor, out _, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out infoReader); + if (rc.IsFailure()) return rc; + + // Iterate through all the saves to find any provisionally committed save data + while (true) + { + Unsafe.SkipInit(out SaveDataInfo info); + + rc = infoReader.Target.Read(out long readCount, OutBuffer.FromStruct(ref info)); + if (rc.IsFailure()) return rc; + + // Break once we're done iterating all save data + if (readCount == 0) + break; + + rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, + in info); + + // Note: Some saves could be missed if there are more than MaxFileSystemCount + // provisionally committed saves. Not sure why Nintendo doesn't catch this. + if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount) + { + savesToRecover[saveCount] = info; + saveCount++; + } + } + } + finally + { + accessor?.Dispose(); + infoReader?.Dispose(); + } + + // Recover the saves by finishing their commits. + // All file systems will try to be recovered, even if one fails. + // If any commits fail, the result from the first failed recovery will be returned. + for (int i = 0; i < saveCount; i++) + { + rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], false); + + if (recoveryResult.IsSuccess() && rc.IsFailure()) + { + recoveryResult = rc; + } + } + + return recoveryResult; + } + finally + { + contextFile?.Dispose(); + } + } + + /// + /// Tries to recover a multi-commit using the context in the provided file system. + /// + /// The core interface used for multi-commits. + /// The file system containing the multi-commit context file. + /// The save data service. + /// + private static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, IFileSystem contextFs, + SaveDataFileSystemServiceImpl saveService) + { + if (multiCommitInterface is null) + return ResultFs.InvalidArgument.Log(); + + if (contextFs is null) + return ResultFs.InvalidArgument.Log(); + + // Keep track of the first error that occurs during the recovery + Result recoveryResult = Result.Success; + + Result rc = RecoverCommit(multiCommitInterface, contextFs, saveService); + + if (rc.IsFailure()) + { + // Note: Yes, the next ~50 lines are exactly the same as the code in RecoverCommit except + // for a single bool value. No, Nintendo doesn't split it out into its own function. + int saveCount = 0; + Span savesToRecover = stackalloc SaveDataInfo[MaxFileSystemCount]; + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable infoReader = null; + try + { + rc = saveService.OpenSaveDataIndexerAccessor(out accessor, out _, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out infoReader); + if (rc.IsFailure()) return rc; + + // Iterate through all the saves to find any provisionally committed save data + while (true) + { + Unsafe.SkipInit(out SaveDataInfo info); + + rc = infoReader.Target.Read(out long readCount, OutBuffer.FromStruct(ref info)); + if (rc.IsFailure()) return rc; + + // Break once we're done iterating all save data + if (readCount == 0) + break; + + rc = multiCommitInterface.IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, + in info); + + // Note: Some saves could be missed if there are more than MaxFileSystemCount + // provisionally committed saves. Not sure why Nintendo doesn't catch this. + if (rc.IsSuccess() && isProvisionallyCommitted && saveCount < MaxFileSystemCount) + { + savesToRecover[saveCount] = info; + saveCount++; + } + } + } + finally + { + accessor?.Dispose(); + infoReader?.Dispose(); + } + + // Recover the saves by rolling them back to the previous commit. + // All file systems will try to be recovered, even if one fails. + // If any commits fail, the result from the first failed recovery will be returned. + for (int i = 0; i < saveCount; i++) + { + rc = multiCommitInterface.RecoverProvisionallyCommittedSaveData(in savesToRecover[i], true); + + if (recoveryResult.IsSuccess() && rc.IsFailure()) + { + recoveryResult = rc; + } + } + } + + // Delete the commit context file + rc = contextFs.DeleteFile(CommitContextFileName); + if (rc.IsFailure()) return rc; + + rc = contextFs.Commit(); + if (rc.IsFailure()) return rc; + + return recoveryResult; + } + + /// + /// Recovers an interrupted multi-commit. The commit will either be completed or rolled back depending on + /// where in the commit process it was interrupted. Does nothing if there is no commit to recover. + /// + /// The core interface used for multi-commits. + /// The save data service. + /// The of the operation.
+ /// : The recovery was successful or there was no multi-commit to recover.
+ public static Result Recover(ISaveDataMultiCommitCoreInterface multiCommitInterface, + SaveDataFileSystemServiceImpl saveService) + { + lock (Locker) + { + bool needsRecover = true; + ReferenceCountedDisposable fileSystem = null; + + try + { + // Check if a multi-commit was interrupted by checking if there's a commit context file. + Result rc = multiCommitInterface.OpenMultiCommitContext(out fileSystem); + + if (rc.IsFailure()) + { + if (!ResultFs.PathNotFound.Includes(rc) && !ResultFs.TargetNotFound.Includes(rc)) + return rc; + + // Unable to open the multi-commit context file system, so there's nothing to recover + needsRecover = false; + } + + if (needsRecover) + { + rc = fileSystem.Target.OpenFile(out IFile file, CommitContextFileName, OpenMode.Read); + file?.Dispose(); + + if (rc.IsFailure()) + { + // Unable to open the context file. No multi-commit to recover. + if (ResultFs.PathNotFound.Includes(rc)) + needsRecover = false; + } + } + + if (!needsRecover) + return Result.Success; + + // There was a context file. Recover the unfinished commit. + return Recover(multiCommitInterface, fileSystem.Target, saveService); + } + finally + { + fileSystem?.Dispose(); + } + } + } + [StructLayout(LayoutKind.Explicit, Size = 0x18)] - private struct CommitContext + private struct Context { [FieldOffset(0x00)] public int Version; [FieldOffset(0x04)] public CommitState State; [FieldOffset(0x08)] public int FileSystemCount; - [FieldOffset(0x10)] public long CommitCount; // I think? + [FieldOffset(0x10)] public long Counter; } private enum CommitState @@ -160,35 +509,41 @@ namespace LibHac.FsSrv.Impl ProvisionallyCommitted = 2 } - private struct CommitContextManager + private struct ContextUpdater { private IFileSystem _fileSystem; - private CommitContext _context; + private Context _context; - public CommitContextManager(IFileSystem contextFileSystem) + public ContextUpdater(IFileSystem contextFileSystem) { _fileSystem = contextFileSystem; _context = default; } - public Result Initialize(long commitCount, int fileSystemCount) + /// + /// Creates and writes the initial commit context to a file. + /// + /// The counter. + /// The number of file systems being committed. + /// The of the operation. + public Result Create(long commitCount, int fileSystemCount) { IFile contextFile = null; try { // Open context file and create if it doesn't exist - Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read); + Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read); if (rc.IsFailure()) { if (!ResultFs.PathNotFound.Includes(rc)) return rc; - rc = _fileSystem.CreateFile(ContextFileName, ContextFileSize, CreateFileOptions.None); + rc = _fileSystem.CreateFile(CommitContextFileName, CommitContextFileSize, CreateFileOptions.None); if (rc.IsFailure()) return rc; - rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.Read); + rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.Read); if (rc.IsFailure()) return rc; } } @@ -199,13 +554,13 @@ namespace LibHac.FsSrv.Impl try { - Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite); + Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; - _context.Version = CurrentContextVersion; + _context.Version = CurrentCommitContextVersion; _context.State = CommitState.NotCommitted; _context.FileSystemCount = fileSystemCount; - _context.CommitCount = commitCount; + _context.Counter = commitCount; // Write the initial context to the file rc = contextFile.Write(0, SpanHelpers.AsByteSpan(ref _context), WriteOption.None); @@ -222,13 +577,18 @@ namespace LibHac.FsSrv.Impl return _fileSystem.Commit(); } - public Result SetCommittedProvisionally() + /// + /// Updates the commit context and writes it to a file, signifying that all + /// the file systems have been provisionally committed. + /// + /// The of the operation. + public Result CommitProvisionallyDone() { IFile contextFile = null; try { - Result rc = _fileSystem.OpenFile(out contextFile, ContextFileName, OpenMode.ReadWrite); + Result rc = _fileSystem.OpenFile(out contextFile, CommitContextFileName, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; _context.State = CommitState.ProvisionallyCommitted; @@ -247,9 +607,13 @@ namespace LibHac.FsSrv.Impl return _fileSystem.Commit(); } - public Result Close() + /// + /// To be called once the multi-commit has been successfully completed. Deletes the commit context file. + /// + /// The of the operation. + public Result CommitDone() { - Result rc = _fileSystem.DeleteFile(ContextFileName); + Result rc = _fileSystem.DeleteFile(CommitContextFileName); if (rc.IsFailure()) return rc; rc = _fileSystem.Commit(); @@ -263,7 +627,7 @@ namespace LibHac.FsSrv.Impl { if (_fileSystem is null) return; - _fileSystem.DeleteFile(ContextFileName).IgnoreResult(); + _fileSystem.DeleteFile(CommitContextFileName).IgnoreResult(); _fileSystem.Commit().IgnoreResult(); _fileSystem = null; diff --git a/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs new file mode 100644 index 00000000..080c84b9 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/OpenCountFileSystem.cs @@ -0,0 +1,74 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; + +namespace LibHac.FsSrv.Impl +{ + internal class OpenCountFileSystem : ForwardingFileSystem + { + private ReferenceCountedDisposable _entryCountSemaphore; + private IUniqueLock _mountCountSemaphore; + + protected OpenCountFileSystem(ref ReferenceCountedDisposable baseFileSystem, + ref ReferenceCountedDisposable entryCountSemaphore) : base( + ref baseFileSystem) + { + Shared.Move(out _entryCountSemaphore, ref entryCountSemaphore); + } + + protected OpenCountFileSystem(ref ReferenceCountedDisposable baseFileSystem, + ref ReferenceCountedDisposable entryCountSemaphore, + ref IUniqueLock mountCountSemaphore) : base(ref baseFileSystem) + { + Shared.Move(out _entryCountSemaphore, ref entryCountSemaphore); + Shared.Move(out _mountCountSemaphore, ref mountCountSemaphore); + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, + ref ReferenceCountedDisposable entryCountSemaphore, + ref IUniqueLock mountCountSemaphore) + { + var filesystem = + new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore, ref mountCountSemaphore); + + return new ReferenceCountedDisposable(filesystem); + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, + ref ReferenceCountedDisposable entryCountSemaphore) + { + var filesystem = + new OpenCountFileSystem(ref baseFileSystem, ref entryCountSemaphore); + + return new ReferenceCountedDisposable(filesystem); + } + + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + { + // Todo: Implement + return base.DoOpenFile(out file, path, mode); + } + + // ReSharper disable once RedundantOverriddenMember + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) + { + // Todo: Implement + return base.DoOpenDirectory(out directory, path, mode); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _entryCountSemaphore?.Dispose(); + _mountCountSemaphore?.Dispose(); + } + + base.Dispose(disposing); + } + } +} diff --git a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs index 736cb080..f81b9ab9 100644 --- a/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs +++ b/src/LibHac/FsSrv/Impl/ProgramRegistryManager.cs @@ -158,6 +158,8 @@ namespace LibHac.FsSrv.Impl public StorageId StorageId { get; } public AccessControl AccessControl { get; } + public ulong ProgramIdValue => ProgramId.Value; + public ProgramInfo(FileSystemServer fsServer, ulong processId, ProgramId programId, StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) { diff --git a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs new file mode 100644 index 00000000..b75ba32b --- /dev/null +++ b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs @@ -0,0 +1,100 @@ +using LibHac.Diag; +using LibHac.Fs; + +namespace LibHac.FsSrv.Impl +{ + public static class SaveDataProperties + { + public static bool IsJournalingSupported(SaveDataType type) + { + switch (type) + { + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Cache: + return true; + case SaveDataType.Temporary: + return false; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + public static bool IsMultiCommitSupported(SaveDataType type) + { + switch (type) + { + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Device: + return true; + case SaveDataType.Bcat: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + public static bool IsSharedOpenNeeded(SaveDataType type) + { + switch (type) + { + case SaveDataType.System: + case SaveDataType.Bcat: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + case SaveDataType.Account: + case SaveDataType.Device: + return true; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + public static bool CanUseIndexerReservedArea(SaveDataType type) + { + switch (type) + { + case SaveDataType.System: + case SaveDataType.SystemBcat: + return true; + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; + } + } + + public static bool IsSystemSaveData(SaveDataType type) + { + switch (type) + { + case SaveDataType.System: + case SaveDataType.SystemBcat: + return true; + case SaveDataType.Account: + case SaveDataType.Bcat: + case SaveDataType.Device: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return false; + default: + Abort.UnexpectedDefault(); + return default; + } + } + } +} diff --git a/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs b/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs new file mode 100644 index 00000000..fb838295 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/StorageInterfaceAdapter.cs @@ -0,0 +1,109 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using IStorageSf = LibHac.FsSrv.Sf.IStorage; + +namespace LibHac.FsSrv.Impl +{ + internal class StorageInterfaceAdapter : IStorageSf + { + private ReferenceCountedDisposable BaseStorage { get; } + + private StorageInterfaceAdapter(ref ReferenceCountedDisposable baseStorage) + { + BaseStorage = Shared.Move(ref baseStorage); + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseStorage) + { + var adapter = new StorageInterfaceAdapter(ref baseStorage); + return new ReferenceCountedDisposable(adapter); + } + + public void Dispose() + { + BaseStorage?.Dispose(); + } + + public Result Read(long offset, Span destination) + { + const int maxTryCount = 2; + + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (destination.Length < 0) + return ResultFs.InvalidSize.Log(); + + Result rc = Result.Success; + + for (int tryNum = 0; tryNum < maxTryCount; tryNum++) + { + rc = BaseStorage.Target.Read(offset, destination); + + // Retry on ResultDataCorrupted + if (!ResultFs.DataCorrupted.Includes(rc)) + break; + } + + return rc; + } + + public Result Write(long offset, ReadOnlySpan source) + { + if (offset < 0) + return ResultFs.InvalidOffset.Log(); + + if (source.Length < 0) + return ResultFs.InvalidSize.Log(); + + // Note: Thread priority is temporarily increased when writing in FS + + return BaseStorage.Target.Write(offset, source); + } + + public Result Flush() + { + return BaseStorage.Target.Flush(); + } + + public Result SetSize(long size) + { + if (size < 0) + return ResultFs.InvalidSize.Log(); + + return BaseStorage.Target.SetSize(size); + } + + public Result GetSize(out long size) + { + return BaseStorage.Target.GetSize(out size); + } + + public Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size) + { + rangeInfo = new QueryRangeInfo(); + + if (operationId == (int)OperationId.InvalidateCache) + { + Result rc = BaseStorage.Target.OperateRange(Span.Empty, OperationId.InvalidateCache, offset, size, + ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + } + else if (operationId == (int)OperationId.QueryRange) + { + Unsafe.SkipInit(out QueryRangeInfo info); + + Result rc = BaseStorage.Target.OperateRange(SpanHelpers.AsByteSpan(ref info), OperationId.QueryRange, + offset, size, ReadOnlySpan.Empty); + if (rc.IsFailure()) return rc; + + rangeInfo.Merge(in info); + } + + return Result.Success; + } + } +} diff --git a/src/LibHac/FsSrv/Impl/Utility.cs b/src/LibHac/FsSrv/Impl/Utility.cs new file mode 100644 index 00000000..51111b06 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/Utility.cs @@ -0,0 +1,171 @@ +using System; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using LibHac.Util; + +namespace LibHac.FsSrv.Impl +{ + internal static class Utility + { + public static Result EnsureDirectory(IFileSystem fileSystem, U8Span path) + { + FsPath.FromSpan(out FsPath pathBuffer, 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.AssertTrue(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.AssertTrue(path.Length > 0); + Assert.NotEqual(StringTraits.DirectorySeparator, path[path.Length - 1]); + + // Make sure the path's not too long + if (path.Length > PathTool.EntryNameLengthMax) + return ResultFs.TooLongPath.Log(); + + // Iterate until we run out of path or find the next separator + int length = path.Length - 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; + } + + public static Result CreateSubDirectoryFileSystem(out ReferenceCountedDisposable subDirFileSystem, + ref ReferenceCountedDisposable baseFileSystem, U8Span subPath, bool preserveUnc = false) + { + subDirFileSystem = default; + + // Check if the directory exists + Result rc = baseFileSystem.Target.OpenDirectory(out IDirectory dir, subPath, OpenDirectoryMode.Directory); + if (rc.IsFailure()) return rc; + + dir.Dispose(); + + var fs = new SubdirectoryFileSystem(ref baseFileSystem, preserveUnc); + using (var subDirFs = new ReferenceCountedDisposable(fs)) + { + rc = subDirFs.Target.Initialize(subPath); + if (rc.IsFailure()) return rc; + + subDirFileSystem = subDirFs.AddReference(); + return Result.Success; + } + } + + public static Result WrapSubDirectory(out ReferenceCountedDisposable fileSystem, + ref ReferenceCountedDisposable baseFileSystem, U8Span path, bool createIfMissing) + { + fileSystem = default; + + // The path must already exist if we're not automatically creating it + if (!createIfMissing) + { + Result result = baseFileSystem.Target.GetEntryType(out _, path); + if (result.IsFailure()) return result; + } + + // Ensure the path exists or check if it's a directory + Result rc = EnsureDirectory(baseFileSystem.Target, path); + if (rc.IsFailure()) return rc; + + return CreateSubDirectoryFileSystem(out fileSystem, ref baseFileSystem, path); + } + + private static ReadOnlySpan FileSystemRootPath => // / + new[] + { + (byte) '/' + }; + } +} diff --git a/src/LibHac/FsSrv/NcaFileSystemService.cs b/src/LibHac/FsSrv/NcaFileSystemService.cs new file mode 100644 index 00000000..df311822 --- /dev/null +++ b/src/LibHac/FsSrv/NcaFileSystemService.cs @@ -0,0 +1,645 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using LibHac.Lr; +using LibHac.Ncm; +using LibHac.Spl; +using LibHac.Util; +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; + +namespace LibHac.FsSrv +{ + internal class NcaFileSystemService : IRomFileSystemAccessFailureManager + { + private const int AocSemaphoreCount = 128; + private const int RomSemaphoreCount = 10; + + private ReferenceCountedDisposable.WeakReference SelfReference { get; set; } + private NcaFileSystemServiceImpl ServiceImpl { get; } + private ulong ProcessId { get; } + private SemaphoreAdaptor AocMountCountSemaphore { get; } + private SemaphoreAdaptor RomMountCountSemaphore { get; } + + private NcaFileSystemService(NcaFileSystemServiceImpl serviceImpl, ulong processId) + { + ServiceImpl = serviceImpl; + ProcessId = processId; + AocMountCountSemaphore = new SemaphoreAdaptor(AocSemaphoreCount, AocSemaphoreCount); + RomMountCountSemaphore = new SemaphoreAdaptor(RomSemaphoreCount, RomSemaphoreCount); + } + + public static ReferenceCountedDisposable CreateShared( + NcaFileSystemServiceImpl serviceImpl, ulong processId) + { + // Create the service + var ncaService = new NcaFileSystemService(serviceImpl, processId); + + // Wrap the service in a ref-counter and give the service a weak self-reference + var sharedService = new ReferenceCountedDisposable(ncaService); + ncaService.SelfReference = + new ReferenceCountedDisposable.WeakReference(sharedService); + + return sharedService; + } + + public void Dispose() + { + AocMountCountSemaphore?.Dispose(); + RomMountCountSemaphore?.Dispose(); + } + + public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, + ProgramId programId, FileSystemProxyType fsType) + { + fileSystem = default; + + const StorageType storageFlag = StorageType.All; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + // Get the program info for the caller and verify permissions + Result rc = GetProgramInfo(out ProgramInfo callerProgramInfo); + if (rc.IsFailure()) return rc; + + if (fsType != FileSystemProxyType.Manual) + { + if (fsType == FileSystemProxyType.Logo || fsType == FileSystemProxyType.Control) + return ResultFs.NotImplemented.Log(); + else + return ResultFs.InvalidArgument.Log(); + } + + Accessibility accessibility = + callerProgramInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentManual); + + if (!accessibility.CanRead) + return ResultFs.PermissionDenied.Log(); + + // Get the program info for the owner of the file system being opened + rc = GetProgramInfoByProgramId(out ProgramInfo ownerProgramInfo, programId.Value); + 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); + + // 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); + + ReferenceCountedDisposable tempFileSystem = null; + ReferenceCountedDisposable accessFailureManager = null; + try + { + if (ResultLr.HtmlDocumentNotFound.Includes(patchResult)) + { + // There must either be an original version or patch version of the file system being opened + if (originalResult.IsFailure()) + return originalResult; + + Assert.AssertTrue(originalPathNormalizerHasValue); + + // There is an original version and no patch version. Open the original directly + rc = ServiceImpl.OpenFileSystem(out tempFileSystem, originalPathNormalizer.Path, fsType, programId.Value); + 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 + var patchPathNormalizer = new PathNormalizer(patchPath, GetPathNormalizerOptions(patchPath)); + if (patchPathNormalizer.Result.IsFailure()) return patchPathNormalizer.Result; + + if (patchResult.IsFailure()) + return patchResult; + + U8Span normalizedPatchPath = patchPathNormalizer.Path; + + // Open the file system using both the original and patch versions + rc = ServiceImpl.OpenFileSystemWithPatch(out tempFileSystem, normalizedOriginalPath, + normalizedPatchPath, fsType, programId.Value); + if (rc.IsFailure()) return rc; + } + + // Add all the file system wrappers + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + + accessFailureManager = SelfReference.AddReference(); + tempFileSystem = DeepRetryFileSystem.CreateShared(ref tempFileSystem, ref accessFailureManager); + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + accessFailureManager?.Dispose(); + } + } + + public Result OpenCodeFileSystem(out ReferenceCountedDisposable fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenDataFileSystemByCurrentProcess(out ReferenceCountedDisposable fileSystem) + { + throw new NotImplementedException(); + } + + private Result OpenDataStorageCore(out ReferenceCountedDisposable storage, out Hash ncaHeaderDigest, + ulong id, StorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByCurrentProcess(out ReferenceCountedDisposable storage) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByProgramId(out ReferenceCountedDisposable storage, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenFileSystemWithId(out ReferenceCountedDisposable fileSystem, in FspPath path, + ulong id, FileSystemProxyType fsType) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + AccessControl ac = programInfo.AccessControl; + + switch (fsType) + { + case FileSystemProxyType.Logo: + if (!ac.GetAccessibilityFor(AccessibilityType.MountLogo).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Control: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentControl).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Manual: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentManual).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Meta: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentMeta).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Data: + if (!ac.GetAccessibilityFor(AccessibilityType.MountContentData).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + case FileSystemProxyType.Package: + if (!ac.GetAccessibilityFor(AccessibilityType.MountApplicationPackage).CanRead) + return ResultFs.PermissionDenied.Log(); + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + if (fsType == FileSystemProxyType.Meta) + { + id = ulong.MaxValue; + } + else if (id == ulong.MaxValue) + { + return ResultFs.InvalidArgument.Log(); + } + + bool canMountSystemDataPrivate = ac.GetAccessibilityFor(AccessibilityType.MountSystemDataPrivate).CanRead; + + var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + ReferenceCountedDisposable fs = null; + + try + { + rc = ServiceImpl.OpenFileSystem(out fs, out _, path, fsType, + canMountSystemDataPrivate, id); + if (rc.IsFailure()) return rc; + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref fs); + + return Result.Success; + } + finally + { + fs?.Dispose(); + } + } + + public Result OpenDataFileSystemByProgramId(out ReferenceCountedDisposable fileSystem, + ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result OpenDataStorageByDataId(out ReferenceCountedDisposable storage, DataId dataId, + StorageId storageId) + { + throw new NotImplementedException(); + } + + public Result OpenDataFileSystemWithProgramIndex(out ReferenceCountedDisposable fileSystem, + byte programIndex) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + // Get the program ID of the program with the specified index if in a multi-program application + rc = ServiceImpl.ResolveRomReferenceProgramId(out ProgramId targetProgramId, programInfo.ProgramId, + programIndex); + if (rc.IsFailure()) return rc; + + ReferenceCountedDisposable tempFileSystem = null; + try + { + rc = OpenDataFileSystemCore(out tempFileSystem, out _, targetProgramId.Value, + programInfo.StorageId); + if (rc.IsFailure()) return rc; + + // Verify the caller has access to the file system + if (targetProgramId != programInfo.ProgramId && + !programInfo.AccessControl.HasContentOwnerId(targetProgramId.Value)) + { + return ResultFs.PermissionDenied.Log(); + } + + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + if (tempFileSystem is null) + return ResultFs.AllocationFailureInAllocateShared.Log(); + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + if (fileSystem is null) + return ResultFs.AllocationFailureInCreateShared.Log(); + + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + } + } + + public Result OpenDataStorageWithProgramIndex(out ReferenceCountedDisposable storage, + byte programIndex) + { + throw new NotImplementedException(); + } + + public Result GetRightsId(out RightsId rightsId, ProgramId programId, StorageId storageId) + { + Unsafe.SkipInit(out rightsId); + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) + return ResultFs.PermissionDenied.Log(); + + rc = ServiceImpl.ResolveProgramPath(out Path programPath, programId, storageId); + if (rc.IsFailure()) return rc; + + var normalizer = new PathNormalizer(programPath, GetPathNormalizerOptions(programPath)); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out _, normalizer.Path, programId); + if (rc.IsFailure()) return rc; + + rightsId = tempRightsId; + return Result.Success; + } + + public Result GetRightsIdAndKeyGenerationByPath(out RightsId rightsId, out byte keyGeneration, in FspPath path) + { + Unsafe.SkipInit(out rightsId); + Unsafe.SkipInit(out keyGeneration); + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.All); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.GetRightsId)) + return ResultFs.PermissionDenied.Log(); + + var normalizer = new PathNormalizer(path, GetPathNormalizerOptions(path)); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + rc = ServiceImpl.GetRightsId(out RightsId tempRightsId, out byte tempKeyGeneration, normalizer.Path, + new ProgramId(ulong.MaxValue)); + if (rc.IsFailure()) return rc; + + rightsId = tempRightsId; + keyGeneration = tempKeyGeneration; + return Result.Success; + } + + private Result OpenDataFileSystemCore(out ReferenceCountedDisposable fileSystem, out bool isHostFs, + ulong programId, StorageId storageId) + { + fileSystem = default; + Unsafe.SkipInit(out isHostFs); + + if (Unsafe.IsNullRef(ref isHostFs)) + return ResultFs.NullptrArgument.Log(); + + StorageType storageFlag = ServiceImpl.GetStorageFlag(programId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = ServiceImpl.ResolveRomPath(out Path romPath, programId, storageId); + if (rc.IsFailure()) return rc; + + var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath)); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + isHostFs = IsHostFs(romPath); + + ReferenceCountedDisposable tempFileSystem = null; + try + { + rc = ServiceImpl.OpenDataFileSystem(out tempFileSystem, normalizer.Path, FileSystemProxyType.Rom, + programId); + if (rc.IsFailure()) return rc; + + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + + Shared.Move(out fileSystem, ref tempFileSystem); + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + } + } + + public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, + ContentStorageId contentStorageId) + { + fileSystem = default; + + StorageType storageFlag = contentStorageId == ContentStorageId.System ? StorageType.Bis : StorageType.All; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountContentStorage); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable tempFileSystem = null; + + try + { + rc = ServiceImpl.OpenContentStorageFileSystem(out tempFileSystem, contentStorageId); + if (rc.IsFailure()) return rc; + + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + + // Create an SF adapter for the file system + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + } + } + + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); + + return ServiceImpl.RegisterExternalKey(in rightsId, in accessKey); + } + + public Result UnregisterExternalKey(in RightsId rightsId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); + + return ServiceImpl.UnregisterExternalKey(in rightsId); + } + + public Result UnregisterAllExternalKey() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.RegisterExternalKey)) + return ResultFs.PermissionDenied.Log(); + + return ServiceImpl.UnregisterAllExternalKey(); + } + + public Result RegisterUpdatePartition() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.RegisterUpdatePartition)) + return ResultFs.PermissionDenied.Log(); + + rc = ServiceImpl.ResolveRomPath(out Path romPath, programInfo.ProgramIdValue, programInfo.StorageId); + if (rc.IsFailure()) return rc; + + var normalizer = new PathNormalizer(romPath, GetPathNormalizerOptions(romPath)); + if (normalizer.Result.IsFailure()) return normalizer.Result; + + return ServiceImpl.RegisterUpdatePartition(programInfo.ProgramIdValue, normalizer.Path); + } + + public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem) + { + fileSystem = default; + + var storageFlag = StorageType.All; + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountRegisteredUpdatePartition); + if (!accessibility.CanRead) + return ResultFs.PermissionDenied.Log(); + + ReferenceCountedDisposable tempFileSystem = null; + try + { + rc = ServiceImpl.OpenRegisteredUpdatePartition(out tempFileSystem); + if (rc.IsFailure()) return rc; + + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + if (tempFileSystem is null) + return ResultFs.AllocationFailureInAllocateShared.Log(); + + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + if (tempFileSystem is null) + return ResultFs.AllocationFailureInAllocateShared.Log(); + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + if (fileSystem is null) + return ResultFs.AllocationFailureInCreateShared.Log(); + + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + } + } + + public Result IsArchivedProgram(out bool isArchived, ulong processId) + { + throw new NotImplementedException(); + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetEncryptionSeed)) + return ResultFs.PermissionDenied.Log(); + + return ServiceImpl.SetSdCardEncryptionSeed(in encryptionSeed); + } + + public Result OpenSystemDataUpdateEventNotifier(out ReferenceCountedDisposable eventNotifier) + { + throw new NotImplementedException(); + } + + public Result NotifySystemDataUpdateEvent() + { + throw new NotImplementedException(); + } + + public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected) + { + return ServiceImpl.HandleResolubleAccessFailure(out wasDeferred, resultForNoFailureDetected, ProcessId); + } + + public void IncrementRomFsRemountForDataCorruptionCount() + { + ServiceImpl.IncrementRomFsRemountForDataCorruptionCount(); + } + + public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() + { + ServiceImpl.IncrementRomFsUnrecoverableDataCorruptionByRemountCount(); + } + + public void IncrementRomFsRecoveredByInvalidateCacheCount() + { + ServiceImpl.IncrementRomFsRecoveredByInvalidateCacheCount(); + } + + private Result TryAcquireAddOnContentOpenCountSemaphore(out IUniqueLock semaphoreLock) + { + throw new NotImplementedException(); + } + + private Result TryAcquireRomMountCountSemaphore(out IUniqueLock semaphoreLock) + { + throw new NotImplementedException(); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return ServiceImpl.GetProgramInfoByProcessId(out programInfo, ProcessId); + } + + private Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) + { + return ServiceImpl.GetProgramInfoByProcessId(out programInfo, processId); + } + + private Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + 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.PreserveTailSeparator | 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) + { + return OpenDataStorageCore(out storage, out ncaHeaderDigest, id, storageId); + } + } +} diff --git a/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs new file mode 100644 index 00000000..1355b6dd --- /dev/null +++ b/src/LibHac/FsSrv/NcaFileSystemServiceImpl.cs @@ -0,0 +1,1023 @@ +using System; +using System.Buffers.Text; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Impl; +using LibHac.FsSystem; +using LibHac.FsSystem.NcaUtils; +using LibHac.Lr; +using LibHac.Ncm; +using LibHac.Spl; +using LibHac.Util; +using RightsId = LibHac.Fs.RightsId; +using Utility = LibHac.FsSrv.Impl.Utility; + +namespace LibHac.FsSrv +{ + public class NcaFileSystemServiceImpl + { + private Configuration _config; + // UpdatePartitionPath + private ExternalKeySet _externalKeyManager; + private LocationResolverSet _locationResolverSet; + // SystemDataUpdateEventManager + private EncryptionSeed _encryptionSeed; + private int _romFsRemountForDataCorruptionCount; + private int _romfsUnrecoverableDataCorruptionByRemountCount; + private int _romFsRecoveredByInvalidateCacheCount; + private object _romfsCountLocker; + + public NcaFileSystemServiceImpl(in Configuration configuration, ExternalKeySet externalKeySet) + { + _config = configuration; + _externalKeyManager = externalKeySet; + _locationResolverSet = new LocationResolverSet(_config.HorizonClient); + _romfsCountLocker = new object(); + } + + public struct Configuration + { + public BaseFileSystemServiceImpl BaseFsService; + public IHostFileSystemCreator HostFsCreator; + public ITargetManagerFileSystemCreator TargetManagerFsCreator; + public IPartitionFileSystemCreator PartitionFsCreator; + public IRomFileSystemCreator RomFsCreator; + public IStorageOnNcaCreator StorageOnNcaCreator; + public ISubDirectoryFileSystemCreator SubDirectoryFsCreator; + public IEncryptedFileSystemCreator EncryptedFsCreator; + public ProgramRegistryServiceImpl ProgramRegistryService; + // AccessFailureService + public InternalProgramIdRangeForSpeedEmulation SpeedEmulationRange; + + // LibHac additions + public HorizonClient HorizonClient; + public ProgramRegistryImpl ProgramRegistry; + } + + + private struct MountInfo + { + public bool IsGameCard; + public int GcHandle; + public bool IsHostFs; + public bool CanMountNca; + } + + + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, + FileSystemProxyType type, ulong id) + { + return OpenFileSystem(out fileSystem, out Unsafe.NullRef(), path, type, false, id); + } + + public Result OpenFileSystem(out ReferenceCountedDisposable fileSystem, + out CodeVerificationData verificationData, U8Span path, FileSystemProxyType type, + bool canMountSystemDataPrivate, ulong id) + { + fileSystem = default; + + Unsafe.SkipInit(out verificationData); + if (!Unsafe.IsNullRef(ref verificationData)) + verificationData.IsValid = false; + + // Get a reference to the path that will be advanced as each part of the path is parsed + U8Span currentPath = path.Slice(0, StringUtils.GetLength(path)); + + // Open the root filesystem based on the path's mount name + Result rc = ParseMountName(ref currentPath, + out ReferenceCountedDisposable baseFileSystem, out bool shouldContinue, + out MountInfo mountNameInfo); + if (rc.IsFailure()) return rc; + + // Don't continue if the rest of the path is empty + if (!shouldContinue) + return ResultFs.InvalidArgument.Log(); + + if (type == FileSystemProxyType.Logo && mountNameInfo.IsGameCard) + { + rc = _config.BaseFsService.OpenGameCardFileSystem(out fileSystem, + new GameCardHandle(mountNameInfo.GcHandle), + GameCardPartition.Logo); + + if (rc.IsSuccess()) + return Result.Success; + + if (!ResultFs.PartitionNotFound.Includes(rc)) + return rc; + } + + rc = CheckDirOrNcaOrNsp(ref currentPath, out bool isDirectory); + if (rc.IsFailure()) return rc; + + if (isDirectory) + { + if (!mountNameInfo.IsHostFs) + return ResultFs.PermissionDenied.Log(); + + if (type == FileSystemProxyType.Manual) + { + ReferenceCountedDisposable manualFileSystem = null; + ReferenceCountedDisposable readOnlyFileSystem = null; + try + { + rc = ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs(out manualFileSystem, ref baseFileSystem, + currentPath); + if (rc.IsFailure()) return rc; + + readOnlyFileSystem = ReadOnlyFileSystem.CreateShared(ref manualFileSystem); + if (readOnlyFileSystem?.Target is null) + return ResultFs.AllocationFailureInAllocateShared.Log(); + + Shared.Move(out fileSystem, ref readOnlyFileSystem); + return Result.Success; + } + finally + { + manualFileSystem?.Dispose(); + readOnlyFileSystem?.Dispose(); + } + } + + return ParseDir(currentPath, out fileSystem, ref baseFileSystem, type, true); + } + + rc = ParseNsp(ref currentPath, out ReferenceCountedDisposable nspFileSystem, baseFileSystem); + + if (rc.IsSuccess()) + { + // Must be the end of the path to open Application Package FS type + if (currentPath.Length == 0 || currentPath[0] == 0) + { + if (type == FileSystemProxyType.Package) + { + fileSystem = nspFileSystem; + return Result.Success; + } + + return ResultFs.InvalidArgument.Log(); + } + + baseFileSystem = nspFileSystem; + } + + if (!mountNameInfo.CanMountNca) + { + return ResultFs.InvalidNcaMountPoint.Log(); + } + + ulong openProgramId = mountNameInfo.IsHostFs ? ulong.MaxValue : id; + + rc = ParseNca(ref currentPath, out Nca nca, baseFileSystem, openProgramId); + if (rc.IsFailure()) return rc; + + rc = OpenStorageByContentType(out ReferenceCountedDisposable ncaSectionStorage, nca, + out NcaFormatType fsType, type, mountNameInfo.IsGameCard, canMountSystemDataPrivate); + if (rc.IsFailure()) return rc; + + switch (fsType) + { + case NcaFormatType.Romfs: + return _config.RomFsCreator.Create(out fileSystem, ncaSectionStorage); + case NcaFormatType.Pfs0: + return _config.PartitionFsCreator.Create(out fileSystem, ncaSectionStorage); + default: + return ResultFs.InvalidNcaFileSystemType.Log(); + } + } + + public Result OpenDataFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, + FileSystemProxyType fsType, ulong programId) + { + throw new NotImplementedException(); + } + + public Result OpenStorageWithPatch(out ReferenceCountedDisposable storage, out Hash ncaHeaderDigest, + U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id) + { + throw new NotImplementedException(); + } + + public Result OpenFileSystemWithPatch(out ReferenceCountedDisposable fileSystem, + U8Span originalNcaPath, U8Span currentNcaPath, FileSystemProxyType fsType, ulong id) + { + fileSystem = default; + + ReferenceCountedDisposable romFsStorage = null; + try + { + Result rc = OpenStorageWithPatch(out romFsStorage, out Unsafe.NullRef(), originalNcaPath, + currentNcaPath, fsType, id); + if (rc.IsFailure()) return rc; + + return _config.RomFsCreator.Create(out fileSystem, romFsStorage); + } + finally + { + romFsStorage?.Dispose(); + } + } + + public Result OpenContentStorageFileSystem(out ReferenceCountedDisposable fileSystem, + ContentStorageId contentStorageId) + { + const int storagePathMaxLen = 0x40; + + fileSystem = default; + + ReferenceCountedDisposable baseFileSystem = null; + ReferenceCountedDisposable subDirFileSystem = null; + ReferenceCountedDisposable encryptedFileSystem = null; + + try + { + Result rc; + + // Open the appropriate base file system for the content storage ID + switch (contentStorageId) + { + case ContentStorageId.System: + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty, + BisPartitionId.System); + break; + case ContentStorageId.User: + rc = _config.BaseFsService.OpenBisFileSystem(out baseFileSystem, U8Span.Empty, + BisPartitionId.User); + break; + case ContentStorageId.SdCard: + rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out baseFileSystem); + break; + default: + rc = ResultFs.InvalidArgument.Log(); + break; + } + + if (rc.IsFailure()) return rc; + + // Build the appropriate path for the content storage ID + Span contentStoragePath = stackalloc byte[storagePathMaxLen]; + + if (contentStorageId == ContentStorageId.SdCard) + { + var sb = new U8StringBuilder(contentStoragePath); + sb.Append(StringTraits.DirectorySeparator).Append(SdCardNintendoRootDirectoryName); + sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); + } + else + { + var sb = new U8StringBuilder(contentStoragePath); + sb.Append(StringTraits.DirectorySeparator).Append(ContentStorageDirectoryName); + } + + // Make sure the content storage path exists + // ReSharper disable once PossibleNullReferenceException + rc = Utility.EnsureDirectory(baseFileSystem.Target, new U8Span(contentStoragePath)); + if (rc.IsFailure()) return rc; + + rc = _config.SubDirectoryFsCreator.Create(out subDirFileSystem, ref baseFileSystem, + new U8Span(contentStoragePath)); + if (rc.IsFailure()) return rc; + + // Only content on the SD card is encrypted + if (contentStorageId != ContentStorageId.SdCard) + { + // Move the shared reference to the out variable + Shared.Move(out fileSystem, ref subDirFileSystem); + + return Result.Success; + } + + // Create an encrypted file system for SD card content storage + rc = _config.EncryptedFsCreator.Create(out encryptedFileSystem, subDirFileSystem, + EncryptedFsKeyId.Content, in _encryptionSeed); + if (rc.IsFailure()) return rc; + + Shared.Move(out fileSystem, ref encryptedFileSystem); + + return Result.Success; + } + finally + { + baseFileSystem?.Dispose(); + subDirFileSystem?.Dispose(); + encryptedFileSystem?.Dispose(); + } + } + + public Result GetRightsId(out RightsId rightsId, out byte keyGeneration, U8Span path, ProgramId programId) + { + throw new NotImplementedException(); + } + + public Result RegisterExternalKey(in RightsId rightsId, in AccessKey accessKey) + { + return _externalKeyManager.Add(rightsId, accessKey); + } + + public Result UnregisterExternalKey(in RightsId rightsId) + { + _externalKeyManager.Remove(rightsId); + + return Result.Success; + } + + public Result UnregisterAllExternalKey() + { + _externalKeyManager.Clear(); + + return Result.Success; + } + + public Result RegisterUpdatePartition(ulong programId, U8Span path) + { + throw new NotImplementedException(); + } + + public Result OpenRegisteredUpdatePartition(out ReferenceCountedDisposable fileSystem) + { + throw new NotImplementedException(); + } + + private Result ParseMountName(ref U8Span path, + out ReferenceCountedDisposable fileSystem, out bool shouldContinue, out MountInfo info) + { + fileSystem = default; + + info = new MountInfo(); + shouldContinue = true; + + if (StringUtils.Compare(path, CommonPaths.GameCardFileSystemMountName, + CommonPaths.GameCardFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.GameCardFileSystemMountName.Length); + + if (StringUtils.GetLength(path.Value, 9) < 9) + return ResultFs.InvalidPath.Log(); + + GameCardPartition partition; + switch ((char)path[0]) + { + case CommonPaths.GameCardFileSystemMountNameUpdateSuffix: + partition = GameCardPartition.Update; + break; + case CommonPaths.GameCardFileSystemMountNameNormalSuffix: + partition = GameCardPartition.Normal; + break; + case CommonPaths.GameCardFileSystemMountNameSecureSuffix: + partition = GameCardPartition.Secure; + break; + default: + return ResultFs.InvalidPath.Log(); + } + + path = path.Slice(1); + bool handleParsed = Utf8Parser.TryParse(path, out int handle, out int bytesConsumed); + + if (!handleParsed || handle == -1 || bytesConsumed != 8) + return ResultFs.InvalidPath.Log(); + + path = path.Slice(8); + + Result rc = _config.BaseFsService.OpenGameCardFileSystem(out fileSystem, new GameCardHandle(handle), + partition); + if (rc.IsFailure()) return rc; + + info.GcHandle = handle; + info.IsGameCard = true; + info.CanMountNca = true; + } + + else if (StringUtils.Compare(path, CommonPaths.ContentStorageSystemMountName, + CommonPaths.ContentStorageSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.ContentStorageSystemMountName.Length); + + Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.System); + if (rc.IsFailure()) return rc; + + info.CanMountNca = true; + } + + else if (StringUtils.Compare(path, CommonPaths.ContentStorageUserMountName, + CommonPaths.ContentStorageUserMountName.Length) == 0) + { + path = path.Slice(CommonPaths.ContentStorageUserMountName.Length); + + Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.User); + if (rc.IsFailure()) return rc; + + info.CanMountNca = true; + } + + else if (StringUtils.Compare(path, CommonPaths.ContentStorageSdCardMountName, + CommonPaths.ContentStorageSdCardMountName.Length) == 0) + { + path = path.Slice(CommonPaths.ContentStorageSdCardMountName.Length); + + Result rc = OpenContentStorageFileSystem(out fileSystem, ContentStorageId.SdCard); + if (rc.IsFailure()) return rc; + + info.CanMountNca = true; + } + + else if (StringUtils.Compare(path, CommonPaths.BisCalibrationFilePartitionMountName, + CommonPaths.BisCalibrationFilePartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisCalibrationFilePartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, + BisPartitionId.CalibrationFile); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisSafeModePartitionMountName, + CommonPaths.BisSafeModePartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisSafeModePartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, + BisPartitionId.SafeMode); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisUserPartitionMountName, + CommonPaths.BisUserPartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisUserPartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, BisPartitionId.User); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.BisSystemPartitionMountName, + CommonPaths.BisSystemPartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.BisSystemPartitionMountName.Length); + + Result rc = _config.BaseFsService.OpenBisFileSystem(out fileSystem, U8Span.Empty, + BisPartitionId.System); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.SdCardFileSystemMountName, + CommonPaths.SdCardFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.SdCardFileSystemMountName.Length); + + Result rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out fileSystem); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.HostRootFileSystemMountName, + CommonPaths.HostRootFileSystemMountName.Length) == 0) + { + path = path.Slice(CommonPaths.HostRootFileSystemMountName.Length); + + info.IsHostFs = true; + info.CanMountNca = true; + + Result rc = OpenHostFileSystem(out fileSystem, U8Span.Empty, openCaseSensitive: false); + if (rc.IsFailure()) return rc; + } + + else if (StringUtils.Compare(path, CommonPaths.RegisteredUpdatePartitionMountName, + CommonPaths.RegisteredUpdatePartitionMountName.Length) == 0) + { + path = path.Slice(CommonPaths.RegisteredUpdatePartitionMountName.Length); + + info.CanMountNca = true; + + throw new NotImplementedException(); + } + + else + { + return ResultFs.PathNotFound.Log(); + } + + if (StringUtils.GetLength(path, FsPath.MaxLength) == 0) + { + shouldContinue = false; + } + + return Result.Success; + } + + private Result CheckDirOrNcaOrNsp(ref U8Span path, out bool isDirectory) + { + isDirectory = default; + + ReadOnlySpan mountSeparator = new[] { (byte)':', (byte)'/' }; + + if (StringUtils.Compare(mountSeparator, path, mountSeparator.Length) != 0) + { + return ResultFs.PathNotFound.Log(); + } + + path = path.Slice(1); + int pathLen = StringUtils.GetLength(path); + + if (path[pathLen - 1] == '/') + { + isDirectory = true; + return Result.Success; + } + + // Now make sure the path has a content file extension + if (pathLen < 5) + return ResultFs.PathNotFound.Log(); + + ReadOnlySpan fileExtension = path.Value.Slice(pathLen - 4); + + ReadOnlySpan ncaExtension = new[] { (byte)'.', (byte)'n', (byte)'c', (byte)'a' }; + ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; + + if (StringUtils.CompareCaseInsensitive(fileExtension, ncaExtension) == 0 || + StringUtils.CompareCaseInsensitive(fileExtension, nspExtension) == 0) + { + isDirectory = false; + return Result.Success; + } + + return ResultFs.PathNotFound.Log(); + } + + private Result ParseDir(U8Span path, + out ReferenceCountedDisposable contentFileSystem, + ref ReferenceCountedDisposable baseFileSystem, FileSystemProxyType fsType, bool preserveUnc) + { + contentFileSystem = default; + + ReferenceCountedDisposable subDirFs = null; + try + { + Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, path, preserveUnc); + if (rc.IsFailure()) return rc; + + return ParseContentTypeForDirectory(out contentFileSystem, ref subDirFs, fsType); + } + finally + { + subDirFs?.Dispose(); + } + } + + private Result ParseDirWithPathCaseNormalizationOnCaseSensitiveHostFs( + out ReferenceCountedDisposable contentFileSystem, + ref ReferenceCountedDisposable baseFileSystem, U8Span path) + { + contentFileSystem = default; + Unsafe.SkipInit(out FsPath fullPath); + + var sb = new U8StringBuilder(fullPath.Str); + sb.Append(path) + .Append(new[] { (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }); + + if (sb.Overflowed) + return ResultFs.TooLongPath.Log(); + + Result rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool success, fullPath.Str); + if (rc.IsFailure()) return rc; + + // Reopen the host filesystem as case sensitive + if (success) + { + baseFileSystem.Dispose(); + + rc = OpenHostFileSystem(out baseFileSystem, U8Span.Empty, openCaseSensitive: true); + if (rc.IsFailure()) return rc; + } + + return _config.SubDirectoryFsCreator.Create(out contentFileSystem, ref baseFileSystem, fullPath); + } + + private Result ParseNsp(ref U8Span path, out ReferenceCountedDisposable fileSystem, + ReferenceCountedDisposable baseFileSystem) + { + fileSystem = default; + + ReadOnlySpan nspExtension = new[] { (byte)'.', (byte)'n', (byte)'s', (byte)'p' }; + + // Search for the end of the nsp part of the path + int nspPathLen = 0; + + while (true) + { + U8Span currentSpan; + + while (true) + { + currentSpan = path.Slice(nspPathLen); + if (StringUtils.CompareCaseInsensitive(nspExtension, currentSpan, 4) == 0) + break; + + if (currentSpan.Length == 0 || currentSpan[0] == 0) + { + return ResultFs.PathNotFound.Log(); + } + + nspPathLen++; + } + + // The nsp filename must be the end of the entire path or the end of a path segment + if (currentSpan.Length <= 4 || currentSpan[4] == 0 || currentSpan[4] == (byte)'/') + break; + + nspPathLen += 4; + } + + nspPathLen += 4; + + if (nspPathLen > FsPath.MaxLength + 1) + return ResultFs.TooLongPath.Log(); + + Result rc = FsPath.FromSpan(out FsPath nspPath, path.Slice(0, nspPathLen)); + if (rc.IsFailure()) return rc; + + var storage = new FileStorageBasedFileSystem(); + using var nspFileStorage = new ReferenceCountedDisposable(storage); + + rc = nspFileStorage.Target.Initialize(baseFileSystem, new U8Span(nspPath.Str), OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = _config.PartitionFsCreator.Create(out fileSystem, nspFileStorage.AddReference()); + + if (rc.IsSuccess()) + { + path = path.Slice(nspPathLen); + } + + return rc; + } + + private Result ParseNca(ref U8Span path, out Nca nca, ReferenceCountedDisposable baseFileSystem, + ulong ncaId) + { + nca = default; + + // Todo: Create ref-counted storage + var ncaFileStorage = new FileStorageBasedFileSystem(); + + Result rc = ncaFileStorage.Initialize(baseFileSystem, path, OpenMode.Read); + if (rc.IsFailure()) return rc; + + rc = _config.StorageOnNcaCreator.OpenNca(out Nca ncaTemp, ncaFileStorage); + if (rc.IsFailure()) return rc; + + if (ncaId == ulong.MaxValue) + { + ulong ncaProgramId = ncaTemp.Header.TitleId; + + if (ncaProgramId != ulong.MaxValue && ncaId != ncaProgramId) + { + return ResultFs.InvalidNcaId.Log(); + } + } + + nca = ncaTemp; + return Result.Success; + } + + private Result ParseContentTypeForDirectory(out ReferenceCountedDisposable fileSystem, + ref ReferenceCountedDisposable baseFileSystem, FileSystemProxyType fsType) + { + fileSystem = default; + ReadOnlySpan dirName; + + // Get the name of the subdirectory for the filesystem type + switch (fsType) + { + case FileSystemProxyType.Package: + fileSystem = baseFileSystem; + return Result.Success; + + case FileSystemProxyType.Code: + dirName = new[] { (byte)'/', (byte)'c', (byte)'o', (byte)'d', (byte)'e', (byte)'/' }; + break; + case FileSystemProxyType.Rom: + case FileSystemProxyType.Control: + case FileSystemProxyType.Manual: + case FileSystemProxyType.Meta: + case FileSystemProxyType.RegisteredUpdate: + dirName = new[] { (byte)'/', (byte)'d', (byte)'a', (byte)'t', (byte)'a', (byte)'/' }; + break; + case FileSystemProxyType.Logo: + dirName = new[] { (byte)'/', (byte)'l', (byte)'o', (byte)'g', (byte)'o', (byte)'/' }; + break; + + default: + return ResultFs.InvalidArgument.Log(); + } + + ReferenceCountedDisposable subDirFs = null; + try + { + // Open the subdirectory filesystem + Result rc = _config.SubDirectoryFsCreator.Create(out subDirFs, ref baseFileSystem, new U8Span(dirName)); + if (rc.IsFailure()) return rc; + + if (fsType == FileSystemProxyType.Code) + { + rc = _config.StorageOnNcaCreator.VerifyAcidSignature(subDirFs.Target, null); + if (rc.IsFailure()) return rc; + } + + Shared.Move(out fileSystem, ref subDirFs); + return Result.Success; + } + finally + { + subDirFs?.Dispose(); + } + } + + private Result SetExternalKeyForRightsId(Nca nca) + { + var rightsId = new RightsId(nca.Header.RightsId); + var zero = new RightsId(0, 0); + + if (Crypto.CryptoUtil.IsSameBytes(rightsId.AsBytes(), zero.AsBytes(), Unsafe.SizeOf())) + return Result.Success; + + // ReSharper disable once UnusedVariable + Result rc = _externalKeyManager.Get(rightsId, out AccessKey accessKey); + if (rc.IsFailure()) return rc; + + // todo: Set key in nca reader + + return Result.Success; + } + + private Result OpenStorageByContentType(out ReferenceCountedDisposable ncaStorage, Nca nca, + out NcaFormatType fsType, FileSystemProxyType fsProxyType, bool isGameCard, bool canMountSystemDataPrivate) + { + ncaStorage = default; + fsType = default; + + NcaContentType contentType = nca.Header.ContentType; + + switch (fsProxyType) + { + case FileSystemProxyType.Code: + case FileSystemProxyType.Rom: + case FileSystemProxyType.Logo: + case FileSystemProxyType.RegisteredUpdate: + if (contentType != NcaContentType.Program) + return ResultFs.PreconditionViolation.Log(); + + break; + + case FileSystemProxyType.Control: + if (contentType != NcaContentType.Control) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Manual: + if (contentType != NcaContentType.Manual) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Meta: + if (contentType != NcaContentType.Meta) + return ResultFs.PreconditionViolation.Log(); + + break; + case FileSystemProxyType.Data: + if (contentType != NcaContentType.Data && contentType != NcaContentType.PublicData) + return ResultFs.PreconditionViolation.Log(); + + if (contentType == NcaContentType.Data && !canMountSystemDataPrivate) + return ResultFs.PermissionDenied.Log(); + + break; + default: + return ResultFs.InvalidArgument.Log(); + } + + if (nca.Header.DistributionType == DistributionType.GameCard && !isGameCard) + return ResultFs.PermissionDenied.Log(); + + Result rc = SetExternalKeyForRightsId(nca); + if (rc.IsFailure()) return rc; + + rc = GetPartitionIndex(out int sectionIndex, fsProxyType); + if (rc.IsFailure()) return rc; + + rc = _config.StorageOnNcaCreator.Create(out ncaStorage, out NcaFsHeader fsHeader, nca, + sectionIndex, fsProxyType == FileSystemProxyType.Code); + if (rc.IsFailure()) return rc; + + fsType = fsHeader.FormatType; + return Result.Success; + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed encryptionSeed) + { + _encryptionSeed = encryptionSeed; + + return Result.Success; + } + + public Result ResolveRomReferenceProgramId(out ProgramId targetProgramId, ProgramId programId, + byte programIndex) + { + Unsafe.SkipInit(out targetProgramId); + + ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, programIndex); + if (mainProgramId == ProgramId.InvalidId) + return ResultFs.TargetProgramIndexNotFound.Log(); + + targetProgramId = mainProgramId; + return Result.Success; + } + + public Result ResolveProgramPath(out Path path, ProgramId programId, StorageId storageId) + { + Result rc = _locationResolverSet.ResolveProgramPath(out path, programId.Value, storageId); + if (rc.IsSuccess()) + return Result.Success; + + var dataId = new DataId(programId.Value); + + rc = _locationResolverSet.ResolveDataPath(out path, dataId, storageId); + if (rc.IsSuccess()) + return Result.Success; + + return ResultFs.TargetNotFound.Log(); + } + + public Result ResolveRomPath(out Path path, ulong id, StorageId storageId) + { + return _locationResolverSet.ResolveRomPath(out path, id, storageId); + } + + public Result ResolveApplicationHtmlDocumentPath(out Path path, Ncm.ApplicationId applicationId, + StorageId storageId) + { + return _locationResolverSet.ResolveApplicationHtmlDocumentPath(out path, applicationId, storageId); + } + + public Result ResolveRegisteredHtmlDocumentPath(out Path path, ulong id) + { + return _locationResolverSet.ResolveRegisteredHtmlDocumentPath(out path, id); + } + + internal StorageType GetStorageFlag(ulong programId) + { + if (programId >= _config.SpeedEmulationRange.ProgramIdMin && + programId <= _config.SpeedEmulationRange.ProgramIdMax) + return StorageType.Bis; + else + return StorageType.All; + } + + public Result HandleResolubleAccessFailure(out bool wasDeferred, Result resultForNoFailureDetected, + ulong processId) + { + throw new NotImplementedException(); + } + + public void IncrementRomFsRemountForDataCorruptionCount() + { + lock (_romfsCountLocker) + { + _romFsRemountForDataCorruptionCount++; + } + } + + public void IncrementRomFsUnrecoverableDataCorruptionByRemountCount() + { + lock (_romfsCountLocker) + { + _romfsUnrecoverableDataCorruptionByRemountCount++; + } + } + + public void IncrementRomFsRecoveredByInvalidateCacheCount() + { + lock (_romfsCountLocker) + { + _romFsRecoveredByInvalidateCacheCount++; + } + } + + public void GetAndClearRomFsErrorInfo(out int recoveredByRemountCount, out int unrecoverableCount, + out int recoveredByCacheInvalidationCount) + { + lock (_romfsCountLocker) + { + recoveredByRemountCount = _romFsRemountForDataCorruptionCount; + unrecoverableCount = _romfsUnrecoverableDataCorruptionByRemountCount; + recoveredByCacheInvalidationCount = _romFsRecoveredByInvalidateCacheCount; + + _romFsRemountForDataCorruptionCount = 0; + _romfsUnrecoverableDataCorruptionByRemountCount = 0; + _romFsRecoveredByInvalidateCacheCount = 0; + } + } + + public Result OpenHostFileSystem(out ReferenceCountedDisposable fileSystem, U8Span path, bool openCaseSensitive) + { + fileSystem = default; + 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(); + } + } + + internal Result GetProgramInfoByProcessId(out ProgramInfo programInfo, ulong processId) + { + return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + + internal Result GetProgramInfoByProgramId(out ProgramInfo programInfo, ulong programId) + { + return _config.ProgramRegistry.GetProgramInfoByProgramId(out programInfo, programId); + } + + private Result GetPartitionIndex(out int index, FileSystemProxyType fspType) + { + switch (fspType) + { + case FileSystemProxyType.Code: + case FileSystemProxyType.Control: + case FileSystemProxyType.Manual: + case FileSystemProxyType.Meta: + case FileSystemProxyType.Data: + index = 0; + return Result.Success; + case FileSystemProxyType.Rom: + case FileSystemProxyType.RegisteredUpdate: + index = 1; + return Result.Success; + case FileSystemProxyType.Logo: + index = 2; + return Result.Success; + default: + index = default; + return ResultFs.InvalidArgument.Log(); + } + } + + private static ReadOnlySpan SdCardNintendoRootDirectoryName => // Nintendo + new[] + { + (byte) 'N', (byte) 'i', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 'd', (byte) 'o' + }; + + private static ReadOnlySpan ContentStorageDirectoryName => // Contents + new[] + { + (byte) 'C', (byte) 'o', (byte) 'n', (byte) 't', (byte) 'e', (byte) 'n', (byte) 't', (byte) 's' + }; + } + + public readonly struct InternalProgramIdRangeForSpeedEmulation + { + public readonly ulong ProgramIdMin; + public readonly ulong ProgramIdMax; + + public InternalProgramIdRangeForSpeedEmulation(ulong min, ulong max) + { + ProgramIdMin = min; + ProgramIdMax = max; + } + } +} diff --git a/src/LibHac/FsSrv/PathNormalizer.cs b/src/LibHac/FsSrv/PathNormalizer.cs index 8e9f50a9..1a28830f 100644 --- a/src/LibHac/FsSrv/PathNormalizer.cs +++ b/src/LibHac/FsSrv/PathNormalizer.cs @@ -69,7 +69,7 @@ namespace LibHac.FsSrv PreserveUnc = (1 << 0), PreserveTailSeparator = (1 << 1), HasMountName = (1 << 2), - AcceptEmpty = (1 << 3), + AcceptEmpty = (1 << 3) } } } diff --git a/src/LibHac/FsSrv/Permissions.cs b/src/LibHac/FsSrv/Permissions.cs deleted file mode 100644 index abe9150a..00000000 --- a/src/LibHac/FsSrv/Permissions.cs +++ /dev/null @@ -1,169 +0,0 @@ -using System; - -namespace LibHac.FsSrv -{ - /// - /// Permissions that control which filesystems or storages can be mounted or opened. - /// - public enum AccessPermissions - { - MountLogo = 0x0, - MountContentMeta = 0x1, - MountContentControl = 0x2, - MountContentManual = 0x3, - MountContentData = 0x4, - MountApplicationPackage = 0x5, - MountSaveDataStorage = 0x6, - MountContentStorage = 0x7, - MountImageAndVideoStorage = 0x8, - MountCloudBackupWorkStorage = 0x9, - MountCustomStorage = 0xA, - MountBisCalibrationFile = 0xB, - MountBisSafeMode = 0xC, - MountBisUser = 0xD, - MountBisSystem = 0xE, - MountBisSystemProperEncryption = 0xF, - MountBisSystemProperPartition = 0x10, - MountSdCard = 0x11, - MountGameCard = 0x12, - MountDeviceSaveData = 0x13, - MountSystemSaveData = 0x14, - MountOthersSaveData = 0x15, - MountOthersSystemSaveData = 0x16, - OpenBisPartitionBootPartition1Root = 0x17, - OpenBisPartitionBootPartition2Root = 0x18, - OpenBisPartitionUserDataRoot = 0x19, - OpenBisPartitionBootConfigAndPackage2Part1 = 0x1A, - OpenBisPartitionBootConfigAndPackage2Part2 = 0x1B, - OpenBisPartitionBootConfigAndPackage2Part3 = 0x1C, - OpenBisPartitionBootConfigAndPackage2Part4 = 0x1D, - OpenBisPartitionBootConfigAndPackage2Part5 = 0x1E, - OpenBisPartitionBootConfigAndPackage2Part6 = 0x1F, - OpenBisPartitionCalibrationBinary = 0x20, - OpenBisPartitionCalibrationFile = 0x21, - OpenBisPartitionSafeMode = 0x22, - OpenBisPartitionUser = 0x23, - OpenBisPartitionSystem = 0x24, - OpenBisPartitionSystemProperEncryption = 0x25, - OpenBisPartitionSystemProperPartition = 0x26, - OpenSdCardStorage = 0x27, - OpenGameCardStorage = 0x28, - MountSystemDataPrivate = 0x29, - MountHost = 0x2A, - MountRegisteredUpdatePartition = 0x2B, - MountSaveDataInternalStorage = 0x2C, - NotMountCustomStorage = 0x2D - } - - /// - /// Permissions that control which actions can be performed. - /// - public enum ActionPermissions - { - // Todo - } - - public static class PermissionUtils - { - public static ulong GetPermissionMask(AccessPermissions id) - { - switch (id) - { - case AccessPermissions.MountLogo: - return 0x8000000000000801; - case AccessPermissions.MountContentMeta: - return 0x8000000000000801; - case AccessPermissions.MountContentControl: - return 0x8000000000000801; - case AccessPermissions.MountContentManual: - return 0x8000000000000801; - case AccessPermissions.MountContentData: - return 0x8000000000000801; - case AccessPermissions.MountApplicationPackage: - return 0x8000000000000801; - case AccessPermissions.MountSaveDataStorage: - return 0x8000000000000000; - case AccessPermissions.MountContentStorage: - return 0x8000000000000800; - case AccessPermissions.MountImageAndVideoStorage: - return 0x8000000000001000; - case AccessPermissions.MountCloudBackupWorkStorage: - return 0x8000000200000000; - case AccessPermissions.MountCustomStorage: - return 0x8000000000000000; - case AccessPermissions.MountBisCalibrationFile: - return 0x8000000000000084; - case AccessPermissions.MountBisSafeMode: - return 0x8000000000000080; - case AccessPermissions.MountBisUser: - return 0x8000000000008080; - case AccessPermissions.MountBisSystem: - return 0x8000000000008080; - case AccessPermissions.MountBisSystemProperEncryption: - return 0x8000000000000080; - case AccessPermissions.MountBisSystemProperPartition: - return 0x8000000000000080; - case AccessPermissions.MountSdCard: - return 0xC000000000200000; - case AccessPermissions.MountGameCard: - return 0x8000000000000010; - case AccessPermissions.MountDeviceSaveData: - return 0x8000000000040020; - case AccessPermissions.MountSystemSaveData: - return 0x8000000000000028; - case AccessPermissions.MountOthersSaveData: - return 0x8000000000000020; - case AccessPermissions.MountOthersSystemSaveData: - return 0x8000000000000020; - case AccessPermissions.OpenBisPartitionBootPartition1Root: - return 0x8000000000010082; - case AccessPermissions.OpenBisPartitionBootPartition2Root: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionUserDataRoot: - return 0x8000000000000080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part1: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part2: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part3: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part4: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part5: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionBootConfigAndPackage2Part6: - return 0x8000000000010080; - case AccessPermissions.OpenBisPartitionCalibrationBinary: - return 0x8000000000000084; - case AccessPermissions.OpenBisPartitionCalibrationFile: - return 0x8000000000000084; - case AccessPermissions.OpenBisPartitionSafeMode: - return 0x8000000000000080; - case AccessPermissions.OpenBisPartitionUser: - return 0x8000000000000080; - case AccessPermissions.OpenBisPartitionSystem: - return 0x8000000000000080; - case AccessPermissions.OpenBisPartitionSystemProperEncryption: - return 0x8000000000000080; - case AccessPermissions.OpenBisPartitionSystemProperPartition: - return 0x8000000000000080; - case AccessPermissions.OpenSdCardStorage: - return 0xC000000000200000; - case AccessPermissions.OpenGameCardStorage: - return 0x8000000000000100; - case AccessPermissions.MountSystemDataPrivate: - return 0x8000000000100008; - case AccessPermissions.MountHost: - return 0xC000000000400000; - case AccessPermissions.MountRegisteredUpdatePartition: - return 0x8000000000010000; - case AccessPermissions.MountSaveDataInternalStorage: - return 0x8000000000000000; - case AccessPermissions.NotMountCustomStorage: - return 0x0; - default: - throw new ArgumentOutOfRangeException(nameof(id), id, null); - } - } - } -} diff --git a/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs b/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs index d918f36e..d334e864 100644 --- a/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs +++ b/src/LibHac/FsSrv/ProgramIndexMapInfoManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using LibHac.Fs; using LibHac.Ncm; +using LibHac.Util; namespace LibHac.FsSrv { @@ -50,7 +51,7 @@ namespace LibHac.FsSrv /// The program ID of the map info to get. /// If the program ID was found, the associated /// with that ID; otherwise, . - public ProgramIndexMapInfo? Get(ProgramId programId) + public Optional Get(ProgramId programId) { lock (MapEntries) { @@ -70,12 +71,13 @@ namespace LibHac.FsSrv { lock (MapEntries) { - ProgramIndexMapInfo? mainProgramMapInfo = GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); + Optional mainProgramMapInfo = + GetImpl((in ProgramIndexMapInfo x) => x.ProgramId == programId); if(!mainProgramMapInfo.HasValue) return ProgramId.InvalidId; - ProgramIndexMapInfo? requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) => + Optional requestedMapInfo = GetImpl((in ProgramIndexMapInfo x) => x.MainProgramId == mainProgramMapInfo.Value.MainProgramId && x.ProgramIndex == programIndex); if (!requestedMapInfo.HasValue) @@ -99,17 +101,17 @@ namespace LibHac.FsSrv private delegate bool EntrySelector(in ProgramIndexMapInfo candidate); - private ProgramIndexMapInfo? GetImpl(EntrySelector selector) + private Optional GetImpl(EntrySelector selector) { foreach (ProgramIndexMapInfo entry in MapEntries) { if (selector(in entry)) { - return entry; + return new Optional(entry); } } - return null; + return new Optional(); } private void ClearImpl() diff --git a/src/LibHac/FsSrv/ProgramRegistryService.cs b/src/LibHac/FsSrv/ProgramIndexRegistryService.cs similarity index 85% rename from src/LibHac/FsSrv/ProgramRegistryService.cs rename to src/LibHac/FsSrv/ProgramIndexRegistryService.cs index dc1dac89..e5f14143 100644 --- a/src/LibHac/FsSrv/ProgramRegistryService.cs +++ b/src/LibHac/FsSrv/ProgramIndexRegistryService.cs @@ -3,21 +3,23 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using LibHac.Fs; using LibHac.FsSrv.Impl; +using LibHac.Sf; +using LibHac.Util; namespace LibHac.FsSrv { /// - /// Used to perform operations on the Program Index Map. + /// Used to perform operations on the program index registry. /// /// Appropriate methods calls on IFileSystemProxy are forwarded to this class /// which then checks the calling process' permissions and performs the requested operation. ///
Based on FS 10.0.0 (nnSdk 10.4.0)
- internal readonly struct ProgramRegistryService + internal readonly struct ProgramIndexRegistryService { private ProgramRegistryServiceImpl ServiceImpl { get; } private ulong ProcessId { get; } - public ProgramRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId) + public ProgramIndexRegistryService(ProgramRegistryServiceImpl serviceImpl, ulong processId) { ServiceImpl = serviceImpl; ProcessId = processId; @@ -33,7 +35,7 @@ namespace LibHac.FsSrv /// : Insufficient permissions.
/// : The buffer was too small to hold the specified /// number of entries. - public Result RegisterProgramIndexMapInfo(ReadOnlySpan programIndexMapInfo, int programCount) + public Result RegisterProgramIndexMapInfo(InBuffer programIndexMapInfo, int programCount) { // Verify the caller's permissions Result rc = GetProgramInfo(out ProgramInfo programInfo, ProcessId); @@ -49,7 +51,7 @@ namespace LibHac.FsSrv // Verify that the provided buffer is large enough to hold "programCount" entries ReadOnlySpan - mapInfo = MemoryMarshal.Cast(programIndexMapInfo); + mapInfo = MemoryMarshal.Cast(programIndexMapInfo.Buffer); if (mapInfo.Length < programCount) return ResultFs.InvalidSize.Log(); @@ -69,7 +71,7 @@ namespace LibHac.FsSrv /// : The operation was successful.
/// : The calling program was not found /// in the program registry. Something's wrong with Loader if this happens.
- public Result GetProgramIndexForAccessLog(out int programIndex, out int programCount) + public Result GetProgramIndex(out int programIndex, out int programCount) { Unsafe.SkipInit(out programIndex); Unsafe.SkipInit(out programCount); @@ -79,10 +81,10 @@ namespace LibHac.FsSrv if (rc.IsFailure()) return rc; // Try to get map info for this process - ProgramIndexMapInfo? mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); + Optional mapInfo = ServiceImpl.GetProgramIndexMapInfo(programInfo.ProgramId); // Set the output program index if map info was found - programIndex = mapInfo?.ProgramIndex ?? 0; + programIndex = mapInfo.HasValue ? mapInfo.ValueRo.ProgramIndex : 0; // Set the number of programs in the current application programCount = ServiceImpl.GetProgramIndexMapInfoCount(); diff --git a/src/LibHac/FsSrv/ProgramRegistryImpl.cs b/src/LibHac/FsSrv/ProgramRegistryImpl.cs index 7946b915..04bd8bf0 100644 --- a/src/LibHac/FsSrv/ProgramRegistryImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryImpl.cs @@ -1,8 +1,8 @@ -using System; -using LibHac.Fs; +using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.FsSrv.Sf; using LibHac.Ncm; +using LibHac.Sf; namespace LibHac.FsSrv { @@ -14,7 +14,7 @@ namespace LibHac.FsSrv /// storage location and file system permissions. This allows FS to resolve the program ID and /// verify the permissions of any process calling it. ///
Based on FS 10.0.0 (nnSdk 10.4.0) - internal class ProgramRegistryImpl : IProgramRegistry + public class ProgramRegistryImpl : IProgramRegistry { private ulong _processId; @@ -38,13 +38,13 @@ namespace LibHac.FsSrv /// : Insufficient permissions. /// public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) + InBuffer accessControlData, InBuffer accessControlDescriptor) { if (!ProgramInfo.IsInitialProgram(_processId)) return ResultFs.PermissionDenied.Log(); - return _registryService.RegisterProgram(processId, programId, storageId, accessControlData, - accessControlDescriptor); + return _registryService.RegisterProgramInfo(processId, programId, storageId, accessControlData.Buffer, + accessControlDescriptor.Buffer); } /// : The operation was successful.
@@ -56,7 +56,7 @@ namespace LibHac.FsSrv if (!ProgramInfo.IsInitialProgram(_processId)) return ResultFs.PermissionDenied.Log(); - return _registryService.UnregisterProgram(processId); + return _registryService.UnregisterProgramInfo(processId); } /// diff --git a/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs b/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs index b7d7e8aa..617f945c 100644 --- a/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs +++ b/src/LibHac/FsSrv/ProgramRegistryServiceImpl.cs @@ -2,6 +2,7 @@ using LibHac.Fs; using LibHac.FsSrv.Impl; using LibHac.Ncm; +using LibHac.Util; namespace LibHac.FsSrv { @@ -21,7 +22,7 @@ namespace LibHac.FsSrv } /// - public Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, + public Result RegisterProgramInfo(ulong processId, ProgramId programId, StorageId storageId, ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor) { return RegistryManager.RegisterProgram(processId, programId, storageId, accessControlData, @@ -29,7 +30,7 @@ namespace LibHac.FsSrv } /// - public Result UnregisterProgram(ulong processId) + public Result UnregisterProgramInfo(ulong processId) { return RegistryManager.UnregisterProgram(processId); } @@ -47,13 +48,13 @@ namespace LibHac.FsSrv } /// - public ProgramId GetProgramId(ProgramId programId, byte programIndex) + public ProgramId GetProgramIdByIndex(ProgramId programId, byte programIndex) { return ProgramIndexManager.GetProgramId(programId, programIndex); } /// - public ProgramIndexMapInfo? GetProgramIndexMapInfo(ProgramId programId) + public Optional GetProgramIndexMapInfo(ProgramId programId) { return ProgramIndexManager.Get(programId); } diff --git a/src/LibHac/FsSrv/SaveDataFileSystemService.cs b/src/LibHac/FsSrv/SaveDataFileSystemService.cs new file mode 100644 index 00000000..e334fb32 --- /dev/null +++ b/src/LibHac/FsSrv/SaveDataFileSystemService.cs @@ -0,0 +1,1941 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSrv.Impl; +using LibHac.FsSrv.Sf; +using LibHac.FsSystem; +using LibHac.Kvdb; +using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Util; +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 Utility = LibHac.FsSystem.Utility; + +namespace LibHac.FsSrv +{ + /// + /// Handles save-data-related calls for . + /// + /// FS will have one instance of this class for every connected process. + /// The FS permissions of the calling process are checked on every function call. + ///
Based on FS 10.2.0 (nnSdk 10.6.0)
+ internal class SaveDataFileSystemService : ISaveDataTransferCoreInterface, ISaveDataMultiCommitCoreInterface + { + private const int OpenEntrySemaphoreCount = 256; + private const int SaveMountSemaphoreCount = 10; + + 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 SemaphoreAdaptor OpenEntryCountSemaphore { get; } + private SemaphoreAdaptor SaveDataMountCountSemaphore { get; } + + private HorizonClient Hos => ServiceImpl.Hos; + + public SaveDataFileSystemService(SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + { + ServiceImpl = serviceImpl; + ProcessId = processId; + OpenEntryCountSemaphore = new SemaphoreAdaptor(OpenEntrySemaphoreCount, OpenEntrySemaphoreCount); + SaveDataMountCountSemaphore = new SemaphoreAdaptor(SaveMountSemaphoreCount, SaveMountSemaphoreCount); + } + + public static ReferenceCountedDisposable CreateShared( + SaveDataFileSystemServiceImpl serviceImpl, ulong processId) + { + // Create the service + var saveService = new SaveDataFileSystemService(serviceImpl, processId); + + // Wrap the service in a ref-counter and give the service a weak self-reference + var sharedService = new ReferenceCountedDisposable(saveService); + saveService.SelfReference = + new ReferenceCountedDisposable.WeakReference(sharedService); + + return sharedService; + } + + private class SaveDataOpenCountAdapter : IEntryOpenCountSemaphoreManager + { + private ReferenceCountedDisposable _saveService; + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable saveService) + { + var adapter = new SaveDataOpenCountAdapter(); + Shared.Move(out adapter._saveService, ref saveService); + + return new ReferenceCountedDisposable(adapter); + } + + public Result TryAcquireEntryOpenCountSemaphore(out IUniqueLock semaphore) + { + return _saveService.Target.TryAcquireSaveDataEntryOpenCountSemaphore(out semaphore); + } + + public void Dispose() + { + _saveService?.Dispose(); + _saveService = null; + } + } + + private Result CheckOpenSaveDataInfoReaderAccessControl(ProgramInfo programInfo, SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + return ResultFs.PermissionDenied.Log(); + break; + case SaveDataSpaceId.User: + case SaveDataSpaceId.Temporary: + case SaveDataSpaceId.SdCache: + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader)) + return ResultFs.PermissionDenied.Log(); + break; + default: + return ResultFs.InvalidSaveDataSpaceId.Log(); + } + + return Result.Success; + } + + private static class SaveDataAccessibilityChecker + { + public delegate Result ExtraDataGetter(out SaveDataExtraData extraData); + + public static Result CheckCreate(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + ProgramInfo programInfo, ProgramId programId) + { + AccessControl accessControl = programInfo.AccessControl; + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + if (creationInfo.OwnerId == programInfo.ProgramIdValue) + { + bool canAccess = accessControl.CanCall(OperationType.CreateSystemSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else + { + // If the program doesn't own the created save data it needs either the permission to create + // any system save data or it needs explicit access to the owner's save data. + Accessibility accessibility = + accessControl.GetAccessibilitySaveDataOwnedBy(creationInfo.OwnerId); + + bool canAccess = + accessControl.CanCall(OperationType.CreateSystemSaveData) && + accessControl.CanCall(OperationType.CreateOthersSystemSaveData) || accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + } + else if (attribute.Type == SaveDataType.Account && attribute.UserId == UserId.InvalidId) + { + bool canAccess = + accessControl.CanCall(OperationType.CreateSaveData) || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else + { + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + creationInfo.OwnerId); + if (rc.IsFailure()) return rc; + + // If none of the above conditions apply, the program needs write access to the owner's save data. + // The program also needs either permission to create any save data, or it must be creating its own + // save data and have the permission to do so. + bool canAccess = accessControl.CanCall(OperationType.CreateSaveData); + + if (accessibility.CanWrite && + attribute.ProgramId == programId || attribute.ProgramId.Value == creationInfo.OwnerId) + { + canAccess |= accessControl.CanCall(OperationType.CreateOwnSaveData); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckOpenPre(in SaveDataAttribute attribute, ProgramInfo programInfo) + { + AccessControl accessControl = programInfo.AccessControl; + + if (attribute.Type == SaveDataType.Device) + { + Accessibility accessibility = + accessControl.GetAccessibilityFor(AccessibilityType.MountDeviceSaveData); + + bool canAccess = accessibility.CanRead && accessibility.CanWrite; + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + else if (attribute.Type == SaveDataType.Account) + { + bool canAccess = attribute.UserId != UserId.InvalidId || + accessControl.CanCall(OperationType.DebugSaveData); + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + } + + return Result.Success; + } + + public static Result CheckOpen(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); + if (rc.IsFailure()) return rc; + + if (accessibility.CanRead && accessibility.CanWrite) + return Result.Success; + + // The program doesn't have permissions for this specific save data. Check if it has overall + // permissions for other programs' save data. + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSystemSaveData); + } + else + { + accessibility = accessControl.GetAccessibilityFor(AccessibilityType.MountOthersSaveData); + } + + if (accessibility.CanRead && accessibility.CanWrite) + return Result.Success; + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckDelete(in SaveDataAttribute attribute, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + // DeleteSystemSaveData permission is needed to delete system save data + if (SaveDataProperties.IsSystemSaveData(attribute.Type) && + !accessControl.CanCall(OperationType.DeleteSystemSaveData)) + { + return ResultFs.PermissionDenied.Log(); + } + + // The DeleteSaveData permission allows deleting any non-system save data + if (accessControl.CanCall(OperationType.DeleteSaveData)) + { + return Result.Success; + } + + // Otherwise the program needs the DeleteOwnSaveData permission and write access to the save + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, extraDataGetter); + if (rc.IsFailure()) return rc; + + if (accessControl.CanCall(OperationType.DeleteOwnSaveData) && accessibility.CanWrite) + { + return Result.Success; + } + + return ResultFs.PermissionDenied.Log(); + } + + public static Result CheckReadExtraData(in SaveDataAttribute attribute, in SaveDataExtraData mask, + ProgramInfo programInfo, ExtraDataGetter extraDataGetter) + { + AccessControl accessControl = programInfo.AccessControl; + + bool canAccess = accessControl.CanCall(OperationType.ReadSaveDataFileSystemExtraData); + + Result rc = GetAccessibilityForSaveData(out Accessibility accessibility, programInfo, + extraDataGetter); + if (rc.IsFailure()) return rc; + + SaveDataExtraData emptyMask = default; + SaveDataExtraData maskWithoutRestoreFlag = mask; + maskWithoutRestoreFlag.Flags &= ~SaveDataFlags.Restore; + + // Only read access to the save is needed to read the restore flag + if (SpanHelpers.AsReadOnlyByteSpan(in emptyMask) + .SequenceEqual(SpanHelpers.AsReadOnlyByteSpan(in maskWithoutRestoreFlag))) + { + canAccess |= accessibility.CanRead; + } + + if (SaveDataProperties.IsSystemSaveData(attribute.Type)) + { + canAccess |= accessibility.CanRead; + } + else if (attribute.ProgramId == programInfo.ProgramId) + { + canAccess |= accessControl.CanCall(OperationType.ReadOwnSaveDataFileSystemExtraData); + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + public static Result CheckFind(in SaveDataFilter filter, ProgramInfo programInfo) + { + bool canAccess; + + if (programInfo.ProgramId == filter.Attribute.ProgramId) + { + canAccess = programInfo.AccessControl.CanCall(OperationType.FindOwnSaveDataWithFilter); + } + else + { + canAccess = true; + } + + if (!canAccess) + return ResultFs.PermissionDenied.Log(); + + return Result.Success; + } + + + private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, + ExtraDataGetter extraDataGetter) + { + Unsafe.SkipInit(out accessibility); + + Result rc = extraDataGetter(out SaveDataExtraData extraData); + if (rc.IsFailure()) + { + if (ResultFs.TargetNotFound.Includes(rc)) + { + accessibility = new Accessibility(false, false); + return Result.Success; + } + + return rc; + } + + if (extraData.OwnerId == 0 && extraData.DataSize == 0 && extraData.JournalSize == 0 && + programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + { + accessibility = new Accessibility(true, true); + return Result.Success; + } + + return GetAccessibilityForSaveData(out accessibility, programInfo, extraData.OwnerId); + } + + private static Result GetAccessibilityForSaveData(out Accessibility accessibility, ProgramInfo programInfo, + ulong ownerId) + { + if (ownerId == programInfo.ProgramIdValue) + { + // A program always has full access to its own save data + accessibility = new Accessibility(true, true); + } + else + { + accessibility = programInfo.AccessControl.GetAccessibilitySaveDataOwnedBy(ownerId); + } + + return Result.Success; + } + } + + public Result GetFreeSpaceSizeForSaveData(out long freeSpaceSize, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result RegisterSaveDataFileSystemAtomicDeletion(InBuffer saveDataIds) + { + throw new NotImplementedException(); + } + + public Result DeleteSaveDataFileSystem(ulong saveDataId) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + return DeleteSaveDataFileSystemCommon(SaveDataSpaceId.System, saveDataId); + } + + private Result DeleteSaveDataFileSystemCore(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile) + { + // Delete the save data's meta files + 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); + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) + return rc; + + return Result.Success; + } + + public Result DeleteSaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, saveDataId); + } + + private Result DeleteSaveDataFileSystemBySaveDataSpaceIdCore(SaveDataSpaceId spaceId, ulong saveDataId) + { + if (saveDataId != FileSystemServer.SaveIndexerId) + { + SaveDataIndexerAccessor accessor = null; + try + { + Result rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsFailure()) return rc; + + if (value.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + } + finally + { + accessor?.Dispose(); + } + } + + return DeleteSaveDataFileSystemCommon(spaceId, saveDataId); + } + + public Result DeleteSaveDataFileSystemBySaveDataAttribute(SaveDataSpaceId spaceId, + in SaveDataAttribute attribute) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rs = GetSaveDataInfo(out SaveDataInfo info, spaceId, in attribute); + if (rs.IsFailure()) return rs; + + return DeleteSaveDataFileSystemBySaveDataSpaceIdCore(spaceId, info.SaveDataId); + } + + private Result DeleteSaveDataFileSystemCommon(SaveDataSpaceId spaceId, ulong saveDataId) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataIndexerAccessor accessor = null; + + try + { + SaveDataSpaceId actualSpaceId; + + // Only the FS process may delete the save indexer's save data. + if (saveDataId == FileSystemServer.SaveIndexerId) + { + if (!IsCurrentProcess(ProcessId)) + return ResultFs.PermissionDenied.Log(); + + actualSpaceId = spaceId; + } + else + { + rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + // Get the actual space ID of this save. + if (spaceId == SaveDataSpaceId.ProperSystem && spaceId == SaveDataSpaceId.SafeMode) + { + actualSpaceId = spaceId; + } + else + { + rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + if (rc.IsFailure()) return rc; + + actualSpaceId = value.SpaceId; + } + + // Check if the caller has permission to delete this save. + rc = accessor.Indexer.GetKey(out SaveDataAttribute key, saveDataId); + if (rc.IsFailure()) return rc; + + Result GetExtraData(out SaveDataExtraData data) => + ServiceImpl.ReadSaveDataFileSystemExtraData(out data, actualSpaceId, saveDataId, key.Type, + SaveDataRootPath); + + rc = SaveDataAccessibilityChecker.CheckDelete(in key, programInfo, GetExtraData); + if (rc.IsFailure()) return rc; + + // Pre-delete checks successful. Put the save in the Processing state until deletion is finished. + rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Processing); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + // Do the actual deletion. + rc = DeleteSaveDataFileSystemCore(actualSpaceId, saveDataId, false); + if (rc.IsFailure()) return rc; + + // Remove the save data from the indexer. + // The indexer doesn't track itself, so skip if deleting its save data. + if (saveDataId != FileSystemServer.SaveIndexerId) + { + // accessor will never be null at this point + rc = accessor!.Indexer.Delete(saveDataId); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + accessor?.Dispose(); + } + } + + public Result SwapSaveDataKeyAndState(SaveDataSpaceId spaceId, ulong saveDataId1, ulong saveDataId2) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataState(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataState state) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataRank(SaveDataSpaceId spaceId, ulong saveDataId, SaveDataRank rank) + { + throw new NotImplementedException(); + } + + public Result FinalizeSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result CancelSaveDataCreation(ulong saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + public Result SetSaveDataRootPath(in FspPath path) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + return ResultFs.PermissionDenied.Log(); + + if (StringUtils.GetLength(path.Str, PathTool.EntryNameLengthMax + 1) > PathTool.EntryNameLengthMax) + return ResultFs.TooLongPath.Log(); + + 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) + { + for (int i = 0; SaveDataRootPath.Str[i] == '/'; i++) + { + SaveDataRootPath.Str[i] = (byte)'\\'; + } + } + + var normalizer = new PathNormalizer(SaveDataRootPath, PathNormalizer.Option.PreserveUnc); + if (normalizer.Result.IsFailure()) + return normalizer.Result; + + StringUtils.Copy(SaveDataRootPath.Str, normalizer.Path); + return Result.Success; + } + + public Result UnsetSaveDataRootPath() + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.DebugSaveData)) + return ResultFs.PermissionDenied.Log(); + + SaveDataRootPath.Str.Clear(); + return Result.Success; + } + + // ReSharper disable once UnusedParameter.Global + public Result UpdateSaveDataMacForDebug(SaveDataSpaceId spaceId, ulong saveDataId) + { + if (saveDataId == FileSystemServer.SaveIndexerId) + return ResultFs.InvalidArgument.Log(); + + return ResultFs.NotImplemented.Log(); + } + + public Result OpenSaveDataFile(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType metaType) + { + throw new NotImplementedException(); + } + + public Result CheckSaveDataFile(long saveDataId, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo, in Optional hashSalt) + { + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, false); + } + + private Result CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo, in Optional hashSalt, bool leaveUnfinalized) + { + ulong saveDataId = 0; + bool creating = false; + Result rc; + + SaveDataIndexerAccessor accessor = null; + + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + try + { + // Add the new save data to the save indexer + if (attribute.StaticSaveDataId == FileSystemServer.SaveIndexerId) + { + // The save indexer doesn't index itself + saveDataId = FileSystemServer.SaveIndexerId; + rc = ServiceImpl.DoesSaveDataEntityExist(out bool saveExists, creationInfo.SpaceId, saveDataId); + + if (rc.IsSuccess() && saveExists) + { + return ResultFs.PathAlreadyExists.Log(); + } + + creating = true; + } + else + { + rc = OpenSaveDataIndexerAccessor(out accessor, creationInfo.SpaceId); + if (rc.IsFailure()) return rc; + + SaveDataAttribute indexerKey = attribute; + + // Add the new value to the indexer + if (attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId) + { + // If a static save data ID is specified that ID is always used + saveDataId = attribute.StaticSaveDataId; + + rc = accessor.Indexer.PutStaticSaveDataIdIndex(in indexerKey); + } + else + { + // The save indexer has an upper limit on the number of entries it can hold. + // A few of those entries are reserved for system saves so the system doesn't + // end up in a situation where it can't create a required system save. + if (!SaveDataProperties.CanUseIndexerReservedArea(attribute.Type)) + { + if (accessor.Indexer.IsRemainedReservedOnly()) + { + return ResultKvdb.OutOfKeyResource.Log(); + } + } + + // If a static save data ID is no specified we're assigned a new save ID + rc = accessor.Indexer.Publish(out saveDataId, in indexerKey); + } + + if (rc.IsFailure()) + { + if (ResultFs.SaveDataPathAlreadyExists.Includes(rc)) + { + return ResultFs.PathAlreadyExists.LogConverted(rc); + } + + return rc; + } + + creating = true; + + // Set the state, space ID and size on the new save indexer entry. + rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Processing); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.SetSpaceId(saveDataId, ConvertToRealSpaceId(creationInfo.SpaceId)); + if (rc.IsFailure()) return rc; + + rc = QuerySaveDataTotalSize(out long saveDataSize, creationInfo.Size, creationInfo.JournalSize); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.SetSize(saveDataId, saveDataSize); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + // 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); + + if (rc.IsFailure()) + { + if (!ResultFs.PathAlreadyExists.Includes(rc)) return rc; + + // Handle the situation where a save exists on disk but not in the save indexer. + // Delete the save data and try creating it again. + rc = DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false); + if (rc.IsFailure()) return rc; + + rc = ServiceImpl.CreateSaveDataFileSystem(saveDataId, in attribute, in creationInfo, + 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, + metaInfo.Size); + if (rc.IsFailure()) return rc; + + if (metaInfo.Type == SaveDataMetaType.Thumbnail) + { + rc = ServiceImpl.OpenSaveDataMeta(out IFile metaFile, saveDataId, creationInfo.SpaceId, + metaInfo.Type); + + using (metaFile) + { + if (rc.IsFailure()) return rc; + + // The first 0x20 bytes of thumbnail meta files is an SHA-256 hash. + // Zero the hash to indicate that it's currently unused. + ReadOnlySpan metaFileHash = stackalloc byte[0x20]; + + rc = metaFile.Write(0, metaFileHash, WriteOption.Flush); + if (rc.IsFailure()) return rc; + } + } + } + + if (leaveUnfinalized) + { + creating = false; + return Result.Success; + } + + // The indexer's save data isn't tracked, so we don't need to update its state. + if (attribute.StaticSaveDataId != FileSystemServer.SaveIndexerId) + { + // accessor shouldn't ever be null, but checking makes the analyzers happy + Abort.DoAbortUnless(accessor != null); + + // Mark the save data as being successfully created + rc = accessor.Indexer.SetState(saveDataId, SaveDataState.Normal); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.Commit(); + if (rc.IsFailure()) return rc; + } + + creating = false; + return Result.Success; + } + finally + { + // Revert changes if an error happened in the middle of creation + if (creating) + { + DeleteSaveDataFileSystemCore(creationInfo.SpaceId, saveDataId, false).IgnoreResult(); + + if (accessor != null && saveDataId != FileSystemServer.SaveIndexerId) + { + rc = accessor.Indexer.GetValue(out SaveDataIndexerValue value, saveDataId); + + if (rc.IsSuccess() && value.SpaceId == creationInfo.SpaceId) + { + accessor.Indexer.Delete(saveDataId).IgnoreResult(); + accessor.Indexer.Commit().IgnoreResult(); + } + } + } + + accessor?.Dispose(); + + } + } + + public Result GetSaveDataInfo(out SaveDataInfo info, SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + Unsafe.SkipInit(out info); + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, spaceId); + if (rc.IsFailure()) return rc; + + using (accessor) + { + rc = accessor.Indexer.Get(out SaveDataIndexerValue value, in attribute); + if (rc.IsFailure()) return rc; + + SaveDataIndexer.GenerateSaveDataInfo(out info, in attribute, in value); + return Result.Success; + } + } + + public Result QuerySaveDataTotalSize(out long totalSize, long dataSize, long journalSize) + { + Unsafe.SkipInit(out totalSize); + + if (dataSize < 0 || journalSize < 0) + return ResultFs.InvalidSize.Log(); + + return ServiceImpl.QuerySaveDataTotalSize(out totalSize, SaveDataBlockSize, dataSize, journalSize); + } + + public Result CreateSaveDataFileSystem(in SaveDataAttribute attribute, in SaveDataCreationInfo creationInfo, + in SaveDataMetaInfo metaInfo) + { + Optional hashSalt = default; + + return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, in hashSalt); + } + + public Result CreateSaveDataFileSystemWithHashSalt(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in HashSalt hashSalt) + { + var optionalHashSalt = new Optional(in hashSalt); + + return CreateSaveDataFileSystemWithHashSaltImpl(in attribute, in creationInfo, in metaInfo, + in optionalHashSalt); + } + + private Result CreateSaveDataFileSystemWithHashSaltImpl(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt) + { + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute = attribute; + SaveDataCreationInfo tempCreationInfo = creationInfo; + + if (hashSalt.HasValue && !programInfo.AccessControl.CanCall(OperationType.CreateSaveDataWithHashSalt)) + { + return ResultFs.PermissionDenied.Log(); + } + + ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in creationInfo, programInfo, programId); + if (rc.IsFailure()) return rc; + + if (tempAttribute.Type == SaveDataType.Account && tempAttribute.UserId == UserId.InvalidId) + { + if (tempAttribute.ProgramId == ProgramId.InvalidId) + { + tempAttribute.ProgramId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + } + + if (tempCreationInfo.OwnerId == 0) + { + tempCreationInfo.OwnerId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + } + } + + return CreateSaveDataFileSystemCore(in tempAttribute, in tempCreationInfo, in metaInfo, in hashSalt); + } + + public Result CreateSaveDataFileSystemBySystemSaveDataId(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo) + { + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, creationInfo.SpaceId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) + return ResultFs.InvalidArgument.Log(); + + SaveDataCreationInfo newCreationInfo = creationInfo; + + if (newCreationInfo.OwnerId == 0) + { + newCreationInfo.OwnerId = programInfo.ProgramIdValue; + } + + rc = SaveDataAccessibilityChecker.CheckCreate(in attribute, in creationInfo, programInfo, + programInfo.ProgramId); + if (rc.IsFailure()) return rc; + + // Static system saves don't usually have meta files + SaveDataMetaInfo dataMetaInfo = default; + Optional hashSalt = default; + + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in dataMetaInfo, in hashSalt); + } + + public Result ExtendSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long dataSize, + long journalSize) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(out fileSystem, spaceId, in attribute, false); + } + + public Result OpenReadOnlySaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + return OpenUserSaveDataFileSystem(out fileSystem, spaceId, in attribute, true); + } + + private Result OpenSaveDataFileSystemCore(out ReferenceCountedDisposable fileSystem, + out ulong saveDataId, SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly, + bool cacheExtraData) + { + Unsafe.SkipInit(out saveDataId); + fileSystem = default; + + SaveDataIndexerAccessor accessor = null; + + try + { + ulong tempSaveDataId; + bool isStaticSaveDataId = attribute.StaticSaveDataId != 0 && attribute.UserId == UserId.InvalidId; + + // Get the ID of the save data + if (isStaticSaveDataId) + { + tempSaveDataId = attribute.StaticSaveDataId; + } + else + { + Result rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.Get(out SaveDataIndexerValue indexerValue, in attribute); + if (rc.IsFailure()) return rc; + + if (indexerValue.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + + if (indexerValue.State == SaveDataState.Extending) + return ResultFs.SaveDataIsExtending.Log(); + + tempSaveDataId = indexerValue.SaveDataId; + } + + // Open the save data using its ID + Result saveFsResult = ServiceImpl.OpenSaveDataFileSystem(out fileSystem, spaceId, tempSaveDataId, + SaveDataRootPath, openReadOnly, attribute.Type, cacheExtraData); + + if (saveFsResult.IsSuccess()) + { + saveDataId = tempSaveDataId; + return Result.Success; + } + + // Copy the key so we can use it in a local function + SaveDataAttribute key = attribute; + + // Remove the save from the indexer if the save is missing from the disk. + if (ResultFs.PathNotFound.Includes(saveFsResult)) + { + Result rc = RemoveSaveIndexerEntry(); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + if (ResultFs.TargetNotFound.Includes(saveFsResult)) + { + Result rc = RemoveSaveIndexerEntry(); + if (rc.IsFailure()) return rc; + } + + return saveFsResult; + + Result RemoveSaveIndexerEntry() + { + if (tempSaveDataId == FileSystemServer.SaveIndexerId) + return Result.Success; + + if (isStaticSaveDataId) + { + // The accessor won't be open yet if the save has a static ID + Result rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + // Check the space ID of the save data + rc = accessor.Indexer.Get(out SaveDataIndexerValue value, in key); + if (rc.IsFailure()) return rc; + + if (value.SpaceId != ConvertToRealSpaceId(spaceId)) + return ResultFs.TargetNotFound.Log(); + } + + // Remove the indexer entry. Nintendo ignores these results + // ReSharper disable once PossibleNullReferenceException + accessor.Indexer.Delete(tempSaveDataId).IgnoreResult(); + accessor.Indexer.Commit().IgnoreResult(); + + return Result.Success; + } + } + finally + { + accessor?.Dispose(); + } + } + + private Result OpenUserSaveDataFileSystemCore(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, ProgramInfo programInfo, bool openReadOnly) + { + fileSystem = default; + IUniqueLock mountCountSemaphore = null; + ReferenceCountedDisposable tempFileSystem = null; + ReferenceCountedDisposable saveService = null; + ReferenceCountedDisposable openEntryCountAdapter = null; + + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + try + { + // Try grabbing the mount count semaphore + Result rc = TryAcquireSaveDataMountCountSemaphore(out mountCountSemaphore); + if (rc.IsFailure()) return rc; + + bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath); + + // Open the file system + rc = OpenSaveDataFileSystemCore(out tempFileSystem, out ulong saveDataId, spaceId, in attribute, + openReadOnly, true); + if (rc.IsFailure()) return rc; + + // 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); + + // Check if we have permissions to open this save data + rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + + if (useAsyncFileSystem) + { + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + } + + saveService = SelfReference.AddReference(); + openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService); + + tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter, + ref mountCountSemaphore); + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + return Result.Success; + } + finally + { + mountCountSemaphore?.Dispose(); + tempFileSystem?.Dispose(); + saveService?.Dispose(); + openEntryCountAdapter?.Dispose(); + } + } + + private Result OpenUserSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, bool openReadOnly) + { + fileSystem = default; + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = SaveDataAccessibilityChecker.CheckOpenPre(in attribute, programInfo); + if (rc.IsFailure()) return rc; + + SaveDataAttribute tempAttribute; + + if (attribute.ProgramId.Value == 0) + { + ProgramId programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId); + + rc = SaveDataAttribute.Make(out tempAttribute, programId, attribute.Type, attribute.UserId, + attribute.StaticSaveDataId, attribute.Index); + if (rc.IsFailure()) return rc; + } + else + { + tempAttribute = attribute; + } + + SaveDataSpaceId actualSpaceId; + + if (tempAttribute.Type == SaveDataType.Cache) + { + // Check whether the save is on the SD card or the BIS + rc = GetCacheStorageSpaceId(out actualSpaceId, tempAttribute.ProgramId.Value); + if (rc.IsFailure()) return rc; + } + else + { + actualSpaceId = spaceId; + } + + return OpenUserSaveDataFileSystemCore(out fileSystem, actualSpaceId, in tempAttribute, programInfo, + openReadOnly); + } + + public Result OpenSaveDataFileSystemBySystemSaveDataId(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + fileSystem = default; + + if (!IsStaticSaveDataIdValueRange(attribute.StaticSaveDataId)) + return ResultFs.InvalidArgument.Log(); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + StorageType storageFlag = DecidePossibleStorageFlag(attribute.Type, spaceId); + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(storageFlag); + + Accessibility accessibility = + programInfo.AccessControl.GetAccessibilityFor(AccessibilityType.MountSystemSaveData); + + if (!accessibility.CanRead || !accessibility.CanWrite) + return ResultFs.PermissionDenied.Log(); + + bool useAsyncFileSystem = !ServiceImpl.IsAllowedDirectorySaveData(spaceId, SaveDataRootPath); + + ReferenceCountedDisposable tempFileSystem = null; + ReferenceCountedDisposable saveService = null; + ReferenceCountedDisposable openEntryCountAdapter = null; + + try + { + // Open the file system + rc = OpenSaveDataFileSystemCore(out tempFileSystem, out ulong saveDataId, spaceId, in attribute, false, + true); + if (rc.IsFailure()) return rc; + + // 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); + + // Check if we have permissions to open this save data + rc = SaveDataAccessibilityChecker.CheckOpen(in attribute, programInfo, ReadExtraData); + if (rc.IsFailure()) return rc; + + // Add all the wrappers for the file system + tempFileSystem = StorageLayoutTypeSetFileSystem.CreateShared(ref tempFileSystem, storageFlag); + + if (useAsyncFileSystem) + { + tempFileSystem = AsynchronousAccessFileSystem.CreateShared(ref tempFileSystem); + } + + saveService = SelfReference.AddReference(); + openEntryCountAdapter = SaveDataOpenCountAdapter.CreateShared(ref saveService); + + tempFileSystem = OpenCountFileSystem.CreateShared(ref tempFileSystem, ref openEntryCountAdapter); + + fileSystem = FileSystemInterfaceAdapter.CreateShared(ref tempFileSystem); + return Result.Success; + } + finally + { + tempFileSystem?.Dispose(); + saveService?.Dispose(); + openEntryCountAdapter?.Dispose(); + } + } + + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, bool isTemporarySaveData) + { + throw new NotImplementedException(); + } + + private Result ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraDataMask) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraData(OutBuffer extraData, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataAttribute(OutBuffer extraData, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraDataBySaveDataSpaceId(OutBuffer extraData, + SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(OutBuffer extraData, + SaveDataSpaceId spaceId, in SaveDataAttribute attribute, InBuffer extraDataMask) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(in SaveDataAttribute attribute, + SaveDataSpaceId spaceId, InBuffer extraData, InBuffer extraDataMask) + { + throw new NotImplementedException(); + } + + private Result WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, ulong saveDataId, + in SaveDataExtraData extraData, SaveDataType saveType, bool updateTimeStamp) + { + throw new NotImplementedException(); + } + + private Result WriteSaveDataFileSystemExtraDataWithMaskCore(ulong saveDataId, SaveDataSpaceId spaceId, + in SaveDataExtraData extraData, in SaveDataExtraData extraDataMask) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraData(ulong saveDataId, SaveDataSpaceId spaceId, InBuffer extraData) + { + throw new NotImplementedException(); + } + + public Result WriteSaveDataFileSystemExtraDataWithMask(ulong saveDataId, SaveDataSpaceId spaceId, + InBuffer extraData, InBuffer extraDataMask) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) + { + infoReader = default; + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReader) || + !programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForSystem)) + { + return ResultFs.PermissionDenied.Log(); + } + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable reader = null; + + try + { + rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out reader); + if (rc.IsFailure()) return rc; + + infoReader = reader.AddReference(); + return Result.Success; + } + finally + { + accessor?.Dispose(); + reader?.Dispose(); + } + } + + public Result OpenSaveDataInfoReaderBySaveDataSpaceId( + out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId) + { + infoReader = default; + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + if (rc.IsFailure()) return rc; + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable reader = null; + + try + { + rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out reader); + if (rc.IsFailure()) return rc; + + var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), default, default, default, + default, default, 0); + + var filterReader = new SaveDataInfoFilterReader(reader, in infoFilter); + infoReader = new ReferenceCountedDisposable(filterReader); + + return Result.Success; + } + finally + { + accessor?.Dispose(); + reader?.Dispose(); + } + } + + public Result OpenSaveDataInfoReaderWithFilter(out ReferenceCountedDisposable infoReader, + SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + infoReader = default; + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.OpenSaveDataInfoReaderForInternal)) + return ResultFs.PermissionDenied.Log(); + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + if (rc.IsFailure()) return rc; + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable reader = null; + + try + { + rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out reader); + if (rc.IsFailure()) return rc; + + var infoFilter = new SaveDataInfoFilter(spaceId, in filter); + + var filterReader = new SaveDataInfoFilterReader(reader, in infoFilter); + infoReader = new ReferenceCountedDisposable(filterReader); + + return Result.Success; + } + finally + { + accessor?.Dispose(); + reader?.Dispose(); + } + } + + private Result FindSaveDataWithFilterImpl(out long count, out SaveDataInfo info, SaveDataSpaceId spaceId, + in SaveDataInfoFilter infoFilter) + { + Unsafe.SkipInit(out count); + Unsafe.SkipInit(out info); + + SaveDataIndexerAccessor accessor = null; + ReferenceCountedDisposable reader = null; + + try + { + Result rc = OpenSaveDataIndexerAccessor(out accessor, spaceId); + if (rc.IsFailure()) return rc; + + rc = accessor.Indexer.OpenSaveDataInfoReader(out reader); + if (rc.IsFailure()) return rc; + + using (var filterReader = new SaveDataInfoFilterReader(reader, in infoFilter)) + { + return filterReader.Read(out count, new OutBuffer(SpanHelpers.AsByteSpan(ref info))); + } + + } + finally + { + accessor?.Dispose(); + reader?.Dispose(); + } + } + + public Result FindSaveDataWithFilter(out long count, OutBuffer saveDataInfoBuffer, SaveDataSpaceId spaceId, + in SaveDataFilter filter) + { + Unsafe.SkipInit(out count); + + if (saveDataInfoBuffer.Size != Unsafe.SizeOf()) + return ResultFs.InvalidArgument.Log(); + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + rc = CheckOpenSaveDataInfoReaderAccessControl(programInfo, spaceId); + + if (rc.IsFailure()) + { + if (!ResultFs.PermissionDenied.Includes(rc)) + return rc; + + // Don't have full info reader permissions. Check if we have find permissions. + rc = SaveDataAccessibilityChecker.CheckFind(in filter, programInfo); + if (rc.IsFailure()) return rc; + } + + var infoFilter = new SaveDataInfoFilter(ConvertToRealSpaceId(spaceId), in filter); + + return FindSaveDataWithFilterImpl(out count, + out SpanHelpers.AsStruct(saveDataInfoBuffer.Buffer), spaceId, in infoFilter); + } + + private Result CreateEmptyThumbnailFile(SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + private Result OpenSaveDataInternalStorageFileSystemCore(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool useSecondMacKey) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataInternalStorageTotalSize(out long size, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result GetSaveDataCommitId(out long commitId, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataInfoReaderOnlyCacheStorage( + out ReferenceCountedDisposable infoReader) + { + infoReader = null; + + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.NonGameCard); + + // Find where the current program's cache storage is located + Result rc = GetCacheStorageSpaceId(out SaveDataSpaceId spaceId); + + if (rc.IsFailure()) + { + spaceId = SaveDataSpaceId.User; + + if (!ResultFs.TargetNotFound.Includes(rc)) + return rc; + } + + return OpenSaveDataInfoReaderOnlyCacheStorage(out infoReader, spaceId); + } + + private Result OpenSaveDataInfoReaderOnlyCacheStorage( + out ReferenceCountedDisposable infoReader, SaveDataSpaceId spaceId) + { + throw new NotImplementedException(); + } + + private Result OpenSaveDataMetaFileRaw(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, + ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMetaFile(out ReferenceCountedDisposable file, SaveDataSpaceId spaceId, + in SaveDataAttribute attribute, SaveDataMetaType metaType) + { + throw new NotImplementedException(); + } + + private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId) + { + Unsafe.SkipInit(out spaceId); + + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + ulong programId = ResolveDefaultSaveDataReferenceProgramId(programInfo.ProgramId).Value; + return GetCacheStorageSpaceId(out spaceId, programId); + } + + private Result GetCacheStorageSpaceId(out SaveDataSpaceId spaceId, ulong programId) + { + Unsafe.SkipInit(out spaceId); + Result rc; + + // Cache storage on the SD card will always take priority over case storage in NAND + if (ServiceImpl.IsSdCardAccessible()) + { + rc = SaveExists(out bool existsOnSdCard, SaveDataSpaceId.SdCache); + if (rc.IsFailure()) return rc; + + if (existsOnSdCard) + { + spaceId = SaveDataSpaceId.SdCache; + return Result.Success; + } + } + + rc = SaveExists(out bool existsOnNand, SaveDataSpaceId.User); + if (rc.IsFailure()) return rc; + + if (existsOnNand) + { + spaceId = SaveDataSpaceId.User; + return Result.Success; + } + + return ResultFs.TargetNotFound.Log(); + + Result SaveExists(out bool exists, SaveDataSpaceId saveSpaceId) + { + Unsafe.SkipInit(out exists); + + var infoFilter = new SaveDataInfoFilter(saveSpaceId, new ProgramId(programId), SaveDataType.Cache, + default, default, default, 0); + + Result result = FindSaveDataWithFilterImpl(out long count, out _, saveSpaceId, in infoFilter); + if (result.IsFailure()) return result; + + exists = count != 0; + return Result.Success; + } + } + + private Result FindCacheStorage(out SaveDataInfo saveInfo, out SaveDataSpaceId spaceId, ushort index) + { + throw new NotImplementedException(); + } + + public Result DeleteCacheStorage(ushort index) + { + throw new NotImplementedException(); + } + + public Result GetCacheStorageSize(out long usableDataSize, out long journalSize, ushort index) + { + throw new NotImplementedException(); + } + + public Result OpenSaveDataMover(out ReferenceCountedDisposable saveMover, + SaveDataSpaceId sourceSpaceId, SaveDataSpaceId destinationSpaceId, NativeHandle workBufferHandle, + ulong workBufferSize) + { + throw new NotImplementedException(); + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + return ServiceImpl.SetSdCardEncryptionSeed(in seed); + } + + public Result ListAccessibleSaveDataOwnerId(out int readCount, OutBuffer idBuffer, ProgramId programId, + int startIndex, int bufferIdCount) + { + throw new NotImplementedException(); + } + + private ProgramId ResolveDefaultSaveDataReferenceProgramId(in ProgramId programId) + { + return ServiceImpl.ResolveDefaultSaveDataReferenceProgramId(in programId); + } + + public Result VerifySaveDataFileSystemBySaveDataSpaceId(SaveDataSpaceId spaceId, ulong saveDataId, + OutBuffer workBuffer) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystemByOffset(SaveDataSpaceId spaceId, ulong saveDataId, long offset) + { + throw new NotImplementedException(); + } + + public Result CleanUpSaveData() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + SaveDataIndexerAccessor accessor = null; + try + { + Result rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + return CleanUpSaveData(accessor); + } + finally + { + accessor?.Dispose(); + } + } + + private Result CleanUpSaveData(SaveDataIndexerAccessor accessor) + { + // Todo: Implement + return Result.Success; + } + + public Result CompleteSaveDataExtension() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + SaveDataIndexerAccessor accessor = null; + try + { + Result rc = OpenSaveDataIndexerAccessor(out accessor, SaveDataSpaceId.System); + if (rc.IsFailure()) return rc; + + return CompleteSaveDataExtension(accessor); + } + finally + { + accessor?.Dispose(); + } + } + + private Result CompleteSaveDataExtension(SaveDataIndexerAccessor accessor) + { + // Todo: Implement + return Result.Success; + } + + public Result CleanUpTemporaryStorage() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageType.Bis); + + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = ServiceImpl.OpenSaveDataDirectoryFileSystem(out fileSystem, SaveDataSpaceId.Temporary, + U8Span.Empty, false); + if (rc.IsFailure()) return rc; + + var rootPath = new U8Span(new[] { (byte)'/' }); + rc = fileSystem.Target.CleanDirectoryRecursively(rootPath); + if (rc.IsFailure()) return rc; + + ServiceImpl.ResetTemporaryStorageIndexer(); + return Result.Success; + } + finally + { + fileSystem?.Dispose(); + } + } + + public Result FixSaveData() + { + // Todo: Implement + return Result.Success; + } + + public Result OpenMultiCommitManager(out ReferenceCountedDisposable commitManager) + { + commitManager = default; + + ReferenceCountedDisposable saveService = null; + ReferenceCountedDisposable commitInterface = null; + try + { + saveService = SelfReference.AddReference(); + commitInterface = saveService.AddReference(); + + commitManager = MultiCommitManager.CreateShared(ref commitInterface, Hos); + return Result.Success; + } + finally + { + saveService?.Dispose(); + commitInterface?.Dispose(); + } + } + + public Result OpenMultiCommitContext(out ReferenceCountedDisposable contextFileSystem) + { + var attribute = new SaveDataAttribute + { + Index = 0, + Type = SaveDataType.System, + UserId = UserId.InvalidId, + StaticSaveDataId = MultiCommitManager.SaveDataId, + ProgramId = new ProgramId(MultiCommitManager.ProgramId) + }; + + return OpenSaveDataFileSystemCore(out contextFileSystem, out _, SaveDataSpaceId.System, in attribute, false, + true); + } + + public Result RecoverMultiCommit() + { + return MultiCommitManager.Recover(this, ServiceImpl); + } + + public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) + { + return ServiceImpl.IsProvisionallyCommittedSaveData(out isProvisionallyCommitted, in saveInfo); + } + + public Result RecoverProvisionallyCommittedSaveData(in SaveDataInfo saveInfo, bool doRollback) + { + var attribute = new SaveDataAttribute + { + Index = saveInfo.Index, + Type = saveInfo.Type, + UserId = UserId.InvalidId, + StaticSaveDataId = saveInfo.StaticSaveDataId, + ProgramId = saveInfo.ProgramId + }; + + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = OpenSaveDataFileSystemCore(out fileSystem, out _, saveInfo.SpaceId, in attribute, false, + false); + if (rc.IsFailure()) return rc; + + if (doRollback) + { + rc = fileSystem.Target.Rollback(); + } + else + { + rc = fileSystem.Target.Commit(); + } + + return rc; + } + finally + { + fileSystem?.Dispose(); + } + } + + private Result TryAcquireSaveDataEntryOpenCountSemaphore(out IUniqueLock semaphoreLock) + { + semaphoreLock = null; + + ReferenceCountedDisposable saveService = null; + IUniqueLock uniqueLock = null; + try + { + saveService = SelfReference.AddReference(); + + Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, OpenEntryCountSemaphore, + ref saveService); + if (rc.IsFailure()) return rc; + + Shared.Move(out semaphoreLock, ref uniqueLock); + return Result.Success; + } + finally + { + saveService?.Dispose(); + uniqueLock?.Dispose(); + } + } + + private Result TryAcquireSaveDataMountCountSemaphore(out IUniqueLock semaphoreLock) + { + semaphoreLock = null; + + ReferenceCountedDisposable saveService = null; + IUniqueLock uniqueLock = null; + try + { + saveService = SelfReference.AddReference(); + + Result rc = Utility.MakeUniqueLockWithPin(out uniqueLock, SaveDataMountCountSemaphore, + ref saveService); + if (rc.IsFailure()) return rc; + + Shared.Move(out semaphoreLock, ref uniqueLock); + return Result.Success; + } + finally + { + saveService?.Dispose(); + uniqueLock?.Dispose(); + } + } + + public Result SetSdCardAccessibility(bool isAccessible) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetSdCardAccessibility)) + return ResultFs.PermissionDenied.Log(); + + ServiceImpl.SetSdCardAccessibility(isAccessible); + return Result.Success; + } + + public Result IsSdCardAccessible(out bool isAccessible) + { + isAccessible = ServiceImpl.IsSdCardAccessible(); + return Result.Success; + } + + private Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, SaveDataSpaceId spaceId) + { + accessor = default; + SaveDataIndexerAccessor accessorTemp = null; + + try + { + Result rc = ServiceImpl.OpenSaveDataIndexerAccessor(out accessorTemp, out bool neededInit, spaceId); + if (rc.IsFailure()) return rc; + + if (neededInit) + { + // todo: nn::fssrv::SaveDataFileSystemService::CleanUpSaveDataCore + // nn::fssrv::SaveDataFileSystemService::CompleteSaveDataExtensionCore + } + + Shared.Move(out accessor, ref accessorTemp); + return Result.Success; + } + finally + { + accessorTemp?.Dispose(); + } + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return ServiceImpl.GetProgramInfo(out programInfo, ProcessId); + } + + private bool IsCurrentProcess(ulong processId) + { + ulong currentId = Hos.Os.GetCurrentProcessId().Value; + + return processId == currentId; + } + + private SaveDataSpaceId ConvertToRealSpaceId(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.ProperSystem || spaceId == SaveDataSpaceId.SafeMode + ? SaveDataSpaceId.System + : spaceId; + } + + private bool IsStaticSaveDataIdValueRange(ulong id) + { + return (long)id < 0; + } + + private StorageType DecidePossibleStorageFlag(SaveDataType type, SaveDataSpaceId spaceId) + { + if (type == SaveDataType.Cache || type == SaveDataType.Bcat) + return StorageType.Bis | StorageType.SdCard | StorageType.Usb; + + if (type == SaveDataType.System || + spaceId != SaveDataSpaceId.SdSystem && spaceId != SaveDataSpaceId.SdCache) + return StorageType.Bis; + + return StorageType.SdCard | StorageType.Usb; + } + + Result ISaveDataTransferCoreInterface.CreateSaveDataFileSystemCore(in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, in SaveDataMetaInfo metaInfo, in Optional hashSalt, + bool leaveUnfinalized) + { + return CreateSaveDataFileSystemCore(in attribute, in creationInfo, in metaInfo, in hashSalt, + leaveUnfinalized); + } + + Result ISaveDataTransferCoreInterface.ReadSaveDataFileSystemExtraDataCore(out SaveDataExtraData extraData, + SaveDataSpaceId spaceId, ulong saveDataId, bool isTemporarySaveData) + { + return ReadSaveDataFileSystemExtraDataCore(out extraData, spaceId, saveDataId, isTemporarySaveData); + } + + Result ISaveDataTransferCoreInterface.WriteSaveDataFileSystemExtraDataCore(SaveDataSpaceId spaceId, + ulong saveDataId, in SaveDataExtraData extraData, SaveDataType type, bool updateTimeStamp) + { + return WriteSaveDataFileSystemExtraDataCore(spaceId, saveDataId, in extraData, type, updateTimeStamp); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataMetaFileRaw(out ReferenceCountedDisposable file, + SaveDataSpaceId spaceId, ulong saveDataId, SaveDataMetaType metaType, OpenMode mode) + { + return OpenSaveDataMetaFileRaw(out file, spaceId, saveDataId, metaType, mode); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataInternalStorageFileSystemCore( + out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId, + bool useSecondMacKey) + { + return OpenSaveDataInternalStorageFileSystemCore(out fileSystem, spaceId, saveDataId, useSecondMacKey); + } + + Result ISaveDataTransferCoreInterface.OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, + SaveDataSpaceId spaceId) + { + return OpenSaveDataIndexerAccessor(out accessor, spaceId); + } + + public void Dispose() + { + OpenEntryCountSemaphore.Dispose(); + SaveDataMountCountSemaphore.Dispose(); + } + } +} diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs new file mode 100644 index 00000000..295967a4 --- /dev/null +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -0,0 +1,626 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSrv.Creators; +using LibHac.FsSrv.Impl; +using LibHac.FsSystem; +using LibHac.FsSystem.Save; +using LibHac.Ncm; +using LibHac.Util; +using Utility = LibHac.FsSrv.Impl.Utility; + +namespace LibHac.FsSrv +{ + public class SaveDataFileSystemServiceImpl + { + private Configuration _config; + private EncryptionSeed _encryptionSeed; + + private ISaveDataFileSystemCacheManager _saveCacheManager; + // Save data extra data cache + // Save data porter manager + private bool _isSdCardAccessible; + // Timestamp getter + + internal HorizonClient Hos => _config.HorizonClient; + + public SaveDataFileSystemServiceImpl(in Configuration configuration) + { + _config = configuration; + } + + public struct Configuration + { + public BaseFileSystemServiceImpl BaseFsService; + // Time service + public IHostFileSystemCreator HostFsCreator; + public ITargetManagerFileSystemCreator TargetManagerFsCreator; + public ISaveDataFileSystemCreator SaveFsCreator; + public IEncryptedFileSystemCreator EncryptedFsCreator; + public ProgramRegistryServiceImpl ProgramRegistryService; + // Buffer manager + public RandomDataGenerator GenerateRandomData; + public SaveDataTransferCryptoConfiguration SaveTransferCryptoConfig; + // Max save FS cache size + public Func ShouldCreateDirectorySaveData; + public ISaveDataIndexerManager SaveIndexerManager; + + // LibHac additions + public HorizonClient HorizonClient; + public ProgramRegistryImpl ProgramRegistry; + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + + public Result DoesSaveDataEntityExist(out bool exists, SaveDataSpaceId spaceId, ulong saveDataId) + { + Unsafe.SkipInit(out exists); + + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, U8Span.Empty, true); + 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); + + rc = fileSystem.Target.GetEntryType(out _, new U8Span(saveDataPath)); + + if (rc.IsSuccess()) + { + exists = true; + return Result.Success; + } + else if (ResultFs.PathNotFound.Includes(rc)) + { + exists = false; + return Result.Success; + } + else + { + return rc; + } + } + finally + { + fileSystem?.Dispose(); + } + } + + public Result OpenSaveDataFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool openReadOnly, SaveDataType type, + bool cacheExtraData) + { + fileSystem = default; + + ReferenceCountedDisposable saveDirectoryFs = null; + ReferenceCountedDisposable cachedSaveDataFs = null; + ReferenceCountedDisposable saveDataFs = null; + try + { + Result rc = OpenSaveDataDirectoryFileSystem(out saveDirectoryFs, spaceId, saveDataRootPath, true); + if (rc.IsFailure()) return rc; + + bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, saveDataRootPath); + + if (allowDirectorySaveData) + { + Span saveDirectoryPath = stackalloc byte[0x12]; + var sb = new U8StringBuilder(saveDirectoryPath); + sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + + rc = Utility.EnsureDirectory(saveDirectoryFs.Target, new U8Span(saveDirectoryPath)); + if (rc.IsFailure()) return rc; + } + + if (!allowDirectorySaveData) + { + // Todo: Missing save FS cache lookup + } + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (saveDataFs is null) + { + ReferenceCountedDisposable extraDataAccessor = null; + try + { + // Todo: Update ISaveDataFileSystemCreator + bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId); + + rc = _config.SaveFsCreator.Create(out IFileSystem saveFs, + out extraDataAccessor, saveDirectoryFs.Target, saveDataId, + allowDirectorySaveData, useDeviceUniqueMac, type, null); + if (rc.IsFailure()) return rc; + + saveDataFs = new ReferenceCountedDisposable(saveFs); + + if (cacheExtraData) + { + // Todo: Missing extra data caching + } + } + finally + { + extraDataAccessor?.Dispose(); + } + } + + if (openReadOnly) + { + saveDataFs = ReadOnlyFileSystem.CreateShared(ref saveDataFs); + } + + Shared.Move(out fileSystem, ref saveDataFs); + return Result.Success; + } + finally + { + saveDirectoryFs?.Dispose(); + // ReSharper disable once ExpressionIsAlwaysNull + // ReSharper disable once ConstantConditionalAccessQualifier + cachedSaveDataFs?.Dispose(); + saveDataFs?.Dispose(); + } + } + + 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) '/' + }); + + Span directoryName = stackalloc byte[0x1B]; + var sb = new U8StringBuilder(directoryName); + sb.Append(metaDirPath).AppendFormat(saveDataId, 'x', 16); + + return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(directoryName)); + } + + public Result OpenSaveDataInternalStorageFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, U8Span saveDataRootPath, bool useSecondMacKey) + { + throw new NotImplementedException(); + } + + public Result QuerySaveDataTotalSize(out long totalSize, int blockSize, long dataSize, long journalSize) + { + // Todo: Implement + totalSize = 0; + return Result.Success; + } + + public Result CreateSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType, + long metaSize) + { + ReferenceCountedDisposable metaDirFs = null; + try + { + 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); + + return metaDirFs.Target.CreateFile(new U8Span(saveMetaPathBuffer), metaSize); + } + finally + { + metaDirFs?.Dispose(); + } + } + + public Result DeleteSaveDataMeta(ulong saveDataId, SaveDataSpaceId spaceId, SaveDataMetaType metaType) + { + ReferenceCountedDisposable metaDirFs = null; + try + { + 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); + + return metaDirFs.Target.DeleteFile(new U8Span(saveMetaPathBuffer)); + } + finally + { + metaDirFs?.Dispose(); + } + } + + public Result DeleteAllSaveDataMetas(ulong saveDataId, SaveDataSpaceId spaceId) + { + var metaDirPath = // /saveMeta + new U8Span(new[] + { + (byte) '/', (byte) 's', (byte) 'a', (byte) 'v', (byte) 'e', (byte) 'M', (byte) 'e', (byte) 't', + (byte) 'a' + }); + + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, metaDirPath, false); + 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); + + // Delete the save data's meta directory, ignoring the error if the directory is already gone + rc = fileSystem.Target.DeleteDirectoryRecursively(new U8Span(saveDataPathBuffer)); + + if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) + return rc; + + return Result.Success; + } + finally + { + fileSystem?.Dispose(); + } + } + + public Result OpenSaveDataMeta(out IFile metaFile, ulong saveDataId, SaveDataSpaceId spaceId, + SaveDataMetaType metaType) + { + metaFile = default; + + ReferenceCountedDisposable metaDirFs = null; + try + { + 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); + + return metaDirFs.Target.OpenFile(out metaFile, new U8Span(saveMetaPathBuffer), OpenMode.ReadWrite); + } + finally + { + metaDirFs?.Dispose(); + } + } + + public Result CreateSaveDataFileSystem(ulong saveDataId, in SaveDataAttribute attribute, + in SaveDataCreationInfo creationInfo, U8Span saveDataRootPath, in Optional hashSalt, + bool skipFormat) + { + // Use directory save data for now + + ReferenceCountedDisposable fileSystem = null; + try + { + Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, creationInfo.SpaceId, saveDataRootPath, + false); + if (rc.IsFailure()) return rc; + + Span saveDataPathBuffer = stackalloc byte[0x12]; + var sb = new U8StringBuilder(saveDataPathBuffer); + sb.Append((byte)'/').AppendFormat(saveDataId, 'x', 16); + + if (_config.ShouldCreateDirectorySaveData()) + { + return Utility.EnsureDirectory(fileSystem.Target, new U8Span(saveDataPathBuffer)); + } + else + { + throw new NotImplementedException(); + } + } + finally + { + fileSystem?.Dispose(); + } + } + + private Result WipeData(IFileSystem fileSystem, U8Span filePath, RandomDataGenerator random) + { + throw new NotImplementedException(); + } + + public Result DeleteSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, bool wipeSaveFile, + U8Span saveDataRootPath) + { + ReferenceCountedDisposable fileSystem = null; + try + { + // Open the directory containing the save data + Result rc = OpenSaveDataDirectoryFileSystem(out fileSystem, spaceId, 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); + + // Check if the save data is a file or a directory + rc = fileSystem.Target.GetEntryType(out DirectoryEntryType entryType, saveDataPath); + if (rc.IsFailure()) return rc; + + // Delete the save data, wiping the file if needed + if (entryType == DirectoryEntryType.Directory) + { + rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath); + if (rc.IsFailure()) return rc; + } + else + { + if (wipeSaveFile) + { + WipeData(fileSystem.Target, saveDataPath, _config.GenerateRandomData).IgnoreResult(); + } + + rc = fileSystem.Target.DeleteDirectoryRecursively(saveDataPath); + if (rc.IsFailure()) return rc; + } + + return Result.Success; + } + finally + { + fileSystem?.Dispose(); + } + } + + public Result ReadSaveDataFileSystemExtraData(out SaveDataExtraData extraData, SaveDataSpaceId spaceId, + ulong saveDataId, SaveDataType type, U8Span saveDataRootPath) + { + // Todo: Find a way to store extra data for directory save data + extraData = default; + return Result.Success; + } + + public Result WriteSaveDataFileSystemExtraData(SaveDataSpaceId spaceId, ulong saveDataId, + in SaveDataExtraData extraData, U8Span saveDataRootPath, SaveDataType type, bool updateTimeStamp) + { + throw new NotImplementedException(); + } + + public Result CorruptSaveDataFileSystem(SaveDataSpaceId spaceId, ulong saveDataId, long offset, + U8Span saveDataRootPath) + { + throw new NotImplementedException(); + } + + private bool IsSaveEmulated(U8Span saveDataRootPath) + { + return !saveDataRootPath.IsEmpty(); + } + + public Result OpenSaveDataDirectoryFileSystem(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, U8Span saveDataRootPath, bool allowEmulatedSave) + { + if (allowEmulatedSave && IsAllowedDirectorySaveData(spaceId, saveDataRootPath)) + { + fileSystem = default; + ReferenceCountedDisposable tmFs = null; + + try + { + // Ensure the target save data directory exists + Result rc = _config.TargetManagerFsCreator.Create(out tmFs, false); + 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); + if (rc.IsFailure()) return rc; + + rc = _config.TargetManagerFsCreator.Create(out tmFs, isTargetFsCaseSensitive); + if (rc.IsFailure()) return rc; + + return Utility.CreateSubDirectoryFileSystem(out fileSystem, ref tmFs, new U8Span(path.Str), true); + } + finally + { + tmFs?.Dispose(); + } + } + + ReadOnlySpan saveDirName; + + if (spaceId == SaveDataSpaceId.Temporary) + { + saveDirName = new[] { (byte)'/', (byte)'t', (byte)'e', (byte)'m', (byte)'p' }; // /temp + } + else + { + saveDirName = new[] { (byte)'/', (byte)'s', (byte)'a', (byte)'v', (byte)'e' }; // /save + } + + return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, new U8Span(saveDirName), true); + } + + public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, U8Span basePath) + { + return OpenSaveDataDirectoryFileSystemImpl(out fileSystem, spaceId, basePath, true); + } + + public Result OpenSaveDataDirectoryFileSystemImpl(out ReferenceCountedDisposable fileSystem, + SaveDataSpaceId spaceId, U8Span basePath, bool createIfMissing) + { + fileSystem = default; + + ReferenceCountedDisposable tempFs = null; + ReferenceCountedDisposable tempSubFs = null; + try + { + Result rc; + + switch (spaceId) + { + case SaveDataSpaceId.System: + rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.System, true); + if (rc.IsFailure()) return rc; + + return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + + case SaveDataSpaceId.User: + case SaveDataSpaceId.Temporary: + rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.User, true); + if (rc.IsFailure()) return rc; + + return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: + rc = _config.BaseFsService.OpenSdCardProxyFileSystem(out tempFs, 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); + + rc = Utility.WrapSubDirectory(out tempSubFs, ref tempFs, path, createIfMissing); + if (rc.IsFailure()) return rc; + + return _config.EncryptedFsCreator.Create(out fileSystem, tempSubFs, EncryptedFsKeyId.Save, + in _encryptionSeed); + + case SaveDataSpaceId.ProperSystem: + rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SystemProperPartition, true); + if (rc.IsFailure()) return rc; + + return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + + case SaveDataSpaceId.SafeMode: + rc = _config.BaseFsService.OpenBisFileSystem(out tempFs, U8Span.Empty, BisPartitionId.SafeMode, true); + if (rc.IsFailure()) return rc; + + return Utility.WrapSubDirectory(out fileSystem, ref tempFs, basePath, createIfMissing); + + default: + return ResultFs.InvalidArgument.Log(); + } + } + finally + { + tempFs?.Dispose(); + tempSubFs?.Dispose(); + } + } + + public Result SetSdCardEncryptionSeed(in EncryptionSeed seed) + { + _encryptionSeed = seed; + + _config.SaveFsCreator.SetSdCardEncryptionSeed(seed.Value); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdSystem); + _config.SaveIndexerManager.InvalidateIndexer(SaveDataSpaceId.SdCache); + + return Result.Success; + } + + public Result IsProvisionallyCommittedSaveData(out bool isProvisionallyCommitted, in SaveDataInfo saveInfo) + { + throw new NotImplementedException(); + } + + public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, U8Span saveDataRootPath) + { + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(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) + { + // Todo: remove "|| true" once file save data is supported + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return spaceId == SaveDataSpaceId.User && IsSaveEmulated(saveDataRootPath) || true; + } + + public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) + { + return spaceId == SaveDataSpaceId.System || + spaceId == SaveDataSpaceId.User || + spaceId == SaveDataSpaceId.Temporary || + spaceId == SaveDataSpaceId.ProperSystem || + spaceId == SaveDataSpaceId.SafeMode; + } + + public void SetSdCardAccessibility(bool isAccessible) + { + _isSdCardAccessible = isAccessible; + } + + public bool IsSdCardAccessible() + { + return _isSdCardAccessible; + } + + /// + /// Gets the program ID of the save data associated with the specified programID. + /// + /// In a standard application the program ID will be the same as the input program ID. + /// In multi-program applications all sub-programs use the program ID of the main program + /// for their save data. The main program always has a program index of 0. + /// The program ID to get the save data program ID for. + /// The program ID of the save data. + public ProgramId ResolveDefaultSaveDataReferenceProgramId(in ProgramId programId) + { + // First check if there's an entry in the program index map with the program ID and program index 0 + ProgramId mainProgramId = _config.ProgramRegistryService.GetProgramIdByIndex(programId, 0); + + if (mainProgramId != ProgramId.InvalidId) + { + return mainProgramId; + } + + // Check if there's an entry with the program ID, ignoring program index + Optional mapInfo = _config.ProgramRegistryService.GetProgramIndexMapInfo(programId); + + if (mapInfo.HasValue) + { + return mapInfo.Value.MainProgramId; + } + + // The program ID isn't in the program index map. Probably running a single-program application + return programId; + } + + public void ResetTemporaryStorageIndexer() + { + _config.SaveIndexerManager.ResetIndexer(SaveDataSpaceId.Temporary); + } + + public Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, + SaveDataSpaceId spaceId) + { + return _config.SaveIndexerManager.OpenSaveDataIndexerAccessor(out accessor, out neededInit, spaceId); + } + } +} diff --git a/src/LibHac/FsSrv/SaveDataIndexer.cs b/src/LibHac/FsSrv/SaveDataIndexer.cs index 2cab3d4f..8bd41a6a 100644 --- a/src/LibHac/FsSrv/SaveDataIndexer.cs +++ b/src/LibHac/FsSrv/SaveDataIndexer.cs @@ -8,7 +8,9 @@ using LibHac.Common; using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Shim; +using LibHac.FsSrv.Sf; using LibHac.Kvdb; +using LibHac.Sf; namespace LibHac.FsSrv { @@ -504,7 +506,7 @@ namespace LibHac.FsSrv } } - public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) + public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) { infoReader = default; @@ -522,7 +524,7 @@ namespace LibHac.FsSrv rc = RegisterReader(reader); if (rc.IsFailure()) return rc; - infoReader = reader.AddReference(); + infoReader = reader.AddReference(); return Result.Success; } } @@ -802,7 +804,7 @@ namespace LibHac.FsSrv } } - private class Reader : ISaveDataInfoReader + private class Reader : SaveDataInfoReaderImpl, ISaveDataInfoReader { private readonly SaveDataIndexer _indexer; private FlatMapKeyValueStore.Iterator _iterator; @@ -816,7 +818,7 @@ namespace LibHac.FsSrv _iterator = indexer.GetBeginIterator(); } - public Result Read(out long readCount, Span saveDataInfoBuffer) + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) { Unsafe.SkipInit(out readCount); @@ -828,7 +830,7 @@ namespace LibHac.FsSrv return ResultFs.InvalidSaveDataInfoReader.Log(); } - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer); + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); int i; for (i = 0; !_iterator.IsEnd() && i < outInfo.Length; i++) diff --git a/src/LibHac/FsSrv/SaveDataIndexerLite.cs b/src/LibHac/FsSrv/SaveDataIndexerLite.cs index fd6216f8..a0d1d6d6 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerLite.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerLite.cs @@ -1,6 +1,8 @@ using System; using System.Runtime.InteropServices; using LibHac.Fs; +using LibHac.FsSrv.Sf; +using LibHac.Sf; namespace LibHac.FsSrv { @@ -207,7 +209,7 @@ namespace LibHac.FsSrv return 1; } - public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) + public Result OpenSaveDataInfoReader(out ReferenceCountedDisposable infoReader) { SaveDataIndexerLiteInfoReader reader; @@ -220,12 +222,12 @@ namespace LibHac.FsSrv reader = new SaveDataIndexerLiteInfoReader(); } - infoReader = new ReferenceCountedDisposable(reader); + infoReader = new ReferenceCountedDisposable(reader); return Result.Success; } - private class SaveDataIndexerLiteInfoReader : ISaveDataInfoReader + private class SaveDataIndexerLiteInfoReader : SaveDataInfoReaderImpl, ISaveDataInfoReader { private bool _finishedIterating; private SaveDataInfo _info; @@ -240,9 +242,9 @@ namespace LibHac.FsSrv SaveDataIndexer.GenerateSaveDataInfo(out _info, in key, in value); } - public Result Read(out long readCount, Span saveDataInfoBuffer) + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) { - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer); + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); // Note: Nintendo doesn't check if the buffer is large enough here if (_finishedIterating || outInfo.IsEmpty) diff --git a/src/LibHac/FsSrv/SaveDataIndexerManager.cs b/src/LibHac/FsSrv/SaveDataIndexerManager.cs index 6cfbe42e..b8680019 100644 --- a/src/LibHac/FsSrv/SaveDataIndexerManager.cs +++ b/src/LibHac/FsSrv/SaveDataIndexerManager.cs @@ -1,9 +1,9 @@ using System; -using System.Threading; using LibHac.Common; using LibHac.Diag; using LibHac.Fs; using LibHac.FsSrv.Storage; +using LibHac.Util; namespace LibHac.FsSrv { @@ -54,97 +54,112 @@ namespace LibHac.FsSrv /// if the indexer needed to be initialized. /// The of the indexer to open. /// The of the operation. - public Result OpenAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, SaveDataSpaceId spaceId) + public Result OpenSaveDataIndexerAccessor(out SaveDataIndexerAccessor accessor, out bool neededInit, + SaveDataSpaceId spaceId) { neededInit = false; + UniqueLock indexerLock = default; if (IsBisUserRedirectionEnabled && spaceId == SaveDataSpaceId.User) { spaceId = SaveDataSpaceId.ProperSystem; } - switch (spaceId) + try { - case SaveDataSpaceId.System: - case SaveDataSpaceId.User: - Monitor.Enter(_bisIndexer.Locker); + ISaveDataIndexer indexer; + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.User: + indexerLock = new UniqueLock(_bisIndexer.Locker); - if (!_bisIndexer.IsInitialized) - { - _bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName), - SaveDataSpaceId.System, SaveDataId, MemoryResource); + if (!_bisIndexer.IsInitialized) + { + _bisIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SystemIndexerMountName), + SaveDataSpaceId.System, SaveDataId, MemoryResource); - neededInit = true; - } + neededInit = true; + } - accessor = new SaveDataIndexerAccessor(_bisIndexer.Indexer, _bisIndexer.Locker); - return Result.Success; + indexer = _bisIndexer.Indexer; + break; + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.SdCache: + // ReSharper doesn't realize that UniqueLock locks the indexer's lock object + // ReSharper disable InconsistentlySynchronizedField + indexerLock = new UniqueLock(_sdCardIndexer.Locker); - case SaveDataSpaceId.SdSystem: - case SaveDataSpaceId.SdCache: - Monitor.Enter(_sdCardIndexer.Locker); + // We need to reinitialize the indexer if the SD card has changed + if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized) + { + _sdCardIndexer.Indexer.Dispose(); + _sdCardIndexer.Indexer = null; + } - // We need to reinitialize the indexer if the SD card has changed - if (!_sdCardHandleManager.IsValid(in _sdCardHandle) && _sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer.Dispose(); - _sdCardIndexer.Indexer = null; - } + if (!_sdCardIndexer.IsInitialized) + { + _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName), + SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource); - if (!_sdCardIndexer.IsInitialized) - { - _sdCardIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SdCardIndexerMountName), - SaveDataSpaceId.SdSystem, SaveDataId, MemoryResource); + _sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); - _sdCardHandleManager.GetHandle(out _sdCardHandle).IgnoreResult(); + neededInit = true; + } - neededInit = true; - } + indexer = _sdCardIndexer.Indexer; + // ReSharper restore InconsistentlySynchronizedField - accessor = new SaveDataIndexerAccessor(_sdCardIndexer.Indexer, _sdCardIndexer.Locker); - return Result.Success; + break; + case SaveDataSpaceId.Temporary: + indexerLock = new UniqueLock(_tempIndexer.Locker); - case SaveDataSpaceId.Temporary: - Monitor.Enter(_tempIndexer.Locker); + indexer = _tempIndexer.Indexer; + break; + case SaveDataSpaceId.ProperSystem: + indexerLock = new UniqueLock(_properSystemIndexer.Locker); - accessor = new SaveDataIndexerAccessor(_tempIndexer.Indexer, _tempIndexer.Locker); - return Result.Success; + if (!_properSystemIndexer.IsInitialized) + { + _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, + new U8Span(ProperSystemIndexerMountName), + SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource); - case SaveDataSpaceId.ProperSystem: - Monitor.Enter(_properSystemIndexer.Locker); + neededInit = true; + } - if (!_properSystemIndexer.IsInitialized) - { - _properSystemIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(ProperSystemIndexerMountName), - SaveDataSpaceId.ProperSystem, SaveDataId, MemoryResource); + indexer = _properSystemIndexer.Indexer; + break; + case SaveDataSpaceId.SafeMode: + indexerLock = new UniqueLock(_safeIndexer.Locker); - neededInit = true; - } + if (!_safeIndexer.IsInitialized) + { + _safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName), + SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource); - accessor = new SaveDataIndexerAccessor(_properSystemIndexer.Indexer, _properSystemIndexer.Locker); - return Result.Success; + neededInit = true; + } - case SaveDataSpaceId.SafeMode: - Monitor.Enter(_safeIndexer.Locker); + indexer = _safeIndexer.Indexer; + break; - if (!_safeIndexer.IsInitialized) - { - _safeIndexer.Indexer = new SaveDataIndexer(FsClient, new U8Span(SafeModeIndexerMountName), - SaveDataSpaceId.SafeMode, SaveDataId, MemoryResource); + default: + accessor = default; + return ResultFs.InvalidArgument.Log(); + } - neededInit = true; - } - accessor = new SaveDataIndexerAccessor(_safeIndexer.Indexer, _safeIndexer.Locker); - return Result.Success; - - default: - accessor = default; - return ResultFs.InvalidArgument.Log(); + accessor = new SaveDataIndexerAccessor(indexer, ref indexerLock); + return Result.Success; + } + finally + { + indexerLock.Dispose(); } } - public void ResetTemporaryStorageIndexer(SaveDataSpaceId spaceId) + public void ResetIndexer(SaveDataSpaceId spaceId) { if (spaceId != SaveDataSpaceId.Temporary) { @@ -156,7 +171,7 @@ namespace LibHac.FsSrv Assert.AssertTrue(rc.IsSuccess()); } - public void InvalidateSdCardIndexer(SaveDataSpaceId spaceId) + public void InvalidateIndexer(SaveDataSpaceId spaceId) { // Note: Nintendo doesn't lock when doing this operation lock (_sdCardIndexer.Locker) @@ -225,24 +240,17 @@ namespace LibHac.FsSrv public class SaveDataIndexerAccessor : IDisposable { public ISaveDataIndexer Indexer { get; } - private object Locker { get; } - private bool HasLock { get; set; } + private UniqueLock _locker; - public SaveDataIndexerAccessor(ISaveDataIndexer indexer, object locker) + public SaveDataIndexerAccessor(ISaveDataIndexer indexer, ref UniqueLock locker) { Indexer = indexer; - Locker = locker; - HasLock = true; + _locker = new UniqueLock(ref locker); } public void Dispose() { - if (HasLock) - { - Monitor.Exit(Locker); - - HasLock = false; - } + _locker.Dispose(); } } } diff --git a/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs b/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs index bbdbf796..5b2a63a5 100644 --- a/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs +++ b/src/LibHac/FsSrv/SaveDataInfoFilterReader.cs @@ -2,41 +2,45 @@ using System.Runtime.InteropServices; using LibHac.Common; using LibHac.Fs; +using LibHac.FsSrv.Sf; using LibHac.Ncm; +using LibHac.Sf; +using LibHac.Util; namespace LibHac.FsSrv { - internal class SaveDataInfoFilterReader : ISaveDataInfoReader + internal class SaveDataInfoFilterReader : SaveDataInfoReaderImpl, ISaveDataInfoReader { - private ReferenceCountedDisposable Reader { get; } - private SaveDataFilterInternal Filter { get; } + private ReferenceCountedDisposable Reader { get; } + private SaveDataInfoFilter InfoFilter { get; } - public SaveDataInfoFilterReader(ReferenceCountedDisposable reader, ref SaveDataFilterInternal filter) + public SaveDataInfoFilterReader(ReferenceCountedDisposable reader, + in SaveDataInfoFilter infoFilter) { - Reader = reader; - Filter = filter; + Reader = reader.AddReference(); + InfoFilter = infoFilter; } - public Result Read(out long readCount, Span saveDataInfoBuffer) + public Result Read(out long readCount, OutBuffer saveDataInfoBuffer) { readCount = default; - Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer); + Span outInfo = MemoryMarshal.Cast(saveDataInfoBuffer.Buffer); SaveDataInfo tempInfo = default; Span tempInfoBytes = SpanHelpers.AsByteSpan(ref tempInfo); - ISaveDataInfoReader reader = Reader.Target; + SaveDataInfoReaderImpl reader = Reader.Target; int count = 0; while (count < outInfo.Length) { - Result rc = reader.Read(out long baseReadCount, tempInfoBytes); + Result rc = reader.Read(out long baseReadCount, new OutBuffer(tempInfoBytes)); if (rc.IsFailure()) return rc; if (baseReadCount == 0) break; - if (Filter.Matches(ref tempInfo)) + if (InfoFilter.Includes(in tempInfo)) { outInfo[count] = tempInfo; @@ -55,133 +59,97 @@ namespace LibHac.FsSrv } } - [StructLayout(LayoutKind.Explicit, Size = 0x50)] - internal struct SaveDataFilterInternal + [StructLayout(LayoutKind.Sequential, Size = 0x60)] + internal struct SaveDataInfoFilter { - [FieldOffset(0x00)] public bool FilterBySaveDataSpaceId; - [FieldOffset(0x01)] public SaveDataSpaceId SpaceId; + public Optional SpaceId; + public Optional ProgramId; + public Optional SaveDataType; + public Optional UserId; + public Optional SaveDataId; + public Optional Index; + public int Rank; - [FieldOffset(0x08)] public bool FilterByProgramId; - [FieldOffset(0x10)] public ProgramId ProgramId; - - [FieldOffset(0x18)] public bool FilterBySaveDataType; - [FieldOffset(0x19)] public SaveDataType SaveDataType; - - [FieldOffset(0x20)] public bool FilterByUserId; - [FieldOffset(0x28)] public UserId UserId; - - [FieldOffset(0x38)] public bool FilterBySaveDataId; - [FieldOffset(0x40)] public ulong SaveDataId; - - [FieldOffset(0x48)] public bool FilterByIndex; - [FieldOffset(0x4A)] public short Index; - - [FieldOffset(0x4C)] public int Rank; - - public SaveDataFilterInternal(ref SaveDataFilter filter, SaveDataSpaceId spaceId) + public SaveDataInfoFilter(in SaveDataInfoFilter filter) { - this = default; + this = filter; + } - FilterBySaveDataSpaceId = true; - SpaceId = spaceId; + public SaveDataInfoFilter(SaveDataSpaceId spaceId, in SaveDataFilter filter) + { + // Start out with no optional values + this = new SaveDataInfoFilter(); + SpaceId = new Optional(spaceId); Rank = (int)filter.Rank; if (filter.FilterByProgramId) { - FilterByProgramId = true; - ProgramId = filter.ProgramId; + ProgramId = new Optional(in filter.Attribute.ProgramId); } if (filter.FilterBySaveDataType) { - FilterBySaveDataType = true; - SaveDataType = filter.SaveDataType; + SaveDataType = new Optional(in filter.Attribute.Type); } if (filter.FilterByUserId) { - FilterByUserId = true; - UserId = filter.UserId; + UserId = new Optional(in filter.Attribute.UserId); } if (filter.FilterBySaveDataId) { - FilterBySaveDataId = true; - SaveDataId = filter.SaveDataId; + SaveDataId = new Optional(in filter.Attribute.StaticSaveDataId); } if (filter.FilterByIndex) { - FilterByIndex = true; - Index = filter.Index; + Index = new Optional(in filter.Attribute.Index); } } - public void SetSaveDataSpaceId(SaveDataSpaceId spaceId) + public SaveDataInfoFilter(Optional spaceId, Optional programId, + Optional saveDataType, Optional userId, Optional saveDataId, + Optional index, int rank) { - FilterBySaveDataSpaceId = true; SpaceId = spaceId; + ProgramId = programId; + SaveDataType = saveDataType; + UserId = userId; + SaveDataId = saveDataId; + Index = index; + Rank = rank; } - public void SetProgramId(ProgramId value) + public bool Includes(in SaveDataInfo saveInfo) { - FilterByProgramId = true; - ProgramId = value; - } - - public void SetSaveDataType(SaveDataType value) - { - FilterBySaveDataType = true; - SaveDataType = value; - } - - public void SetUserId(UserId value) - { - FilterByUserId = true; - UserId = value; - } - - public void SetSaveDataId(ulong value) - { - FilterBySaveDataId = true; - SaveDataId = value; - } - - public void SetIndex(short value) - { - FilterByIndex = true; - Index = value; - } - - public bool Matches(ref SaveDataInfo info) - { - if (FilterBySaveDataSpaceId && info.SpaceId != SpaceId) + if (SpaceId.HasValue && saveInfo.SpaceId != SpaceId.Value) { return false; } - if (FilterByProgramId && info.ProgramId != ProgramId) + if (ProgramId.HasValue && saveInfo.ProgramId != ProgramId.Value) { return false; } - if (FilterBySaveDataType && info.Type != SaveDataType) + if (SaveDataType.HasValue && saveInfo.Type != SaveDataType.Value) { return false; } - if (FilterByUserId && info.UserId != UserId) + if (UserId.HasValue && saveInfo.UserId != UserId.Value) { return false; } - if (FilterBySaveDataId && info.SaveDataId != SaveDataId) + if (SaveDataId.HasValue && saveInfo.SaveDataId != SaveDataId.Value) { return false; } - if (FilterByIndex && info.Index != Index) + if (Index.HasValue && saveInfo.Index != Index.Value) { return false; } @@ -189,7 +157,7 @@ namespace LibHac.FsSrv var filterRank = (SaveDataRank)(Rank & 1); // When filtering by secondary rank, match on both primary and secondary ranks - if (filterRank == SaveDataRank.Primary && info.Rank == SaveDataRank.Secondary) + if (filterRank == SaveDataRank.Primary && saveInfo.Rank == SaveDataRank.Secondary) { return false; } diff --git a/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs b/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs new file mode 100644 index 00000000..eef02a62 --- /dev/null +++ b/src/LibHac/FsSrv/SaveDataInfoReaderImpl.cs @@ -0,0 +1,28 @@ +using System; +using LibHac.Fs; +using LibHac.Sf; + +namespace LibHac.FsSrv +{ + /// + /// Iterates through the of the save data + /// in a single save data space. + /// + // ReSharper disable once InconsistentNaming + // Kinda weird to name an interface / pure abstract class SaveDataInfoReaderImpl. Ask Nintendo, not me. + // + public interface SaveDataInfoReaderImpl : IDisposable + { + /// + /// Returns the next entries. This method will continue writing + /// entries to until there is either no more space + /// in the buffer, or until there are no more entries to iterate. + /// + /// If the method returns successfully, contains the number + /// of written to . + /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. + /// The buffer in which to write the . + /// The of the operation. + Result Read(out long readCount, OutBuffer saveDataInfoBuffer); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs b/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs new file mode 100644 index 00000000..ebbc3501 --- /dev/null +++ b/src/LibHac/FsSrv/SaveDataTransferCryptoConfiguration.cs @@ -0,0 +1,35 @@ +using System; +using LibHac.Crypto; + +namespace LibHac.FsSrv +{ + public class SaveDataTransferCryptoConfiguration + { + private Data100 _tokenSigningKeyModulus; + private Data100 _keySeedPackageSigningKeyModulus; + private Data100 _kekEncryptionKeyModulus; + private Data100 _keyPackageSigningModulus; + + public Span TokenSigningKeyModulus => _tokenSigningKeyModulus.Data; + public Span KeySeedPackageSigningKeyModulus => _keySeedPackageSigningKeyModulus.Data; + public Span KekEncryptionKeyModulus => _kekEncryptionKeyModulus.Data; + public Span KeyPackageSigningModulus => _keyPackageSigningModulus.Data; + + public SaveTransferAesKeyGenerator GenerateAesKey { get; set; } + public RandomDataGenerator GenerateRandomData { get; set; } + public SaveTransferCmacGenerator GenerateCmac { get; set; } + + public enum KeyIndex + { + SaveDataTransferToken, + SaveDataTransfer, + SaveDataTransferKeySeedPackage, + CloudBackUpInitialData, + CloudBackUpImportContext, + CloudBackUpInitialDataMac, + SaveDataRepairKeyPackage, + SaveDataRepairInitialDataMacBeforeRepair, + SaveDataRepairInitialDataMacAfterRepair + } + } +} diff --git a/src/LibHac/FsSrv/Sf/FspPath.cs b/src/LibHac/FsSrv/Sf/FspPath.cs index fe314ada..761507c0 100644 --- a/src/LibHac/FsSrv/Sf/FspPath.cs +++ b/src/LibHac/FsSrv/Sf/FspPath.cs @@ -38,6 +38,12 @@ namespace LibHac.FsSrv.Sf return overflowed ? ResultFs.TooLongPath.Log() : Result.Success; } + public static void CreateEmpty(out FspPath fspPath) + { + Unsafe.SkipInit(out fspPath); + SpanHelpers.AsByteSpan(ref fspPath)[0] = 0; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator U8Span(in FspPath value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); diff --git a/src/LibHac/FsSrv/IDeviceOperator.cs b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs similarity index 66% rename from src/LibHac/FsSrv/IDeviceOperator.cs rename to src/LibHac/FsSrv/Sf/IDeviceOperator.cs index aa855d58..f8185a1c 100644 --- a/src/LibHac/FsSrv/IDeviceOperator.cs +++ b/src/LibHac/FsSrv/Sf/IDeviceOperator.cs @@ -1,6 +1,8 @@ -namespace LibHac.FsSrv +using System; + +namespace LibHac.FsSrv.Sf { - public interface IDeviceOperator + public interface IDeviceOperator : IDisposable { Result IsSdCardInserted(out bool isInserted); Result IsGameCardInserted(out bool isInserted); diff --git a/src/LibHac/FsSrv/Sf/IDirectory.cs b/src/LibHac/FsSrv/Sf/IDirectory.cs new file mode 100644 index 00000000..086e9e9a --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IDirectory.cs @@ -0,0 +1,11 @@ +using System; +using LibHac.Sf; + +namespace LibHac.FsSrv.Sf +{ + public interface IDirectory : IDisposable + { + Result Read(out long entriesRead, OutBuffer entryBuffer); + Result GetEntryCount(out long entryCount); + } +} diff --git a/src/LibHac/FsSrv/Sf/IEventNotifier.cs b/src/LibHac/FsSrv/Sf/IEventNotifier.cs new file mode 100644 index 00000000..2097a577 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IEventNotifier.cs @@ -0,0 +1,10 @@ +using System; +using LibHac.Sf; + +namespace LibHac.FsSrv.Sf +{ + public interface IEventNotifier : IDisposable + { + Result GetEventHandle(out NativeHandle handle); + } +} diff --git a/src/LibHac/FsSrv/Sf/IFile.cs b/src/LibHac/FsSrv/Sf/IFile.cs new file mode 100644 index 00000000..3e35af92 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IFile.cs @@ -0,0 +1,15 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsSrv.Sf +{ + public interface IFile : IDisposable + { + Result Read(out long bytesRead, long offset, Span destination, ReadOption option); + Result Write(long offset, ReadOnlySpan source, WriteOption option); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); + } +} diff --git a/src/LibHac/FsSrv/Sf/IFileSystem.cs b/src/LibHac/FsSrv/Sf/IFileSystem.cs new file mode 100644 index 00000000..268cd40a --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IFileSystem.cs @@ -0,0 +1,28 @@ +using System; +using LibHac.Fs; +using IFileSf = LibHac.FsSrv.Sf.IFile; +using IDirectorySf = LibHac.FsSrv.Sf.IDirectory; + +namespace LibHac.FsSrv.Sf +{ + public interface IFileSystem : IDisposable + { + Result GetImpl(out ReferenceCountedDisposable fileSystem); + Result CreateFile(in Path path, long size, int option); + Result DeleteFile(in Path path); + Result CreateDirectory(in Path path); + Result DeleteDirectory(in Path path); + Result DeleteDirectoryRecursively(in Path path); + Result RenameFile(in Path oldPath, in Path newPath); + Result RenameDirectory(in Path oldPath, in Path newPath); + Result GetEntryType(out uint entryType, in Path path); + Result OpenFile(out ReferenceCountedDisposable file, in Path path, uint mode); + Result OpenDirectory(out ReferenceCountedDisposable directory, in Path path, uint mode); + Result Commit(); + Result GetFreeSpaceSize(out long freeSpace, in Path path); + Result GetTotalSpaceSize(out long totalSpace, in Path path); + Result CleanDirectoryRecursively(in Path path); + Result GetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path); + Result QueryEntry(Span outBuffer, ReadOnlySpan inBuffer, int queryId, in Path path); + } +} diff --git a/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs index 8b772555..4e03762b 100644 --- a/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs +++ b/src/LibHac/FsSrv/Sf/IFileSystemProxyForLoader.cs @@ -1,13 +1,13 @@ using LibHac.Fs; -using LibHac.Fs.Fsa; using LibHac.Ncm; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; namespace LibHac.FsSrv.Sf { public interface IFileSystemProxyForLoader { - Result OpenCodeFileSystem(out IFileSystem fileSystem, out CodeVerificationData verificationData, - in FspPath path, ProgramId programId); + Result OpenCodeFileSystem(out ReferenceCountedDisposable fileSystem, + out CodeVerificationData verificationData, in FspPath path, ProgramId programId); Result IsArchivedProgram(out bool isArchived, ulong processId); Result SetCurrentProcess(ulong processId); diff --git a/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs b/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs new file mode 100644 index 00000000..4406af0f --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IMultiCommitManager.cs @@ -0,0 +1,11 @@ +using System; +using IFileSystemSf = LibHac.FsSrv.Sf.IFileSystem; + +namespace LibHac.FsSrv.Sf +{ + public interface IMultiCommitManager : IDisposable + { + Result Add(ReferenceCountedDisposable fileSystem); + Result Commit(); + } +} diff --git a/src/LibHac/FsSrv/Sf/IProgramRegistry.cs b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs index a4724dd7..78e34586 100644 --- a/src/LibHac/FsSrv/Sf/IProgramRegistry.cs +++ b/src/LibHac/FsSrv/Sf/IProgramRegistry.cs @@ -1,12 +1,12 @@ -using System; -using LibHac.Ncm; +using LibHac.Ncm; +using LibHac.Sf; namespace LibHac.FsSrv.Sf { public interface IProgramRegistry { Result RegisterProgram(ulong processId, ProgramId programId, StorageId storageId, - ReadOnlySpan accessControlData, ReadOnlySpan accessControlDescriptor); + InBuffer accessControlData, InBuffer accessControlDescriptor); Result UnregisterProgram(ulong processId); Result SetCurrentProcess(ulong processId); diff --git a/src/LibHac/FsSrv/ISaveDataInfoReader.cs b/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs similarity index 90% rename from src/LibHac/FsSrv/ISaveDataInfoReader.cs rename to src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs index 51e6ef47..8512a22f 100644 --- a/src/LibHac/FsSrv/ISaveDataInfoReader.cs +++ b/src/LibHac/FsSrv/Sf/ISaveDataInfoReader.cs @@ -1,7 +1,8 @@ using System; using LibHac.Fs; +using LibHac.Sf; -namespace LibHac.FsSrv +namespace LibHac.FsSrv.Sf { /// /// Iterates through the of the save data @@ -19,6 +20,6 @@ namespace LibHac.FsSrv /// A value of 0 indicates that there are no more entries to iterate, or the buffer is too small. /// The buffer in which to write the . /// The of the operation. - Result Read(out long readCount, Span saveDataInfoBuffer); + Result Read(out long readCount, OutBuffer saveDataInfoBuffer); } -} \ No newline at end of file +} diff --git a/src/LibHac/FsSrv/Sf/ISaveDataMover.cs b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs new file mode 100644 index 00000000..8e26bddd --- /dev/null +++ b/src/LibHac/FsSrv/Sf/ISaveDataMover.cs @@ -0,0 +1,11 @@ +using System; + +namespace LibHac.FsSrv.Sf +{ + public interface ISaveDataMover : IDisposable + { + Result Register(ulong saveDataId); + Result Process(out long remainingSize, long sizeToProcess); + Result Cancel(); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Sf/IStorage.cs b/src/LibHac/FsSrv/Sf/IStorage.cs new file mode 100644 index 00000000..264dbd49 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IStorage.cs @@ -0,0 +1,15 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsSrv.Sf +{ + public interface IStorage : IDisposable + { + Result Read(long offset, Span destination); + Result Write(long offset, ReadOnlySpan source); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); + } +} diff --git a/src/LibHac/FsSrv/Sf/IWiper.cs b/src/LibHac/FsSrv/Sf/IWiper.cs new file mode 100644 index 00000000..b19b0a7b --- /dev/null +++ b/src/LibHac/FsSrv/Sf/IWiper.cs @@ -0,0 +1,10 @@ +using System; + +namespace LibHac.FsSrv.Sf +{ + public interface IWiper : IDisposable + { + public Result Startup(out long spaceToWipe); + public Result Process(out long remainingSpaceToWipe); + } +} diff --git a/src/LibHac/FsSrv/Sf/Path.cs b/src/LibHac/FsSrv/Sf/Path.cs new file mode 100644 index 00000000..c714a1d7 --- /dev/null +++ b/src/LibHac/FsSrv/Sf/Path.cs @@ -0,0 +1,21 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Fs; + +namespace LibHac.FsSrv.Sf +{ + [StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax + 1)] + public readonly struct Path + { +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly byte Padding300; +#endif + + public ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + } +} diff --git a/src/LibHac/FsSrv/Storage/IGameCardDeviceManager.cs b/src/LibHac/FsSrv/Storage/IGameCardDeviceManager.cs new file mode 100644 index 00000000..f2bf4720 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/IGameCardDeviceManager.cs @@ -0,0 +1,15 @@ +using System; +using LibHac.Fs.Impl; + +namespace LibHac.FsSrv.Storage +{ + internal interface IGameCardDeviceManager + { + Result AcquireReadLock(out UniqueLock locker, uint handle); + Result AcquireReadLockSecureMode(out UniqueLock locker, ref uint handle, ReadOnlySpan cardDeviceId, ReadOnlySpan cardImageHash); + Result AcquireWriteLock(out SharedLock locker); + Result HandleGameCardAccessResult(Result result); + Result GetHandle(out uint handle); + bool IsSecureMode(); + } +} diff --git a/src/LibHac/FsSrv/Storage/IGameCardKeyManager.cs b/src/LibHac/FsSrv/Storage/IGameCardKeyManager.cs new file mode 100644 index 00000000..3c9b507b --- /dev/null +++ b/src/LibHac/FsSrv/Storage/IGameCardKeyManager.cs @@ -0,0 +1,9 @@ +using System; + +namespace LibHac.FsSrv.Storage +{ + public interface IGameCardKeyManager + { + void PresetInternalKeys(ReadOnlySpan gameCardKey, ReadOnlySpan gameCardCertificate); + } +} diff --git a/src/LibHac/FsSrv/Storage/ISdmmcDeviceManager.cs b/src/LibHac/FsSrv/Storage/ISdmmcDeviceManager.cs new file mode 100644 index 00000000..dfa07375 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/ISdmmcDeviceManager.cs @@ -0,0 +1,12 @@ +using LibHac.Fs; + +namespace LibHac.FsSrv.Storage +{ + internal interface ISdmmcDeviceManager + { + Result Lock(out object locker, uint handle); + IStorage GetStorage(); + SdmmcPort GetPortId(); + Result NotifyCloseStorageDevice(uint handle); + } +} diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs new file mode 100644 index 00000000..b133fc87 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDevice.cs @@ -0,0 +1,18 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsSrv.Storage.Sf +{ + public interface IStorageDevice : IDisposable + { + Result GetHandle(out uint handle); + Result IsHandleValid(out bool isValid); + Result OpenOperator(out ReferenceCountedDisposable deviceOperator); + Result Read(long offset, Span destination); + Result Write(long offset, ReadOnlySpan source); + Result Flush(); + Result SetSize(long size); + Result GetSize(out long size); + Result OperateRange(out QueryRangeInfo rangeInfo, int operationId, long offset, long size); + } +} diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs new file mode 100644 index 00000000..7a7539a5 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceManager.cs @@ -0,0 +1,21 @@ +using System; +using LibHac.FsSrv.Sf; +using IStorage = LibHac.Fs.IStorage; + +namespace LibHac.FsSrv.Storage.Sf +{ + public interface IStorageDeviceManager : IDisposable + { + Result IsInserted(out bool isInserted); + Result IsHandleValid(out bool isValid, uint handle); + Result OpenDetectionEvent(out ReferenceCountedDisposable eventNotifier); + Result OpenOperator(out ReferenceCountedDisposable deviceOperator); + Result OpenDevice(out ReferenceCountedDisposable storageDevice, ulong attribute); + Result OpenStorage(out ReferenceCountedDisposable storage, ulong attribute); + Result PutToSleep(); + Result Awaken(); + Result Initialize(); + Result Shutdown(); + Result Invalidate(); + } +} diff --git a/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs new file mode 100644 index 00000000..33a4e449 --- /dev/null +++ b/src/LibHac/FsSrv/Storage/Sf/IStorageDeviceOperator.cs @@ -0,0 +1,14 @@ +using System; + +namespace LibHac.FsSrv.Storage.Sf +{ + public interface IStorageDeviceOperator : IDisposable + { + Result Operate(uint operationId); + Result OperateIn(ReadOnlySpan buffer, long offset, long size, uint operationId); + Result OperateOut(out long bytesWritten, Span buffer, long offset, long size, uint operationId); + Result OperateOut2(out long bytesWrittenBuffer1, Span buffer1, out long bytesWrittenBuffer2, Span buffer2, uint operationId); + Result OperateInOut(out long bytesWritten, Span outBuffer, ReadOnlySpan inBuffer, long offset, long size, uint operationId); + Result OperateIn2Out(out long bytesWritten, Span outBuffer, ReadOnlySpan inBuffer1, ReadOnlySpan inBuffer2, long offset, long size, uint operationId); + } +} diff --git a/src/LibHac/FsSrv/TimeService.cs b/src/LibHac/FsSrv/TimeService.cs new file mode 100644 index 00000000..4776b25e --- /dev/null +++ b/src/LibHac/FsSrv/TimeService.cs @@ -0,0 +1,77 @@ +using System; +using LibHac.Fs; +using LibHac.FsSrv.Impl; + +namespace LibHac.FsSrv +{ + public readonly struct TimeService + { + private readonly TimeServiceImpl _serviceImpl; + private readonly ulong _processId; + + public TimeService(TimeServiceImpl serviceImpl, ulong processId) + { + _serviceImpl = serviceImpl; + _processId = processId; + } + + public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) + { + Result rc = GetProgramInfo(out ProgramInfo programInfo); + if (rc.IsFailure()) return rc; + + if (!programInfo.AccessControl.CanCall(OperationType.SetCurrentPosixTime)) + return ResultFs.PermissionDenied.Log(); + + return _serviceImpl.SetCurrentPosixTimeWithTimeDifference(currentTime, timeDifference); + } + + private Result GetProgramInfo(out ProgramInfo programInfo) + { + return _serviceImpl.GetProgramInfo(out programInfo, _processId); + } + } + + public class TimeServiceImpl + { + private Configuration _config; + private long _baseTime; + private int _timeDifference; + private object _lockObject; + + public TimeServiceImpl(in Configuration configuration) + { + _config = configuration; + _baseTime = 0; + _timeDifference = 0; + _lockObject = new object(); + } + + // The entire Configuration struct is a LibHac addition to avoid using global state + public struct Configuration + { + public HorizonClient HorizonClient; + public ProgramRegistryImpl ProgramRegistry; + } + + public Result GetCurrentPosixTime(out long time) + { + throw new NotImplementedException(); + } + + public Result GetCurrentPosixTimeWithTimeDifference(out long currentTime, out int timeDifference) + { + throw new NotImplementedException(); + } + + public Result SetCurrentPosixTimeWithTimeDifference(long currentTime, int timeDifference) + { + throw new NotImplementedException(); + } + + internal Result GetProgramInfo(out ProgramInfo programInfo, ulong processId) + { + return _config.ProgramRegistry.GetProgramInfo(out programInfo, processId); + } + } +} diff --git a/src/LibHac/FsSrv/Util.cs b/src/LibHac/FsSrv/Util.cs index 852b101f..3e2c9641 100644 --- a/src/LibHac/FsSrv/Util.cs +++ b/src/LibHac/FsSrv/Util.cs @@ -47,7 +47,7 @@ namespace LibHac.FsSrv public static Result VerifyHostPath(U8Span path) { - if(path.IsEmpty()) + if (path.IsEmpty()) return Result.Success; if (path[0] != StringTraits.DirectorySeparator) @@ -55,7 +55,7 @@ namespace LibHac.FsSrv U8Span path2 = path.Slice(1); - if(path2.IsEmpty()) + if (path2.IsEmpty()) return Result.Success; int skipLength = PathUtility.GetWindowsPathSkipLength(path2); diff --git a/src/LibHac/FsSystem/AesXtsFileSystem.cs b/src/LibHac/FsSystem/AesXtsFileSystem.cs index 94b6d88a..41cc8320 100644 --- a/src/LibHac/FsSystem/AesXtsFileSystem.cs +++ b/src/LibHac/FsSystem/AesXtsFileSystem.cs @@ -11,14 +11,16 @@ namespace LibHac.FsSystem public int BlockSize { get; } private IFileSystem BaseFileSystem { get; } + private ReferenceCountedDisposable SharedBaseFileSystem { get; } private byte[] KekSource { get; } private byte[] ValidationKey { get; } - public AesXtsFileSystem(IFileSystem fs, byte[] kekSource, byte[] validationKey, int blockSize) + public AesXtsFileSystem(ReferenceCountedDisposable fs, byte[] keys, int blockSize) { - BaseFileSystem = fs; - KekSource = kekSource; - ValidationKey = validationKey; + SharedBaseFileSystem = fs.AddReference(); + BaseFileSystem = SharedBaseFileSystem.Target; + KekSource = keys.AsSpan(0, 0x10).ToArray(); + ValidationKey = keys.AsSpan(0x10, 0x10).ToArray(); BlockSize = blockSize; } @@ -30,6 +32,16 @@ namespace LibHac.FsSystem BlockSize = blockSize; } + protected override void Dispose(bool disposing) + { + if (disposing) + { + SharedBaseFileSystem?.Dispose(); + } + + base.Dispose(disposing); + } + protected override Result DoCreateDirectory(U8Span path) { return BaseFileSystem.CreateDirectory(path); diff --git a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs new file mode 100644 index 00000000..831663cc --- /dev/null +++ b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs @@ -0,0 +1,90 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSystem +{ + public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAccessor + { + protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteFile(U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoCreateDirectory(U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectory(U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoDeleteDirectoryRecursively(U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoCleanDirectoryRecursively(U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) + { + throw new NotImplementedException(); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) + { + throw new NotImplementedException(); + } + + protected override Result DoCommit() + { + throw new NotImplementedException(); + } + + public Result WriteExtraData(in SaveDataExtraData extraData) + { + throw new NotImplementedException(); + } + + public Result CommitExtraData(bool updateTimeStamp) + { + throw new NotImplementedException(); + } + + public Result ReadExtraData(out SaveDataExtraData extraData) + { + throw new NotImplementedException(); + } + + public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/LibHac/FsSystem/ForwardingFileSystem.cs b/src/LibHac/FsSystem/ForwardingFileSystem.cs new file mode 100644 index 00000000..a230168c --- /dev/null +++ b/src/LibHac/FsSystem/ForwardingFileSystem.cs @@ -0,0 +1,83 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSystem +{ + public class ForwardingFileSystem : IFileSystem + { + private ReferenceCountedDisposable BaseFileSystem { get; set; } + + public ForwardingFileSystem(ReferenceCountedDisposable baseFileSystem) + { + BaseFileSystem = baseFileSystem.AddReference(); + } + + protected ForwardingFileSystem(ref ReferenceCountedDisposable baseFileSystem) + { + BaseFileSystem = Shared.Move(ref baseFileSystem); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseFileSystem?.Dispose(); + } + + base.Dispose(disposing); + } + + protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) => + BaseFileSystem.Target.CreateFile(path, size, option); + + protected override Result DoDeleteFile(U8Span path) => BaseFileSystem.Target.DeleteFile(path); + + protected override Result DoCreateDirectory(U8Span path) => BaseFileSystem.Target.CreateDirectory(path); + + protected override Result DoDeleteDirectory(U8Span path) => BaseFileSystem.Target.DeleteDirectory(path); + + protected override Result DoDeleteDirectoryRecursively(U8Span path) => + BaseFileSystem.Target.DeleteDirectoryRecursively(path); + + protected override Result DoCleanDirectoryRecursively(U8Span path) => + BaseFileSystem.Target.CleanDirectoryRecursively(path); + + protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => + BaseFileSystem.Target.RenameFile(oldPath, newPath); + + protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => + BaseFileSystem.Target.RenameDirectory(oldPath, newPath); + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) => + BaseFileSystem.Target.GetEntryType(out entryType, path); + + protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) => + BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path); + + protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) => + BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path); + + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) => + BaseFileSystem.Target.OpenFile(out file, path, mode); + + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) => + BaseFileSystem.Target.OpenDirectory(out directory, path, mode); + + protected override Result DoCommit() => BaseFileSystem.Target.Commit(); + + protected override Result DoCommitProvisionally(long counter) => + BaseFileSystem.Target.CommitProvisionally(counter); + + protected override Result DoRollback() => BaseFileSystem.Target.Rollback(); + + protected override Result DoFlush() => BaseFileSystem.Target.Flush(); + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) => + BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path); + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, + U8Span path) => BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path); + } +} diff --git a/src/LibHac/FsSystem/Hash.cs b/src/LibHac/FsSystem/Hash.cs new file mode 100644 index 00000000..f4e0503f --- /dev/null +++ b/src/LibHac/FsSystem/Hash.cs @@ -0,0 +1,17 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using LibHac.Common; + +namespace LibHac.FsSystem +{ + [StructLayout(LayoutKind.Sequential, Size = 0x20)] + public struct Hash + { + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy0; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private ulong _dummy1; + + public readonly ReadOnlySpan Bytes => SpanHelpers.AsReadOnlyByteSpan(in this); + public Span BytesMutable => SpanHelpers.AsByteSpan(ref this); + } +} diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs new file mode 100644 index 00000000..9b05ec5e --- /dev/null +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs @@ -0,0 +1,13 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsSystem +{ + public interface ISaveDataExtraDataAccessor : IDisposable + { + Result WriteExtraData(in SaveDataExtraData extraData); + Result CommitExtraData(bool updateTimeStamp); + Result ReadExtraData(out SaveDataExtraData extraData); + void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs new file mode 100644 index 00000000..433940ef --- /dev/null +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs @@ -0,0 +1,10 @@ +using System; +using LibHac.Fs; + +namespace LibHac.FsSystem +{ + public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable + { + void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs new file mode 100644 index 00000000..acfe5ba1 --- /dev/null +++ b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs @@ -0,0 +1,14 @@ +using System; +using LibHac.Fs; +using LibHac.FsSystem.Save; + +namespace LibHac.FsSystem +{ + public interface ISaveDataFileSystemCacheManager : IDisposable + { + bool GetCache(out ReferenceCountedDisposable fileSystem, SaveDataSpaceId spaceId, ulong saveDataId); + void Register(ReferenceCountedDisposable fileSystem); + void Register(ReferenceCountedDisposable fileSystem); + void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/LocalFileSystem.cs b/src/LibHac/FsSystem/LocalFileSystem.cs index 12906dfb..24664610 100644 --- a/src/LibHac/FsSystem/LocalFileSystem.cs +++ b/src/LibHac/FsSystem/LocalFileSystem.cs @@ -61,7 +61,7 @@ namespace LibHac.FsSystem if (PathTool.IsSubpath(normalizedPath1, normalizedPath2)) { - return ResultFs.DestinationIsSubPathOfSource.Log(); + return ResultFs.DirectoryNotRenamable.Log(); } return Result.Success; diff --git a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs index fe4a2977..a5dd5c8d 100644 --- a/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs +++ b/src/LibHac/FsSystem/NcaUtils/NcaStructs.cs @@ -39,7 +39,7 @@ Code, Data, Logo - }; + } public enum NcaContentType { diff --git a/src/LibHac/FsSystem/PartitionFileSystemCore.cs b/src/LibHac/FsSystem/PartitionFileSystemCore.cs index 9ee82613..ad6bf1a7 100644 --- a/src/LibHac/FsSystem/PartitionFileSystemCore.cs +++ b/src/LibHac/FsSystem/PartitionFileSystemCore.cs @@ -15,6 +15,16 @@ namespace LibHac.FsSystem private PartitionFileSystemMetaCore MetaData { get; set; } private bool IsInitialized { get; set; } private int DataOffset { get; set; } + private ReferenceCountedDisposable BaseStorageShared { get; set; } + + public Result Initialize(ReferenceCountedDisposable baseStorage) + { + Result rc = Initialize(baseStorage.Target); + if (rc.IsFailure()) return rc; + + BaseStorageShared = baseStorage.AddReference(); + return Result.Success; + } public Result Initialize(IStorage baseStorage) { @@ -33,6 +43,16 @@ namespace LibHac.FsSystem return Result.Success; } + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseStorageShared?.Dispose(); + } + + base.Dispose(disposing); + } + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) { directory = default; diff --git a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs index f7addcfc..27cf5e29 100644 --- a/src/LibHac/FsSystem/ReadOnlyFileSystem.cs +++ b/src/LibHac/FsSystem/ReadOnlyFileSystem.cs @@ -7,12 +7,27 @@ namespace LibHac.FsSystem public class ReadOnlyFileSystem : IFileSystem { private IFileSystem BaseFs { get; } + private ReferenceCountedDisposable BaseFsShared { get; } + // Todo: Remove non-shared constructor public ReadOnlyFileSystem(IFileSystem baseFileSystem) { BaseFs = baseFileSystem; } + public ReadOnlyFileSystem(ReferenceCountedDisposable baseFileSystem) + { + BaseFsShared = baseFileSystem; + BaseFs = BaseFsShared.Target; + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem) + { + var fs = new ReadOnlyFileSystem(Shared.Move(ref baseFileSystem)); + return new ReferenceCountedDisposable(fs); + } + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) { return BaseFs.OpenDirectory(out directory, path, mode); @@ -36,11 +51,7 @@ namespace LibHac.FsSystem protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) { - freeSpace = 0; - return Result.Success; - - // FS does: - // return ResultFs.UnsupportedOperationReadOnlyFileSystemGetSpace.Log(); + return BaseFs.GetFreeSpaceSize(out freeSpace, path); } protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) @@ -79,5 +90,15 @@ namespace LibHac.FsSystem protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log(); protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) => ResultFs.UnsupportedOperationModifyReadOnlyFileSystem.Log(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseFsShared?.Dispose(); + } + + base.Dispose(disposing); + } } } diff --git a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs index 31a4764d..a79ee565 100644 --- a/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs +++ b/src/LibHac/FsSystem/Save/HierarchicalSaveFileTable.cs @@ -347,7 +347,7 @@ namespace LibHac.FsSystem.Save if (PathTools.IsSubPath(srcPath, dstPath)) { - return ResultFs.DestinationIsSubPathOfSource.Log(); + return ResultFs.DirectoryNotRenamable.Log(); } if (oldKey.Parent != newKey.Parent) diff --git a/src/LibHac/FsSystem/Save/ISaveDataExtraDataAccessor.cs b/src/LibHac/FsSystem/Save/ISaveDataExtraDataAccessor.cs deleted file mode 100644 index 707e172a..00000000 --- a/src/LibHac/FsSystem/Save/ISaveDataExtraDataAccessor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace LibHac.FsSystem.Save -{ - public interface ISaveDataExtraDataAccessor - { - Result Write(ExtraData data); - Result Commit(); - Result Read(out ExtraData data); - } -} diff --git a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs index 50abb275..e6c32dc4 100644 --- a/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/Save/SaveDataFileSystem.cs @@ -72,7 +72,7 @@ namespace LibHac.FsSystem.Save MapStorage = MetaRemapStorage.Slice(layout.JournalMapTableOffset, layout.JournalMapTableSize), PhysicalBlockBitmap = MetaRemapStorage.Slice(layout.JournalPhysicalBitmapOffset, layout.JournalPhysicalBitmapSize), VirtualBlockBitmap = MetaRemapStorage.Slice(layout.JournalVirtualBitmapOffset, layout.JournalVirtualBitmapSize), - FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize), + FreeBlockBitmap = MetaRemapStorage.Slice(layout.JournalFreeBitmapOffset, layout.JournalFreeBitmapSize) }; IStorage journalData = DataRemapStorage.Slice(layout.JournalDataOffset, diff --git a/src/LibHac/FsSystem/SemaphoreAdaptor.cs b/src/LibHac/FsSystem/SemaphoreAdaptor.cs new file mode 100644 index 00000000..b0c16a84 --- /dev/null +++ b/src/LibHac/FsSystem/SemaphoreAdaptor.cs @@ -0,0 +1,30 @@ +using System; +using System.Threading; + +namespace LibHac.FsSystem +{ + public class SemaphoreAdaptor : IDisposable + { + private SemaphoreSlim _semaphore; + + public SemaphoreAdaptor(int initialCount, int maxCount) + { + _semaphore = new SemaphoreSlim(initialCount, maxCount); + } + + public bool TryLock() + { + return _semaphore.Wait(TimeSpan.Zero); + } + + public void Unlock() + { + _semaphore.Release(); + } + + public void Dispose() + { + _semaphore?.Dispose(); + } + } +} diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs new file mode 100644 index 00000000..fb089897 --- /dev/null +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetFileSystem.cs @@ -0,0 +1,166 @@ +using System; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSystem +{ + internal class StorageLayoutTypeSetFileSystem : IFileSystem + { + private ReferenceCountedDisposable BaseFileSystem { get; } + private StorageType StorageFlag { get; } + + public StorageLayoutTypeSetFileSystem(ReferenceCountedDisposable baseFileSystem, + StorageType storageFlag) + { + BaseFileSystem = baseFileSystem.AddReference(); + StorageFlag = storageFlag; + } + + protected StorageLayoutTypeSetFileSystem(ref ReferenceCountedDisposable baseFileSystem, + StorageType storageFlag) + { + BaseFileSystem = Shared.Move(ref baseFileSystem); + StorageFlag = storageFlag; + } + + public static ReferenceCountedDisposable CreateShared( + ReferenceCountedDisposable baseFileSystem, StorageType storageFlag) + { + return new ReferenceCountedDisposable( + new StorageLayoutTypeSetFileSystem(baseFileSystem, storageFlag)); + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseFileSystem, StorageType storageFlag) + { + return new ReferenceCountedDisposable( + new StorageLayoutTypeSetFileSystem(ref baseFileSystem, storageFlag)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + BaseFileSystem?.Dispose(); + } + + base.Dispose(disposing); + } + + protected override Result DoCreateFile(U8Span path, long size, CreateFileOptions option) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.CreateFile(path, size, option); + } + + protected override Result DoDeleteFile(U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.DeleteFile(path); + } + + protected override Result DoCreateDirectory(U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.CreateDirectory(path); + } + + protected override Result DoDeleteDirectory(U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.DeleteDirectory(path); + } + + protected override Result DoDeleteDirectoryRecursively(U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.DeleteDirectoryRecursively(path); + } + + protected override Result DoCleanDirectoryRecursively(U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.CleanDirectoryRecursively(path); + } + + protected override Result DoRenameFile(U8Span oldPath, U8Span newPath) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.RenameFile(oldPath, newPath); + } + + protected override Result DoRenameDirectory(U8Span oldPath, U8Span newPath) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.RenameDirectory(oldPath, newPath); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.GetEntryType(out entryType, path); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.GetFreeSpaceSize(out freeSpace, path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.GetTotalSpaceSize(out totalSpace, path); + } + + protected override Result DoOpenFile(out IFile file, U8Span path, OpenMode mode) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.OpenFile(out file, path, mode); + } + + protected override Result DoOpenDirectory(out IDirectory directory, U8Span path, OpenDirectoryMode mode) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.OpenDirectory(out directory, path, mode); + } + + protected override Result DoCommit() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.CommitProvisionally(counter); + } + + protected override Result DoRollback() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.Rollback(); + } + + protected override Result DoFlush() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.Flush(); + } + + protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.GetFileTimeStampRaw(out timeStamp, path); + } + + protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, U8Span path) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseFileSystem.Target.QueryEntry(outBuffer, inBuffer, queryId, path); + } + } +} diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetStorage.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetStorage.cs new file mode 100644 index 00000000..8d82a4dc --- /dev/null +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetStorage.cs @@ -0,0 +1,67 @@ +using System; +using LibHac.Common; +using LibHac.Fs; + +namespace LibHac.FsSystem +{ + internal class StorageLayoutTypeSetStorage : IStorage + { + private ReferenceCountedDisposable BaseStorage { get; } + private StorageType StorageFlag { get; } + + protected StorageLayoutTypeSetStorage(ref ReferenceCountedDisposable baseStorage, + StorageType storageFlag) + { + BaseStorage = Shared.Move(ref baseStorage); + StorageFlag = storageFlag; + } + + public static ReferenceCountedDisposable CreateShared( + ref ReferenceCountedDisposable baseStorage, StorageType storageFlag) + { + return new ReferenceCountedDisposable( + new StorageLayoutTypeSetStorage(ref baseStorage, storageFlag)); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + BaseStorage?.Dispose(); + } + + base.Dispose(disposing); + } + + protected override Result DoRead(long offset, Span destination) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseStorage.Target.Read(offset, destination); + } + + protected override Result DoWrite(long offset, ReadOnlySpan source) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseStorage.Target.Write(offset, source); + } + + protected override Result DoFlush() + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseStorage.Target.Flush(); + } + + protected override Result DoSetSize(long size) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseStorage.Target.SetSize(size); + } + + protected override Result DoGetSize(out long size) + { + using var scopedLayoutType = new ScopedStorageLayoutTypeSetter(StorageFlag); + return BaseStorage.Target.GetSize(out size); + } + } +} diff --git a/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs new file mode 100644 index 00000000..c6eafb4a --- /dev/null +++ b/src/LibHac/FsSystem/StorageLayoutTypeSetter.cs @@ -0,0 +1,30 @@ +using System; + +namespace LibHac.FsSystem +{ + internal struct ScopedStorageLayoutTypeSetter : IDisposable + { + // ReSharper disable once UnusedParameter.Local + public ScopedStorageLayoutTypeSetter(StorageType storageFlag) + { + // Todo: Implement + } + + public void Dispose() + { + + } + } + + [Flags] + internal enum StorageType + { + Bis = 1 << 0, + SdCard = 1 << 1, + GameCard = 1 << 2, + Usb = 1 << 3, + + NonGameCard = Bis | SdCard | Usb, + All = Bis | SdCard | GameCard | Usb + } +} diff --git a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs index 7d117d92..02a6df46 100644 --- a/src/LibHac/FsSystem/SubdirectoryFileSystem.cs +++ b/src/LibHac/FsSystem/SubdirectoryFileSystem.cs @@ -10,6 +10,7 @@ namespace LibHac.FsSystem public class SubdirectoryFileSystem : IFileSystem { private IFileSystem BaseFileSystem { get; } + private ReferenceCountedDisposable BaseFileSystemShared { get; } private U8String RootPath { get; set; } private bool PreserveUnc { get; } @@ -35,7 +36,24 @@ namespace LibHac.FsSystem PreserveUnc = preserveUnc; } - private Result Initialize(U8Span rootPath) + public SubdirectoryFileSystem(ref ReferenceCountedDisposable baseFileSystem, bool preserveUnc = false) + { + BaseFileSystemShared = Shared.Move(ref baseFileSystem); + BaseFileSystem = BaseFileSystemShared.Target; + PreserveUnc = preserveUnc; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + BaseFileSystemShared?.Dispose(); + } + + base.Dispose(disposing); + } + + public Result Initialize(U8Span rootPath) { if (StringUtils.GetLength(rootPath, PathTools.MaxPathLength + 1) > PathTools.MaxPathLength) return ResultFs.TooLongPath.Log(); diff --git a/src/LibHac/FsSystem/UniqueLockSemaphore.cs b/src/LibHac/FsSystem/UniqueLockSemaphore.cs new file mode 100644 index 00000000..49d37764 --- /dev/null +++ b/src/LibHac/FsSystem/UniqueLockSemaphore.cs @@ -0,0 +1,97 @@ +using System; +using System.Threading; +using LibHac.Common; +using LibHac.Diag; + +namespace LibHac.FsSystem +{ + public interface IUniqueLock : IDisposable + { + } + + /// + /// Represents a lock that may be passed between functions or objects. + /// + /// This struct must never be copied. It must always be passed by + /// reference or moved via the move constructor. + public struct UniqueLockSemaphore : IDisposable + { + private SemaphoreAdaptor _semaphore; + private bool _isLocked; + + public UniqueLockSemaphore(SemaphoreAdaptor semaphore) + { + _semaphore = semaphore; + _isLocked = false; + } + + public UniqueLockSemaphore(ref UniqueLockSemaphore other) + { + _semaphore = other._semaphore; + _isLocked = other._isLocked; + + other._isLocked = false; + other._semaphore = null; + } + + public bool IsLocked => _isLocked; + + public bool TryLock() + { + if (_isLocked) + { + throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked."); + } + + _isLocked = _semaphore.TryLock(); + return _isLocked; + } + + public void Unlock() + { + if (!_isLocked) + { + throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked."); + } + + _semaphore.Unlock(); + _isLocked = false; + } + + public void Dispose() + { + if (_isLocked) + { + _semaphore.Unlock(); + + _isLocked = false; + _semaphore = null; + } + } + } + + public class UniqueLockWithPin : IUniqueLock where T : class, IDisposable + { + private UniqueLockSemaphore _semaphore; + private ReferenceCountedDisposable _pinnedObject; + + public UniqueLockWithPin(ref UniqueLockSemaphore semaphore, ref ReferenceCountedDisposable pinnedObject) + { + Shared.Move(out _semaphore, ref semaphore); + Shared.Move(out _pinnedObject, ref pinnedObject); + + Assert.AssertTrue(_semaphore.IsLocked); + } + + public void Dispose() + { + if (_pinnedObject != null) + { + _semaphore.Dispose(); + _pinnedObject.Dispose(); + + _pinnedObject = null; + } + } + } +} diff --git a/src/LibHac/FsSystem/Utility.cs b/src/LibHac/FsSystem/Utility.cs index 6da63cc5..f238c4e1 100644 --- a/src/LibHac/FsSystem/Utility.cs +++ b/src/LibHac/FsSystem/Utility.cs @@ -226,6 +226,48 @@ namespace LibHac.FsSystem return Result.Success; } + public static Result TryAcquireCountSemaphore(out UniqueLockSemaphore uniqueLock, SemaphoreAdaptor semaphore) + { + UniqueLockSemaphore tempUniqueLock = default; + try + { + tempUniqueLock = new UniqueLockSemaphore(semaphore); + + if (!tempUniqueLock.TryLock()) + { + uniqueLock = default; + return ResultFs.OpenCountLimit.Log(); + } + + uniqueLock = new UniqueLockSemaphore(ref tempUniqueLock); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + + public static Result MakeUniqueLockWithPin(out IUniqueLock uniqueLock, SemaphoreAdaptor semaphore, + ref ReferenceCountedDisposable objectToPin) where T : class, IDisposable + { + uniqueLock = default; + + UniqueLockSemaphore tempUniqueLock = default; + try + { + Result rc = TryAcquireCountSemaphore(out tempUniqueLock, semaphore); + if (rc.IsFailure()) return rc; + + uniqueLock = new UniqueLockWithPin(ref tempUniqueLock, ref objectToPin); + return Result.Success; + } + finally + { + tempUniqueLock.Dispose(); + } + } + public static Result RetryFinitelyForTargetLocked(Func function) { const int maxRetryCount = 10; diff --git a/src/LibHac/HorizonClient.cs b/src/LibHac/HorizonClient.cs index 029c698f..a58be645 100644 --- a/src/LibHac/HorizonClient.cs +++ b/src/LibHac/HorizonClient.cs @@ -1,6 +1,7 @@ using System; using LibHac.Arp; using LibHac.Fs; +using LibHac.Lr; using LibHac.Os; using LibHac.Sm; @@ -17,6 +18,7 @@ namespace LibHac public FileSystemClient Fs { get; } public ServiceManagerClient Sm { get; } public OsClient Os { get; } + public LrClient Lr { get; } public ArpClient Arp => ArpLazy.Value; public ITimeSpanGenerator Time => Horizon.Time; @@ -29,6 +31,7 @@ namespace LibHac Fs = new FileSystemClient(this); Sm = new ServiceManagerClient(horizon.ServiceManager); Os = new OsClient(this); + Lr = new LrClient(this); ArpLazy = new Lazy(InitArpClient, true); } diff --git a/src/LibHac/LibHac.csproj b/src/LibHac/LibHac.csproj index a38292a4..cba4a9bf 100644 --- a/src/LibHac/LibHac.csproj +++ b/src/LibHac/LibHac.csproj @@ -45,7 +45,7 @@ - + diff --git a/src/LibHac/Lr/AddOnContentLocationResolver.cs b/src/LibHac/Lr/AddOnContentLocationResolver.cs new file mode 100644 index 00000000..fead88b6 --- /dev/null +++ b/src/LibHac/Lr/AddOnContentLocationResolver.cs @@ -0,0 +1,36 @@ +using System; +using LibHac.Ncm; +using LibHac.Sf; + +namespace LibHac.Lr +{ + public class AddOnContentLocationResolver : IDisposable + { + private ReferenceCountedDisposable _interface; + + public AddOnContentLocationResolver(ReferenceCountedDisposable baseInterface) + { + _interface = baseInterface.AddReference(); + } + + public Result ResolveAddOnContentPath(out Path path, DataId id) => + _interface.Target.ResolveAddOnContentPath(out path, id); + + public Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId) => + _interface.Target.RegisterAddOnContentStorage(id, applicationId, storageId); + + public Result UnregisterAllAddOnContentPath() => + _interface.Target.UnregisterAllAddOnContentPath(); + + public Result RefreshApplicationAddOnContent(InArray ids) => + _interface.Target.RefreshApplicationAddOnContent(ids); + + public Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id) => + _interface.Target.UnregisterApplicationAddOnContent(id); + + public void Dispose() + { + _interface?.Dispose(); + } + } +} diff --git a/src/LibHac/Lr/IAddOnContentLocationResolver.cs b/src/LibHac/Lr/IAddOnContentLocationResolver.cs new file mode 100644 index 00000000..a199d9bd --- /dev/null +++ b/src/LibHac/Lr/IAddOnContentLocationResolver.cs @@ -0,0 +1,15 @@ +using System; +using LibHac.Ncm; +using LibHac.Sf; + +namespace LibHac.Lr +{ + public interface IAddOnContentLocationResolver : IDisposable + { + Result ResolveAddOnContentPath(out Path path, DataId id); + Result RegisterAddOnContentStorage(DataId id, Ncm.ApplicationId applicationId, StorageId storageId); + Result UnregisterAllAddOnContentPath(); + Result RefreshApplicationAddOnContent(InArray ids); + Result UnregisterApplicationAddOnContent(Ncm.ApplicationId id); + } +} diff --git a/src/LibHac/Lr/ILocationResolver.cs b/src/LibHac/Lr/ILocationResolver.cs new file mode 100644 index 00000000..c54f5953 --- /dev/null +++ b/src/LibHac/Lr/ILocationResolver.cs @@ -0,0 +1,30 @@ +using System; +using LibHac.Ncm; +using LibHac.Sf; + +namespace LibHac.Lr +{ + public interface ILocationResolver : IDisposable + { + Result ResolveProgramPath(out Path path, ProgramId id); + Result RedirectProgramPath(in Path path, ProgramId id); + Result ResolveApplicationControlPath(out Path path, ProgramId id); + Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id); + Result ResolveDataPath(out Path path, DataId id); + Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId); + Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); + Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id); + Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId); + Result Refresh(); + Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result ClearApplicationRedirection(InArray excludingIds); + Result EraseProgramRedirection(ProgramId id); + Result EraseApplicationControlRedirection(ProgramId id); + Result EraseApplicationHtmlDocumentRedirection(ProgramId id); + Result EraseApplicationLegalInformationRedirection(ProgramId id); + Result ResolveProgramPathForDebug(out Path path, ProgramId id); + Result RedirectProgramPathForDebug(in Path path, ProgramId id); + Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId); + Result EraseProgramRedirectionForDebug(ProgramId id); + } +} diff --git a/src/LibHac/Lr/ILocationResolverManager.cs b/src/LibHac/Lr/ILocationResolverManager.cs new file mode 100644 index 00000000..922cf55c --- /dev/null +++ b/src/LibHac/Lr/ILocationResolverManager.cs @@ -0,0 +1,13 @@ +using System; +using LibHac.Ncm; + +namespace LibHac.Lr +{ + public interface ILocationResolverManager : IDisposable + { + Result OpenLocationResolver(out ReferenceCountedDisposable resolver, StorageId storageId); + Result OpenRegisteredLocationResolver(out ReferenceCountedDisposable resolver); + Result RefreshLocationResolver(StorageId storageId); + Result OpenAddOnContentLocationResolver(out ReferenceCountedDisposable resolver); + } +} diff --git a/src/LibHac/Lr/IRegisteredLocationResolver.cs b/src/LibHac/Lr/IRegisteredLocationResolver.cs new file mode 100644 index 00000000..4aa7c543 --- /dev/null +++ b/src/LibHac/Lr/IRegisteredLocationResolver.cs @@ -0,0 +1,19 @@ +using System; +using LibHac.Ncm; + +namespace LibHac.Lr +{ + public interface IRegisteredLocationResolver : IDisposable + { + Result ResolveProgramPath(out Path path, ProgramId id); + Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result UnregisterProgramPath(ProgramId id); + Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId); + Result ResolveHtmlDocumentPath(out Path path, ProgramId id); + Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId); + Result UnregisterHtmlDocumentPath(ProgramId id); + Result RedirectHtmlDocumentPath(in Path path, ProgramId id); + Result Refresh(); + Result RefreshExcluding(ReadOnlySpan ids); + } +} diff --git a/src/LibHac/Lr/LocationResolver.cs b/src/LibHac/Lr/LocationResolver.cs new file mode 100644 index 00000000..5dce2624 --- /dev/null +++ b/src/LibHac/Lr/LocationResolver.cs @@ -0,0 +1,81 @@ +using System; +using LibHac.Ncm; +using LibHac.Sf; + +namespace LibHac.Lr +{ + public class LocationResolver : IDisposable + { + private ReferenceCountedDisposable _interface; + + public LocationResolver(ReferenceCountedDisposable baseInterface) + { + _interface = baseInterface.AddReference(); + } + + public Result ResolveProgramPath(out Path path, ProgramId id) => + _interface.Target.ResolveProgramPath(out path, id); + + public Result RedirectProgramPath(in Path path, ProgramId id) => + _interface.Target.RedirectProgramPath(in path, id); + + public Result ResolveApplicationControlPath(out Path path, ProgramId id) => + _interface.Target.ResolveApplicationControlPath(out path, id); + + public Result ResolveApplicationHtmlDocumentPath(out Path path, ProgramId id) => + _interface.Target.ResolveApplicationHtmlDocumentPath(out path, id); + + public Result ResolveDataPath(out Path path, DataId id) => + _interface.Target.ResolveDataPath(out path, id); + + public Result RedirectApplicationControlPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectApplicationControlPath(in path, id, ownerId); + + public Result RedirectApplicationHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectApplicationHtmlDocumentPath(in path, id, ownerId); + + public Result ResolveApplicationLegalInformationPath(out Path path, ProgramId id) => + _interface.Target.ResolveApplicationLegalInformationPath(out path, id); + + public Result RedirectApplicationLegalInformationPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectApplicationLegalInformationPath(in path, id, ownerId); + + public Result Refresh() => + _interface.Target.Refresh(); + + public Result RedirectApplicationProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectApplicationProgramPath(in path, id, ownerId); + + public Result ClearApplicationRedirection(InArray excludingIds) => + _interface.Target.ClearApplicationRedirection(excludingIds); + + public Result EraseProgramRedirection(ProgramId id) => + _interface.Target.EraseProgramRedirection(id); + + public Result EraseApplicationControlRedirection(ProgramId id) => + _interface.Target.EraseApplicationControlRedirection(id); + + public Result EraseApplicationHtmlDocumentRedirection(ProgramId id) => + _interface.Target.EraseApplicationHtmlDocumentRedirection(id); + + public Result EraseApplicationLegalInformationRedirection(ProgramId id) => + _interface.Target.EraseApplicationLegalInformationRedirection(id); + + public Result ResolveProgramPathForDebug(out Path path, ProgramId id) => + _interface.Target.ResolveProgramPathForDebug(out path, id); + + public Result RedirectProgramPathForDebug(in Path path, ProgramId id) => + _interface.Target.RedirectProgramPathForDebug(in path, id); + + public Result RedirectApplicationProgramPathForDebug(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectApplicationProgramPathForDebug(in path, id, ownerId); + + public Result EraseProgramRedirectionForDebug(ProgramId id) => + _interface.Target.EraseProgramRedirectionForDebug(id); + + public void Dispose() + { + _interface?.Dispose(); + } + } +} diff --git a/src/LibHac/Lr/LrClient.cs b/src/LibHac/Lr/LrClient.cs new file mode 100644 index 00000000..77c62907 --- /dev/null +++ b/src/LibHac/Lr/LrClient.cs @@ -0,0 +1,102 @@ +using System; +using LibHac.Ncm; + +namespace LibHac.Lr +{ + public class LrClient : IDisposable + { + private HorizonClient Hos { get; } + + private ILocationResolverManager LrManager { get; set; } + private readonly object _lrInitLocker = new object(); + + public LrClient(HorizonClient horizonClient) + { + Hos = horizonClient; + } + + public Result OpenLocationResolver(out LocationResolver resolver, StorageId storageId) + { + resolver = default; + EnsureInitialized(); + + Result rc = LrManager.OpenLocationResolver(out ReferenceCountedDisposable baseResolver, + storageId); + if (rc.IsFailure()) return rc; + + using (baseResolver) + { + resolver = new LocationResolver(baseResolver); + return Result.Success; + } + } + + public Result OpenRegisteredLocationResolver(out RegisteredLocationResolver resolver) + { + resolver = default; + EnsureInitialized(); + + Result rc = LrManager.OpenRegisteredLocationResolver( + out ReferenceCountedDisposable baseResolver); + if (rc.IsFailure()) return rc; + + using (baseResolver) + { + resolver = new RegisteredLocationResolver(baseResolver); + return Result.Success; + } + } + + public Result OpenAddOnContentLocationResolver(out AddOnContentLocationResolver resolver) + { + resolver = default; + EnsureInitialized(); + + Result rc = LrManager.OpenAddOnContentLocationResolver( + out ReferenceCountedDisposable baseResolver); + if (rc.IsFailure()) return rc; + + using (baseResolver) + { + resolver = new AddOnContentLocationResolver(baseResolver); + return Result.Success; + } + } + + public Result RefreshLocationResolver(StorageId storageId) + { + EnsureInitialized(); + + Result rc = LrManager.RefreshLocationResolver(storageId); + if (rc.IsFailure()) return rc; + + return Result.Success; + } + + private void EnsureInitialized() + { + if (LrManager != null) + return; + + lock (_lrInitLocker) + { + if (LrManager != null) + return; + + Result rc = Hos.Sm.GetService(out ILocationResolverManager manager, "lr"); + + if (rc.IsFailure()) + { + throw new HorizonResultException(rc, "Failed to initialize lr client."); + } + + LrManager = manager; + } + } + + public void Dispose() + { + LrManager?.Dispose(); + } + } +} diff --git a/src/LibHac/Lr/Path.cs b/src/LibHac/Lr/Path.cs new file mode 100644 index 00000000..f2dadc64 --- /dev/null +++ b/src/LibHac/Lr/Path.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Util; + +namespace LibHac.Lr +{ + [DebuggerDisplay("{ToString()}")] + [StructLayout(LayoutKind.Sequential, Size = PathTool.EntryNameLengthMax)] + public struct Path + { +#if DEBUG + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding000; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding100; + [DebuggerBrowsable(DebuggerBrowsableState.Never)] private readonly Padding100 Padding200; +#endif + + public readonly ReadOnlySpan Str => SpanHelpers.AsReadOnlyByteSpan(in this); + public Span StrMutable => SpanHelpers.AsByteSpan(ref this); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InitEmpty(out Path path) + { + Unsafe.SkipInit(out path); + SpanHelpers.AsByteSpan(ref path)[0] = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator U8Span(in Path value) => new U8Span(SpanHelpers.AsReadOnlyByteSpan(in value)); + + public override readonly string ToString() => StringUtils.Utf8ZToString(Str); + } +} diff --git a/src/LibHac/Lr/RegisteredLocationResolver.cs b/src/LibHac/Lr/RegisteredLocationResolver.cs new file mode 100644 index 00000000..904b268a --- /dev/null +++ b/src/LibHac/Lr/RegisteredLocationResolver.cs @@ -0,0 +1,50 @@ +using System; +using LibHac.Ncm; + +namespace LibHac.Lr +{ + public class RegisteredLocationResolver : IDisposable + { + private ReferenceCountedDisposable _interface; + + public RegisteredLocationResolver(ReferenceCountedDisposable baseInterface) + { + _interface = baseInterface.AddReference(); + } + + public Result ResolveProgramPath(out Path path, ProgramId id) => + _interface.Target.ResolveProgramPath(out path, id); + + public Result RegisterProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RegisterProgramPath(in path, id, ownerId); + + public Result UnregisterProgramPath(ProgramId id) => + _interface.Target.UnregisterProgramPath(id); + + public Result RedirectProgramPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RedirectProgramPath(in path, id, ownerId); + + public Result ResolveHtmlDocumentPath(out Path path, ProgramId id) => + _interface.Target.ResolveHtmlDocumentPath(out path, id); + + public Result RegisterHtmlDocumentPath(in Path path, ProgramId id, ProgramId ownerId) => + _interface.Target.RegisterHtmlDocumentPath(in path, id, ownerId); + + public Result UnregisterHtmlDocumentPath(ProgramId id) => + _interface.Target.UnregisterHtmlDocumentPath(id); + + public Result RedirectHtmlDocumentPath(in Path path, ProgramId id) => + _interface.Target.RedirectHtmlDocumentPath(in path, id); + + public Result Refresh() => + _interface.Target.Refresh(); + + public Result RefreshExcluding(ReadOnlySpan ids) => + _interface.Target.RefreshExcluding(ids); + + public void Dispose() + { + _interface?.Dispose(); + } + } +} diff --git a/src/LibHac/Lr/ResultLr.cs b/src/LibHac/Lr/ResultLr.cs new file mode 100644 index 00000000..6319c99d --- /dev/null +++ b/src/LibHac/Lr/ResultLr.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// This file was automatically generated. +// Changes to this file will be lost when the file is regenerated. +// +// To change this file, modify /build/CodeGen/results.csv at the root of this +// repo and run the build script. +// +// The script can be run with the "codegen" option to run only the +// code generation portion of the build. +//----------------------------------------------------------------------------- + +namespace LibHac.Lr +{ + public static class ResultLr + { + public const int ModuleLr = 8; + + /// Error code: 2008-0002; Inner value: 0x408 + public static Result.Base ProgramNotFound => new Result.Base(ModuleLr, 2); + /// Error code: 2008-0003; Inner value: 0x608 + public static Result.Base DataNotFound => new Result.Base(ModuleLr, 3); + /// Error code: 2008-0004; Inner value: 0x808 + public static Result.Base UnknownStorageId => new Result.Base(ModuleLr, 4); + /// Error code: 2008-0005; Inner value: 0xa08 + public static Result.Base LocationResolverNotFound => new Result.Base(ModuleLr, 5); + /// Error code: 2008-0006; Inner value: 0xc08 + public static Result.Base HtmlDocumentNotFound => new Result.Base(ModuleLr, 6); + /// Error code: 2008-0007; Inner value: 0xe08 + public static Result.Base AddOnContentNotFound => new Result.Base(ModuleLr, 7); + /// Error code: 2008-0008; Inner value: 0x1008 + public static Result.Base ControlNotFound => new Result.Base(ModuleLr, 8); + /// Error code: 2008-0009; Inner value: 0x1208 + public static Result.Base LegalInformationNotFound => new Result.Base(ModuleLr, 9); + /// Error code: 2008-0010; Inner value: 0x1408 + public static Result.Base DebugProgramNotFound => new Result.Base(ModuleLr, 10); + /// Error code: 2008-0090; Inner value: 0xb408 + public static Result.Base TooManyRegisteredPaths => new Result.Base(ModuleLr, 90); + } +} diff --git a/src/LibHac/Ncm/ContentMetaId.cs b/src/LibHac/Ncm/ContentMetaId.cs index 93118434..66d6c10e 100644 --- a/src/LibHac/Ncm/ContentMetaId.cs +++ b/src/LibHac/Ncm/ContentMetaId.cs @@ -1,6 +1,8 @@ -namespace LibHac.Ncm +using System; + +namespace LibHac.Ncm { - public readonly struct ApplicationId + public readonly struct ApplicationId : IEquatable { public readonly ulong Value; @@ -20,6 +22,13 @@ { return Start <= programId && programId <= End; } + + public bool Equals(ApplicationId other) => Value == other.Value; + public override bool Equals(object obj) => obj is ApplicationId id && Equals(id); + public override int GetHashCode() => Value.GetHashCode(); + + public static bool operator ==(ApplicationId left, ApplicationId right) => left.Equals(right); + public static bool operator !=(ApplicationId left, ApplicationId right) => !left.Equals(right); } public readonly struct PatchId diff --git a/src/LibHac/ReferenceCountedDisposable.cs b/src/LibHac/ReferenceCountedDisposable.cs index 4e820194..f577f2af 100644 --- a/src/LibHac/ReferenceCountedDisposable.cs +++ b/src/LibHac/ReferenceCountedDisposable.cs @@ -8,6 +8,7 @@ #nullable enable using System; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace LibHac @@ -26,14 +27,14 @@ namespace LibHac /// released through either of the following actions: /// /// - /// The reference is explicitly released by a call to . + /// The reference is explicitly released by a call to . /// The reference is no longer in use by managed code and gets reclaimed by the garbage collector. /// /// /// While each instance of should be explicitly disposed when /// the object is no longer needed by the code owning the reference, this implementation will not leak resources /// in the event one or more callers fail to do so. When all references to an object are explicitly released - /// (i.e. by calling ), the target object will itself be deterministically released by a + /// (i.e. by calling ), the target object will itself be deterministically released by a /// call to when the last reference to it is released. However, in the event /// one or more references is not explicitly released, the underlying object will still become eligible for /// non-deterministic release (i.e. finalization) as soon as each reference to it is released by one of the @@ -65,7 +66,7 @@ namespace LibHac /// /// /// This value is only cleared in order to support cases where one or more references is garbage - /// collected without having called. + /// collected without having called. /// private T? _instance; @@ -110,16 +111,37 @@ namespace LibHac /// Gets the target object. ///
/// - /// This call is not valid after is called. If this property or the target - /// object is used concurrently with a call to , it is possible for the code to be + /// This call is not valid after is called. If this property or the target + /// object is used concurrently with a call to , it is possible for the code to be /// using a disposed object. After the current instance is disposed, this property throws /// . However, the exact time when this property starts throwing after - /// is called is unspecified; code is expected to not use this property or the object - /// it returns after any code invokes . + /// is called is unspecified; code is expected to not use this property or the object + /// it returns after any code invokes . /// /// The target object. public T Target => _instance ?? throw new ObjectDisposedException(nameof(ReferenceCountedDisposable)); + /// + /// Initializes a new reference counting wrapper around an object. + /// Returns a reference counting wrapper of the base type of the input object, and creates a + /// of the object's derived type. + /// + /// The derived type of the object to be wrapped. + /// The object owned by this wrapper. + /// The weak reference of the derived type. + /// + public static ReferenceCountedDisposable Create(TDerived instance, + out ReferenceCountedDisposable.WeakReference derivedWeakReference) + where TDerived : class, IDisposable, T + { + var baseStrongRef = new ReferenceCountedDisposable(instance); + + derivedWeakReference = + new ReferenceCountedDisposable.WeakReference(instance, baseStrongRef._boxedReferenceCount); + + return baseStrongRef; + } + /// /// Increments the reference count for the disposable object, and returns a new disposable reference to it. /// @@ -272,6 +294,11 @@ namespace LibHac } } + // Print info on non-disposed references in debug mode +#if DEBUG + ~ReferenceCountedDisposable() => Dispose(false); +#endif + /// /// Releases the current reference, causing the underlying object to be disposed if this was the last /// reference. @@ -283,26 +310,42 @@ namespace LibHac /// public void Dispose() { - T? instanceToDispose = null; - lock (_boxedReferenceCount) + Dispose(true); + +#if DEBUG + GC.SuppressFinalize(this); +#endif + } + + private void Dispose(bool disposing) + { + if (disposing) { - if (_instance == null) + T? instanceToDispose = null; + lock (_boxedReferenceCount) { - // Already disposed; allow multiple without error. - return; + if (_instance == null) + { + // Already disposed; allow multiple without error. + return; + } + + _boxedReferenceCount.Value--; + if (_boxedReferenceCount.Value == 0) + { + instanceToDispose = _instance; + } + + // Ensure multiple calls to Dispose for this instance are a NOP. + _instance = null; } - _boxedReferenceCount.Value--; - if (_boxedReferenceCount.Value == 0) - { - instanceToDispose = _instance; - } - - // Ensure multiple calls to Dispose for this instance are a NOP. - _instance = null; + instanceToDispose?.Dispose(); + } + else + { + Trace.WriteLine($"Failed to dispose object with type {GetType().FullName}."); } - - instanceToDispose?.Dispose(); } /// @@ -349,6 +392,24 @@ namespace LibHac _boxedReferenceCount = referenceCount; } + /// + /// + /// + /// + /// + internal WeakReference(T instance, StrongBox referenceCount) + { + // This constructor is meant for internal use when creating a weak reference + // to an instance that is already wrapped by a ReferenceCountedDisposable. + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + _weakInstance = new WeakReference(instance); + _boxedReferenceCount = referenceCount; + } + /// /// Increments the reference count for the disposable object, and returns a new disposable reference to /// it. @@ -380,6 +441,72 @@ namespace LibHac return TryAddReferenceImpl(target, referenceCount); } + + public ReferenceCountedDisposable? TryAddReference() + where TTo : class, IDisposable + { + WeakReference? weakInstance = _weakInstance; + if (weakInstance == null || !weakInstance.TryGetTarget(out var target)) + { + return null; + } + + StrongBox? referenceCount = _boxedReferenceCount; + if (referenceCount == null) + { + return null; + } + + return TryAddReferenceImpl(target, referenceCount, out _); + } + + /// + /// Increments the reference count for the disposable object, and returns a new disposable reference to + /// it. + /// + /// + /// Unlike , this method is capable of + /// adding a reference to the underlying instance all the way up to the point where it is finally + /// disposed. + /// + /// The returned object is an independent reference to the same underlying object. Disposing of + /// the returned value multiple times will only cause the reference count to be decreased once. + /// + /// A new pointing to the same underlying object, + /// if it has not yet been disposed; otherwise, is thrown if the + /// underlying object has already been disposed. + public ReferenceCountedDisposable AddReference() => + TryAddReference() ?? throw new ObjectDisposedException(nameof(WeakReference)); + + public ReferenceCountedDisposable AddReference() where TTo : class, IDisposable + { + WeakReference? weakInstance = _weakInstance; + if (weakInstance == null || !weakInstance.TryGetTarget(out var target)) + { + throw new ObjectDisposedException(nameof(WeakReference)); + } + + StrongBox? referenceCount = _boxedReferenceCount; + if (referenceCount == null) + { + throw new ObjectDisposedException(nameof(WeakReference)); + } + + ReferenceCountedDisposable? newReference = + TryAddReferenceImpl(target, referenceCount, out CreateResult result); + + if (newReference != null) + { + return newReference; + } + + throw result switch + { + CreateResult.Disposed => new ObjectDisposedException(nameof(ReferenceCountedDisposable)), + CreateResult.NotCastable => new InvalidCastException(), + _ => new NotSupportedException("This exception should never be thrown.") + }; + } } } } \ No newline at end of file diff --git a/src/LibHac/Sf/Buffers.cs b/src/LibHac/Sf/Buffers.cs new file mode 100644 index 00000000..8a2198f2 --- /dev/null +++ b/src/LibHac/Sf/Buffers.cs @@ -0,0 +1,74 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; + +namespace LibHac.Sf +{ + public readonly ref struct InBuffer + { + private readonly ReadOnlySpan _buffer; + + public int Size => _buffer.Length; + public ReadOnlySpan Buffer => _buffer; + + public InBuffer(ReadOnlySpan buffer) + { + _buffer = buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static InBuffer FromStruct(in T value) where T : unmanaged + { + return new InBuffer(SpanHelpers.AsReadOnlyByteSpan(in value)); + } + } + + public readonly ref struct OutBuffer + { + private readonly Span _buffer; + + public int Size => _buffer.Length; + public Span Buffer => _buffer; + + public OutBuffer(Span buffer) + { + _buffer = buffer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static OutBuffer FromStruct(ref T value) where T : unmanaged + { + return new OutBuffer(SpanHelpers.AsByteSpan(ref value)); + } + } + + public readonly ref struct InArray where T : unmanaged + { + private readonly ReadOnlySpan _array; + + public int Size => _array.Length; + public ReadOnlySpan Array => _array; + + public InArray(ReadOnlySpan array) + { + _array = array; + } + + public ref readonly T this[int i] => ref _array[i]; + } + + public readonly ref struct OutArray where T : unmanaged + { + private readonly Span _array; + + public int Size => _array.Length; + public Span Array => _array; + + public OutArray(Span array) + { + _array = array; + } + + public ref T this[int i] => ref _array[i]; + } +} diff --git a/src/LibHac/Sf/NativeHandle.cs b/src/LibHac/Sf/NativeHandle.cs new file mode 100644 index 00000000..fd2ed0ac --- /dev/null +++ b/src/LibHac/Sf/NativeHandle.cs @@ -0,0 +1,21 @@ +namespace LibHac.Sf +{ + // How should this be handled? Using a C# struct would be more accurate, but C# + // doesn't have copy constructors or any way to prevent a struct from being copied. + public class NativeHandle + { + public uint Handle { get; private set; } + public bool IsManaged { get; private set; } + + public NativeHandle(uint handle) + { + Handle = handle; + } + + public NativeHandle(uint handle, bool isManaged) + { + Handle = handle; + IsManaged = isManaged; + } + } +} diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index 71572bab..26f3a459 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -43,12 +43,16 @@ namespace LibHac public static SwitchFs OpenSdCard(KeySet keySet, IAttributeFileSystem fileSystem) { var concatFs = new ConcatenationFileSystem(fileSystem); - SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem contentDirFs, concatFs, "/Nintendo/Contents".ToU8String()).ThrowIfFailure(); + + var contentDirFs = new SubdirectoryFileSystem(concatFs); + contentDirFs.Initialize("/Nintendo/Contents".ToU8String()).ThrowIfFailure(); AesXtsFileSystem encSaveFs = null; if (fileSystem.DirectoryExists("/Nintendo/save")) { - SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem saveDirFs, concatFs, "/Nintendo/save".ToU8String()).ThrowIfFailure(); + var saveDirFs = new SubdirectoryFileSystem(concatFs); + saveDirFs.Initialize("/Nintendo/save".ToU8String()).ThrowIfFailure(); + encSaveFs = new AesXtsFileSystem(saveDirFs, keySet.SdCardEncryptionKeys[0].DataRo.ToArray(), 0x4000); } diff --git a/src/LibHac/Util/Impl/HexConverter.cs b/src/LibHac/Util/Impl/HexConverter.cs index 7e0caa0c..415e2eda 100644 --- a/src/LibHac/Util/Impl/HexConverter.cs +++ b/src/LibHac/Util/Impl/HexConverter.cs @@ -20,7 +20,7 @@ namespace LibHac.Util.Impl // while outputs in the range [ 0x41 .. 0x46 ] ([ 'A' .. 'F' ]) // don't have the 0x20 bit set, so ORing them maps to // [ 0x61 .. 0x66 ] ([ 'a' .. 'f' ]), which is what we want. - Lower = 0x2020U, + Lower = 0x2020U } // We want to pack the incoming byte into a single integer [ 0000 HHHH 0000 LLLL ], diff --git a/src/LibHac/Util/Optional.cs b/src/LibHac/Util/Optional.cs index c2964d8d..4ba683ae 100644 --- a/src/LibHac/Util/Optional.cs +++ b/src/LibHac/Util/Optional.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using LibHac.Common; using LibHac.Diag; namespace LibHac.Util @@ -8,7 +9,7 @@ namespace LibHac.Util private bool _hasValue; private T _value; - public bool HasValue => _hasValue; + public readonly bool HasValue => _hasValue; public ref T Value { get @@ -18,6 +19,22 @@ namespace LibHac.Util return ref MemoryMarshal.CreateSpan(ref _value, 1)[0]; } } + public readonly ref readonly T ValueRo + { + get + { + Assert.AssertTrue(_hasValue); + return ref SpanHelpers.CreateReadOnlySpan(in _value, 1)[0]; + } + } + + public Optional(in T value) + { + _value = value; + _hasValue = true; + } + + public static implicit operator Optional(in T value) => new Optional(in value); public void Set(in T value) { diff --git a/src/LibHac/Util/UniqueLock.cs b/src/LibHac/Util/UniqueLock.cs new file mode 100644 index 00000000..ed4f68b9 --- /dev/null +++ b/src/LibHac/Util/UniqueLock.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading; + +namespace LibHac.Util +{ + /// + /// Represents a lock that may be passed between functions or objects. + /// + /// This struct must never be copied. It must always be passed by + /// reference or moved via the move constructor. + public struct UniqueLock : IDisposable + { + private object _lockObject; + private bool _isLocked; + + /// + /// Creates a new from the provided object and acquires the lock. + /// + /// The object to lock. + public UniqueLock(object lockObject) + { + _lockObject = lockObject; + _isLocked = false; + + Lock(); + } + + public UniqueLock(ref UniqueLock other) + { + _lockObject = other._lockObject; + _isLocked = other._isLocked; + + other._isLocked = false; + other._lockObject = null; + } + + public bool IsLocked => _isLocked; + + public void Lock() + { + if (_isLocked) + { + throw new SynchronizationLockException("Attempted to lock a UniqueLock that was already locked."); + } + + Monitor.Enter(_lockObject, ref _isLocked); + } + + public void Unlock() + { + if (!_isLocked) + { + throw new SynchronizationLockException("Attempted to unlock a UniqueLock that was not locked."); + } + + Monitor.Exit(_lockObject); + _isLocked = false; + } + + public void Dispose() + { + if (_isLocked) + { + Monitor.Exit(_lockObject); + + _isLocked = false; + _lockObject = null; + } + } + } +} diff --git a/src/hactoolnet/Program.cs b/src/hactoolnet/Program.cs index 1a3dcbd2..935c6292 100644 --- a/src/hactoolnet/Program.cs +++ b/src/hactoolnet/Program.cs @@ -14,7 +14,13 @@ namespace hactoolnet { try { - if (Run(args)) return 0; + if (Run(args)) + { + // Console.ReadKey(); + return 0; + } + + } catch (MissingKeyException ex) { diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs index 9d431c7a..43e3fb79 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ApplicationSaveDataManagementTests.cs @@ -60,7 +60,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests Assert.Equal(1, entriesRead); Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.Zero, info[0].UserId); + Assert.Equal(UserId.InvalidId, info[0].UserId); Assert.Equal(SaveDataType.Device, info[0].Type); } @@ -86,7 +86,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests Assert.Equal(1, entriesRead); Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.Zero, info[0].UserId); + Assert.Equal(UserId.InvalidId, info[0].UserId); Assert.Equal(SaveDataType.Bcat, info[0].Type); } @@ -112,7 +112,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests Assert.Equal(1, entriesRead); Assert.Equal(applicationId, info[0].ProgramId); - Assert.Equal(UserId.Zero, info[0].UserId); + Assert.Equal(UserId.InvalidId, info[0].UserId); Assert.Equal(SaveDataType.Temporary, info[0].Type); } diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs index ac6edef7..a09eadd0 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/ShimTests/SaveDataManagement.cs @@ -80,8 +80,8 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests Assert.Equal(applicationId, info[0].ProgramId); Assert.Equal(applicationId, info[1].ProgramId); - var expectedIndexes = new short[] { 0, 1 }; - short[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray(); + var expectedIndexes = new ushort[] { 0, 1 }; + ushort[] actualIndexes = info.Take(2).Select(x => x.Index).OrderBy(x => x).ToArray(); Assert.Equal(expectedIndexes, actualIndexes); } @@ -294,7 +294,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests for (int i = 1; i <= count; i++) { var applicationId = new Ncm.ApplicationId((uint)i); - Result rc = fs.CreateSaveData(applicationId, UserId.Zero, 0, 0x4000, 0x4000, SaveDataFlags.None); + Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); if (rc.IsFailure()) return rc; } } @@ -305,7 +305,7 @@ namespace LibHac.Tests.Fs.FileSystemClientTests.ShimTests for (int i = 1; i <= count; i++) { var applicationId = new Ncm.ApplicationId((uint)rng.Next()); - Result rc = fs.CreateSaveData(applicationId, UserId.Zero, 0, 0x4000, 0x4000, SaveDataFlags.None); + Result rc = fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); if (rc.IsFailure()) return rc; } } diff --git a/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs b/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs new file mode 100644 index 00000000..c77455a2 --- /dev/null +++ b/tests/LibHac.Tests/Fs/FsaTests/MultiCommitTests.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Shim; +using LibHac.Tests.Fs.FileSystemClientTests; +using Xunit; + +namespace LibHac.Tests.Fs.FsaTests +{ + public class MultiCommitTests + { + [Fact] + public void Commit_MultipleFileSystems_AllFileSystemsAreCommitted() + { + FileSystemClient fs = FileSystemServerFactory.CreateClient(true); + + var saveInfo = new List<(int id, string name)> + { + (1, "Save1"), + (3, "Save2"), + (2, "Save3") + }; + + foreach ((int id, string name) info in saveInfo) + { + var applicationId = new Ncm.ApplicationId((uint)info.id); + fs.CreateSaveData(applicationId, UserId.InvalidId, 0, 0x4000, 0x4000, SaveDataFlags.None); + fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); + } + + foreach ((int id, string name) info in saveInfo) + { + fs.CreateFile($"{info.name}:/file{info.id}".ToU8Span(), 0); + } + + var names = new List(); + + foreach ((int id, string name) info in saveInfo) + { + names.Add(info.name.ToU8String()); + } + + Assert.Success(fs.Commit(names.ToArray())); + + foreach ((int id, string name) info in saveInfo) + { + fs.Unmount(info.name.ToU8Span()); + } + + foreach ((int id, string name) info in saveInfo) + { + var applicationId = new Ncm.ApplicationId((uint)info.id); + fs.MountSaveData(info.name.ToU8Span(), applicationId, UserId.InvalidId); + } + + foreach ((int id, string name) info in saveInfo) + { + Assert.Success(fs.GetEntryType(out _, $"{info.name}:/file{info.id}".ToU8Span())); + } + } + } +} diff --git a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs index 38d3629c..c49a572e 100644 --- a/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs +++ b/tests/LibHac.Tests/Fs/SubdirectoryFileSystemTests.cs @@ -20,7 +20,9 @@ namespace LibHac.Tests.Fs baseFs.CreateDirectory("/sub".ToU8Span()); baseFs.CreateDirectory("/sub/path".ToU8Span()); - SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/sub/path".ToU8String()).ThrowIfFailure(); + var subFs = new SubdirectoryFileSystem(baseFs); + subFs.Initialize("/sub/path".ToU8String()).ThrowIfFailure(); + return (baseFs, subFs); } @@ -53,7 +55,8 @@ namespace LibHac.Tests.Fs { var baseFs = new InMemoryFileSystem(); - SubdirectoryFileSystem.CreateNew(out SubdirectoryFileSystem subFs, baseFs, "/".ToU8String()).ThrowIfFailure(); + var subFs = new SubdirectoryFileSystem(baseFs); + subFs.Initialize("/".ToU8String()).ThrowIfFailure(); return subFs; } } diff --git a/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs b/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs index 08023a51..e299c82a 100644 --- a/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs +++ b/tests/LibHac.Tests/FsSrv/ProgramIndexMapInfoTests.cs @@ -4,6 +4,7 @@ using LibHac.Fs.Shim; using LibHac.FsSrv; using LibHac.FsSrv.Impl; using LibHac.Ncm; +using LibHac.Util; using Xunit; namespace LibHac.Tests.FsSrv @@ -72,10 +73,10 @@ namespace LibHac.Tests.FsSrv ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 3); - Assert.Null(manager.Get(new ProgramId(0))); - Assert.Null(manager.Get(new ProgramId(2))); - Assert.Null(manager.Get(new ProgramId(8))); - Assert.Null(manager.Get(new ProgramId(9001))); + Assert.False(manager.Get(new ProgramId(0)).HasValue); + Assert.False(manager.Get(new ProgramId(2)).HasValue); + Assert.False(manager.Get(new ProgramId(8)).HasValue); + Assert.False(manager.Get(new ProgramId(9001)).HasValue); } [Fact] @@ -86,14 +87,14 @@ namespace LibHac.Tests.FsSrv ProgramIndexMapInfoManager manager = CreatePopulatedManager(count, x => x + 1); // ReSharper disable PossibleInvalidOperationException - ProgramIndexMapInfo? map = manager.Get(new ProgramId(1)); - Assert.NotNull(map); + Optional map = manager.Get(new ProgramId(1)); + Assert.True(map.HasValue); Assert.Equal(new ProgramId(1), map.Value.MainProgramId); Assert.Equal(new ProgramId(1), map.Value.ProgramId); Assert.Equal(0, map.Value.ProgramIndex); map = manager.Get(new ProgramId(4)); - Assert.NotNull(map); + Assert.True(map.HasValue); Assert.Equal(new ProgramId(1), map.Value.MainProgramId); Assert.Equal(new ProgramId(4), map.Value.ProgramId); Assert.Equal(3, map.Value.ProgramIndex); @@ -186,7 +187,7 @@ namespace LibHac.Tests.FsSrv manager.Clear(); - Assert.Null(manager.Get(new ProgramId(2))); + Assert.False(manager.Get(new ProgramId(2)).HasValue); } } } diff --git a/tests/LibHac.Tests/FsSrv/SaveDataServiceTests/TypeSizeTests.cs b/tests/LibHac.Tests/FsSrv/SaveDataServiceTests/TypeSizeTests.cs new file mode 100644 index 00000000..517e1238 --- /dev/null +++ b/tests/LibHac.Tests/FsSrv/SaveDataServiceTests/TypeSizeTests.cs @@ -0,0 +1,43 @@ +// ReSharper disable InconsistentNaming +using System.Runtime.CompilerServices; +using LibHac.Fs; +using LibHac.FsSrv; +using LibHac.Ncm; +using LibHac.Util; +using Xunit; + +namespace LibHac.Tests.FsSrv.SaveDataServiceTests +{ + public class TypeSizeTests + { + [Fact] + public static void SaveDataInfoFilterSizeIs0x60() + { + Assert.Equal(0x60, Unsafe.SizeOf()); + } + + [Fact] + public static void SaveDataInfoFilterLayoutIsCorrect() + { + var filter = new SaveDataInfoFilter(); + + ref byte baseRef = ref Unsafe.As(ref filter); + + ref byte spaceIdRef = ref Unsafe.As, byte>(ref filter.SpaceId); + ref byte programIdRef = ref Unsafe.As, byte>(ref filter.ProgramId); + ref byte saveTypeRef = ref Unsafe.As, byte>(ref filter.SaveDataType); + ref byte userIdRef = ref Unsafe.As, byte>(ref filter.UserId); + ref byte saveIdRef = ref Unsafe.As, byte>(ref filter.SaveDataId); + ref byte indexRef = ref Unsafe.As, byte>(ref filter.Index); + ref byte rankRef = ref Unsafe.As(ref filter.Rank); + + Assert.Equal(0x00, (int)Unsafe.ByteOffset(ref baseRef, ref spaceIdRef)); + Assert.Equal(0x08, (int)Unsafe.ByteOffset(ref baseRef, ref programIdRef)); + Assert.Equal(0x18, (int)Unsafe.ByteOffset(ref baseRef, ref saveTypeRef)); + Assert.Equal(0x20, (int)Unsafe.ByteOffset(ref baseRef, ref userIdRef)); + Assert.Equal(0x38, (int)Unsafe.ByteOffset(ref baseRef, ref saveIdRef)); + Assert.Equal(0x48, (int)Unsafe.ByteOffset(ref baseRef, ref indexRef)); + Assert.Equal(0x4C, (int)Unsafe.ByteOffset(ref baseRef, ref rankRef)); + } + } +}