From 2c14770ceb245da652eb04ed1809e0da5da09d3f Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 9 Mar 2024 17:47:17 -0700 Subject: [PATCH] Implement SaveDataFileSystem --- build/CodeGen/results.csv | 1 + src/LibHac/Fs/ResultFs.cs | 2 + .../FsSystem/IResultConvertFileSystem.cs | 6 +- src/LibHac/FsSystem/SaveDataFileSystem.cs | 214 ++++++++++++++---- .../SaveDataResultConvertFileSystem.cs | 10 +- src/LibHac/FsSystem/SwitchStorage.cs | 2 +- 6 files changed, 181 insertions(+), 54 deletions(-) diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index 6df4f75b..520d2e68 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -1068,6 +1068,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,6395,,,,UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem, 2,6396,,,,UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem, 2,6397,,,,UnsupportedOperateRangeForRegionSwitchStorage, +2,6398,,,,UnsupportedOperateRangeForSaveDataFile, 2,6400,6449,,,PermissionDenied, 2,6403,,,,HostFileSystemOperationDisabled,Returned when opening a host FS on a retail device. diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index b9d97bcb..ad9fb24f 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -1960,6 +1960,8 @@ public static class ResultFs public static Result.Base UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6396); /// Error code: 2002-6397; Inner value: 0x31fa02 public static Result.Base UnsupportedOperateRangeForRegionSwitchStorage => new Result.Base(ModuleFs, 6397); + /// Error code: 2002-6398; Inner value: 0x31fc02 + public static Result.Base UnsupportedOperateRangeForSaveDataFile => new Result.Base(ModuleFs, 6398); /// Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002 public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); } diff --git a/src/LibHac/FsSystem/IResultConvertFileSystem.cs b/src/LibHac/FsSystem/IResultConvertFileSystem.cs index a8a4c6b5..69c4b3d3 100644 --- a/src/LibHac/FsSystem/IResultConvertFileSystem.cs +++ b/src/LibHac/FsSystem/IResultConvertFileSystem.cs @@ -10,7 +10,7 @@ namespace LibHac.FsSystem; /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public abstract class IResultConvertFile : IFile { private UniqueRef _baseFile; @@ -66,7 +66,7 @@ public abstract class IResultConvertFile : IFile /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public abstract class IResultConvertDirectory : IDirectory { private UniqueRef _baseDirectory; @@ -101,7 +101,7 @@ public abstract class IResultConvertDirectory : IDirectory /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public abstract class IResultConvertFileSystem : ISaveDataFileSystem where T : IFileSystem { private SharedRef _baseFileSystem; diff --git a/src/LibHac/FsSystem/SaveDataFileSystem.cs b/src/LibHac/FsSystem/SaveDataFileSystem.cs index ea9988d9..66a433ac 100644 --- a/src/LibHac/FsSystem/SaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/SaveDataFileSystem.cs @@ -1,7 +1,7 @@ -// ReSharper disable UnusedMember.Local UnusedType.Local -#pragma warning disable CS0169 // Field is never used -using System; +using System; +using System.Runtime.CompilerServices; using LibHac.Common; +using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem.Save; @@ -9,52 +9,92 @@ using LibHac.Os; namespace LibHac.FsSystem; +/// +/// Wraps an opened by a +/// +/// Based on nnSdk 17.5.0 (FS 17.0.0) file class SaveDataFile : IFile { private UniqueRef _file; public SaveDataFile(ref UniqueRef file) { - throw new NotImplementedException(); + _file = new UniqueRef(ref file); + + Assert.SdkRequiresNotNull(in _file); } public override void Dispose() { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + _file.Destroy(); + base.Dispose(); } protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + Result res = _file.Get.Read(out bytesRead, offset, destination); + + if (res.IsFailure()) + { + if (ResultFs.InvalidSaveDataFileReadOffset.Includes(res)) + { + return ResultFs.OutOfRange.LogConverted(res); + } + + return res.Miss(); + } + + return Result.Success; } protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + return _file.Get.Write(offset, source, in option).Ret(); } protected override Result DoFlush() { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + return _file.Get.Flush().Ret(); } protected override Result DoSetSize(long size) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + return _file.Get.SetSize(size).Ret(); } protected override Result DoGetSize(out long size) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + return _file.Get.GetSize(out size).Ret(); } protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) { - throw new NotImplementedException(); + Assert.SdkRequiresNotNull(in _file); + + if (operationId == OperationId.InvalidateCache) + return ResultFs.UnsupportedOperateRangeForSaveDataFile.Log(); + + return _file.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer).Ret(); } } +/// +/// Reads and writes to the file system inside a journal integrity save data image file. +/// +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystemHolder { private SharedRef _baseStorage; @@ -69,39 +109,75 @@ public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystem public SaveDataFileSystem() { - throw new NotImplementedException(); + _baseStorage = new SharedRef(); + _saveFsDriver = new JournalIntegritySaveDataFileSystemDriver(); + _cacheObserver = null; + _mutex = new SdkMutex(); + _canCommitProvisionally = false; } public override void Dispose() { - throw new NotImplementedException(); + _saveFsDriver.FinalizeObject(); + _cacheObserver?.Unregister(_spaceId, _saveDataId); + + _saveFsDriver.Dispose(); + _baseStorage.Destroy(); + base.Dispose(); } public static Result ExtractParameters(out JournalIntegritySaveDataParameters outParam, IStorage saveStorage, IBufferManager bufferManager, IMacGenerator macGenerator, IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out outParam); + + using var fileSystem = new JournalIntegritySaveDataFileSystemDriver(); + + Result res = saveStorage.GetSize(out long size); + if (res.IsFailure()) return res.Miss(); + + using var saveSubStorage = new ValueSubStorage(saveStorage, 0, size); + res = fileSystem.Initialize(in saveSubStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion); + if (res.IsFailure()) return res.Miss(); + + fileSystem.ExtractParameters(out outParam); + return Result.Success; } public Result Initialize(IStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator, IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion, bool canCommitProvisionally) { - throw new NotImplementedException(); + return Initialize(baseStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, timeStampGetter: null, + randomGenerator: null, minimumVersion, canCommitProvisionally).Ret(); } public Result Initialize(IStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator, IHash256GeneratorFactorySelector hashGeneratorFactorySelector, ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, uint minimumVersion, bool canCommitProvisionally) { - throw new NotImplementedException(); + Result res = baseStorage.GetSize(out long size); + if (res.IsFailure()) return res.Miss(); + + using var baseSubStorage = new ValueSubStorage(baseStorage, 0, size); + res = _saveFsDriver.Initialize(in baseSubStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion); + if (res.IsFailure()) return res.Miss(); + + _commitTimeStampGetter = timeStampGetter; + _randomGeneratorForCommit = randomGenerator; + _canCommitProvisionally = canCommitProvisionally; + + return Result.Success; } public Result Initialize(ref readonly SharedRef baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator, IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion, bool canCommitProvisionally) { - throw new NotImplementedException(); + _baseStorage.SetByCopy(in baseStorage); + + return Initialize(_baseStorage.Get, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion, + canCommitProvisionally).Ret(); } public Result Initialize(ref readonly SharedRef baseStorage, IBufferManager bufferManager, @@ -109,136 +185,184 @@ public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystem ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, uint minimumVersion, bool canCommitProvisionally) { - throw new NotImplementedException(); + _baseStorage.SetByCopy(in baseStorage); + + return Initialize(_baseStorage.Get, bufferManager, macGenerator, hashGeneratorFactorySelector, timeStampGetter, + randomGenerator, minimumVersion, canCommitProvisionally).Ret(); } protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option) { - throw new NotImplementedException(); + return _saveFsDriver.CreateFile(in path, size, option).Ret(); } protected override Result DoDeleteFile(ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.DeleteFile(in path).Ret(); } protected override Result DoCreateDirectory(ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.CreateDirectory(in path).Ret(); } protected override Result DoDeleteDirectory(ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.DeleteDirectory(in path).Ret(); } protected override Result DoDeleteDirectoryRecursively(ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.DeleteDirectoryRecursively(in path).Ret(); } protected override Result DoCleanDirectoryRecursively(ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.CleanDirectoryRecursively(in path).Ret(); } protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath) { - throw new NotImplementedException(); + return _saveFsDriver.RenameFile(in currentPath, in newPath).Ret(); } protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath) { - throw new NotImplementedException(); + return _saveFsDriver.RenameDirectory(in currentPath, in newPath).Ret(); } protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.GetEntryType(out entryType, in path).Ret(); } protected override Result DoOpenFile(ref UniqueRef outFile, ref readonly Path path, OpenMode mode) { - throw new NotImplementedException(); + using var file = new UniqueRef(); + Result res = _saveFsDriver.OpenFile(ref file.Ref, in path, mode); + if (res.IsFailure()) return res.Miss(); + + using var wrapperFile = new UniqueRef(new SaveDataFile(ref file.Ref)); + + outFile.Set(ref wrapperFile.Ref); + return Result.Success; } protected override Result DoOpenDirectory(ref UniqueRef outDirectory, ref readonly Path path, OpenDirectoryMode mode) { - throw new NotImplementedException(); + return _saveFsDriver.OpenDirectory(ref outDirectory, in path, mode).Ret(); } private Result DoCommit(bool updateTimeStamp) { - throw new NotImplementedException(); + if (updateTimeStamp && _commitTimeStampGetter is not null) + { + Assert.SdkNotNull(_randomGeneratorForCommit); + + Result res = ReadExtraData(out SaveDataExtraData extraData); + if (res.IsFailure()) return res.Miss(); + + res = _commitTimeStampGetter.Get(out long timeStamp); + if (res.IsSuccess()) + extraData.TimeStamp = timeStamp; + + long commitId = 0; + do + { + _randomGeneratorForCommit(SpanHelpers.AsByteSpan(ref commitId)); + } while (commitId == 0 || commitId == extraData.CommitId); + + extraData.CommitId = commitId; + + res = WriteExtraData(in extraData); + if (res.IsFailure()) return res.Miss(); + } + + return _saveFsDriver.Commit().Ret(); } protected override Result DoCommit() { - throw new NotImplementedException(); + return DoCommit(updateTimeStamp: true).Ret(); } public long GetCounterForBundledCommit() { - throw new NotImplementedException(); + return _saveFsDriver.GetCounterForBundledCommit(); } protected override Result DoCommitProvisionally(long counter) { - throw new NotImplementedException(); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + if (!_canCommitProvisionally) + return ResultFs.UnsupportedCommitProvisionallyForSaveDataFileSystem.Log(); + + return _saveFsDriver.CommitProvisionally(counter).Ret(); } protected override Result DoRollback() { - throw new NotImplementedException(); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return _saveFsDriver.Rollback().Ret(); } protected override Result DoGetFreeSpaceSize(out long freeSpace, ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.GetFreeSpaceSize(out freeSpace, in path).Ret(); } protected override Result DoGetTotalSpaceSize(out long totalSpace, ref readonly Path path) { - throw new NotImplementedException(); + return _saveFsDriver.GetTotalSpaceSize(out totalSpace, in path).Ret(); } protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute) { - throw new NotImplementedException(); + return _saveFsDriver.GetFileSystemAttribute(out outAttribute).Ret(); } public override Result WriteExtraData(in SaveDataExtraData extraData) { - throw new NotImplementedException(); + return _saveFsDriver.WriteExtraData(in Unsafe.As(ref Unsafe.AsRef(in extraData))).Ret(); } public override Result CommitExtraData(bool updateTimeStamp) { - throw new NotImplementedException(); + return DoCommit(updateTimeStamp).Ret(); } public override Result ReadExtraData(out SaveDataExtraData extraData) { - throw new NotImplementedException(); + UnsafeHelpers.SkipParamInit(out extraData); + _saveFsDriver.ReadExtraData(out Unsafe.As(ref extraData)); + + return Result.Success; } - public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) + public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, + SaveDataSpaceId spaceId, ulong saveDataId) { - throw new NotImplementedException(); + _cacheObserver = observer; + _spaceId = spaceId; + _saveDataId = saveDataId; } public override bool IsSaveDataFileSystemCacheEnabled() { - throw new NotImplementedException(); + return true; } public override Result RollbackOnlyModified() { - throw new NotImplementedException(); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return _saveFsDriver.RollbackOnlyModified().Ret(); } public IInternalStorageFileSystem GetInternalStorageFileSystem() { - throw new NotImplementedException(); + return _saveFsDriver; } } \ No newline at end of file diff --git a/src/LibHac/FsSystem/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSystem/SaveDataResultConvertFileSystem.cs index 12c3f159..7d4c97d1 100644 --- a/src/LibHac/FsSystem/SaveDataResultConvertFileSystem.cs +++ b/src/LibHac/FsSystem/SaveDataResultConvertFileSystem.cs @@ -9,7 +9,7 @@ namespace LibHac.FsSystem; /// Wraps an , converting its returned s /// to save-data-specific s. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class SaveDataResultConvertFile : IResultConvertFile { private bool _isReconstructible; @@ -29,13 +29,13 @@ public class SaveDataResultConvertFile : IResultConvertFile /// Wraps an , converting its returned s /// to save-data-specific s. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class SaveDataResultConvertDirectory : IResultConvertDirectory { private bool _isReconstructible; - public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory, bool isReconstructible) : base( - ref baseDirectory) + public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory, bool isReconstructible) + : base(ref baseDirectory) { _isReconstructible = isReconstructible; } @@ -50,7 +50,7 @@ public class SaveDataResultConvertDirectory : IResultConvertDirectory /// Wraps an , converting its returned s /// to save-data-specific s. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class SaveDataResultConvertFileSystem : IResultConvertFileSystem { private bool _isReconstructible; diff --git a/src/LibHac/FsSystem/SwitchStorage.cs b/src/LibHac/FsSystem/SwitchStorage.cs index 5561f4a4..51c75f2e 100644 --- a/src/LibHac/FsSystem/SwitchStorage.cs +++ b/src/LibHac/FsSystem/SwitchStorage.cs @@ -112,7 +112,7 @@ public class SwitchStorage : IStorage /// the provided will be forwarded to one , and requests outside /// will be forwarded to the other. /// -/// Based on nnSdk 14.3.0 (FS 14.1.0) +/// Based on nnSdk 17.5.0 (FS 17.0.0) public class RegionSwitchStorage : IStorage { public struct Region