From cac796ac19282ad271c10f1021725df9c07fa2df Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Thu, 21 Apr 2022 19:29:03 -0700 Subject: [PATCH 1/4] Add the new save data cache classes from 14.0.0 --- .../SaveDataExtraDataAccessorCacheManager.cs | 19 +- .../Impl/SaveDataFileSystemCacheManager.cs | 165 ++++++++++++++++++ .../Impl/SaveDataFileSystemCacheRegister.cs | 117 +++++++++++++ .../Impl/SaveDataResultConvertFileSystem.cs | 45 ----- .../FsSrv/SaveDataFileSystemServiceImpl.cs | 4 +- .../ApplicationTemporaryFileSystem.cs | 2 +- .../FsSystem/DirectorySaveDataFileSystem.cs | 4 +- .../FsSystem/ISaveDataExtraDataAccessor.cs | 2 +- ...ISaveDataExtraDataAccessorCacheObserver.cs | 4 +- src/LibHac/FsSystem/ISaveDataFileSystem.cs | 22 +++ 10 files changed, 322 insertions(+), 62 deletions(-) create mode 100644 src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheManager.cs create mode 100644 src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs create mode 100644 src/LibHac/FsSystem/ISaveDataFileSystem.cs diff --git a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs index 1d3ce965..abaa6f6c 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs @@ -10,13 +10,13 @@ namespace LibHac.FsSrv.Impl; /// /// Holds the s for opened save data file systems. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorCacheObserver +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorObserver { /// /// Holds a single cached extra data accessor identified by its save data ID and save data space ID. /// - /// Based on FS 13.1.0 (nnSdk 13.4.0) + /// Based on FS 14.1.0 (nnSdk 14.3.0) [NonCopyable] private struct Cache : IDisposable { @@ -77,13 +77,14 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorC public Result Register(in SharedRef accessor, SaveDataSpaceId spaceId, ulong saveDataId) { + accessor.Get.RegisterCacheObserver(this, spaceId, saveDataId); + var node = new LinkedListNode(new Cache(in accessor, spaceId, saveDataId)); - using (ScopedLock.Lock(ref _mutex)) - { - UnregisterImpl(spaceId, saveDataId); - _accessorList.AddLast(node); - } + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + UnregisterImpl(spaceId, saveDataId); + _accessorList.AddLast(node); return Result.Success; } @@ -134,7 +135,7 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorC if (!accessor.HasValue) return ResultFs.TargetNotFound.Log(); - outAccessor.Reset(new SaveDataExtraDataResultConvertAccessor(ref accessor.Ref())); + outAccessor.SetByMove(ref accessor.Ref()); return Result.Success; } diff --git a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheManager.cs b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheManager.cs new file mode 100644 index 00000000..f2bf5858 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheManager.cs @@ -0,0 +1,165 @@ +using System; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; +using LibHac.FsSystem; +using LibHac.Os; + +namespace LibHac.FsSrv.Impl; + +/// +/// Manages a list of cached save data file systems. Each file system is registered and retrieved +/// based on its save data ID and save data space ID. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataFileSystemCacheManager : IDisposable +{ + [NonCopyable] + private struct Cache + { + private SharedRef _fileSystem; + private ulong _saveDataId; + private SaveDataSpaceId _spaceId; + + public void Dispose() + { + _fileSystem.Destroy(); + } + + public bool IsCached(SaveDataSpaceId spaceId, ulong saveDataId) + { + return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId; + } + + public SharedRef Move() + { + return SharedRef.CreateMove(ref _fileSystem); + } + + public void Register(ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId) + { + _fileSystem.SetByMove(ref fileSystem); + _spaceId = spaceId; + _saveDataId = saveDataId; + } + + public void Unregister() + { + _fileSystem.Reset(); + } + } + + private SdkRecursiveMutexType _mutex; + private Cache[] _cachedFileSystems; + private int _maxCachedFileSystemCount; + private int _nextCacheIndex; + + public SaveDataFileSystemCacheManager() + { + _mutex = new SdkRecursiveMutexType(); + } + + public void Dispose() + { + Cache[] caches = Shared.Move(ref _cachedFileSystems); + + if (caches is not null) + { + for (int i = 0; i < caches.Length; i++) + { + caches[i].Dispose(); + } + } + } + + public Result Initialize(int maxCacheCount) + { + Assert.SdkRequiresGreaterEqual(maxCacheCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + Assert.SdkAssert(_cachedFileSystems is null); + + _maxCachedFileSystemCount = maxCacheCount; + if (maxCacheCount > 0) + { + // Note: The original checks for overflow here + _cachedFileSystems = new Cache[maxCacheCount]; + } + + return Result.Success; + } + + public UniqueLockRef GetScopedLock() + { + return new UniqueLockRef(ref _mutex); + } + + public bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) + { + using SharedRef cachedFs = _cachedFileSystems[i].Move(); + outFileSystem.SetByMove(ref cachedFs.Ref()); + + return true; + } + } + + return false; + } + + public void Register(ref SharedRef fileSystem, SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequiresNotNull(in fileSystem); + + if (_maxCachedFileSystemCount <= 0) + return; + + Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0); + Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex); + + if (!fileSystem.Get.IsSaveDataFileSystemCacheEnabled()) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + fileSystem.Reset(); + } + else if (spaceId == SaveDataSpaceId.SdSystem) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + fileSystem.Reset(); + } + else + { + Result rc = fileSystem.Get.RollbackOnlyModified(); + if (rc.IsSuccess()) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + _cachedFileSystems[_nextCacheIndex].Register(ref fileSystem, spaceId, saveDataId); + _nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount; + } + } + } + + public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) + { + Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); + + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + for (int i = 0; i < _maxCachedFileSystemCount; i++) + { + if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) + { + _cachedFileSystems[i].Unregister(); + } + } + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs new file mode 100644 index 00000000..c2166d00 --- /dev/null +++ b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs @@ -0,0 +1,117 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; + +namespace LibHac.FsSrv.Impl; + +/// +/// Wraps an . +/// Upon disposal the base file system is returned to the provided . +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataFileSystemCacheRegister : IFileSystem +{ + private SharedRef _baseFileSystem; + private SaveDataFileSystemCacheManager _cacheManager; + private SaveDataSpaceId _spaceId; + private ulong _saveDataId; + + public SaveDataFileSystemCacheRegister(ref SharedRef baseFileSystem, + SaveDataFileSystemCacheManager cacheManager, SaveDataSpaceId spaceId, ulong saveDataId) + { + _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); + _cacheManager = cacheManager; + _spaceId = spaceId; + _saveDataId = saveDataId; + } + + public override void Dispose() + { + _cacheManager.Register(ref _baseFileSystem, _spaceId, _saveDataId); + _baseFileSystem.Destroy(); + + base.Dispose(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + return _baseFileSystem.Get.OpenFile(ref outFile, in path, mode); + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + return _baseFileSystem.Get.OpenDirectory(ref outDirectory, in path, mode); + } + + protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) + { + return _baseFileSystem.Get.GetEntryType(out entryType, in path); + } + + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) + { + return _baseFileSystem.Get.CreateFile(in path, size, option); + } + + protected override Result DoDeleteFile(in Path path) + { + return _baseFileSystem.Get.DeleteFile(in path); + } + + protected override Result DoCreateDirectory(in Path path) + { + return _baseFileSystem.Get.CreateDirectory(in path); + } + + protected override Result DoDeleteDirectory(in Path path) + { + return _baseFileSystem.Get.DeleteDirectory(in path); + } + + protected override Result DoDeleteDirectoryRecursively(in Path path) + { + return _baseFileSystem.Get.DeleteDirectoryRecursively(in path); + } + + protected override Result DoCleanDirectoryRecursively(in Path path) + { + return _baseFileSystem.Get.CleanDirectoryRecursively(in path); + } + + protected override Result DoRenameFile(in Path currentPath, in Path newPath) + { + return _baseFileSystem.Get.RenameFile(in currentPath, in newPath); + } + + protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) + { + return _baseFileSystem.Get.RenameDirectory(in currentPath, in newPath); + } + + protected override Result DoCommit() + { + return _baseFileSystem.Get.Commit(); + } + + protected override Result DoCommitProvisionally(long counter) + { + return _baseFileSystem.Get.CommitProvisionally(counter); + } + + protected override Result DoRollback() + { + return _baseFileSystem.Get.Rollback(); + } + + protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) + { + return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path); + } + + protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) + { + return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs index d49eab27..e53dffc8 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs @@ -2,7 +2,6 @@ using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; -using LibHac.FsSystem; namespace LibHac.FsSrv.Impl; @@ -201,48 +200,4 @@ public class SaveDataResultConvertFileSystem : IResultConvertFileSystem { return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result); } -} - -/// -/// Wraps an , converting its returned s -/// to save-data-specific s. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataExtraDataResultConvertAccessor : ISaveDataExtraDataAccessor -{ - private SharedRef _accessor; - - public SaveDataExtraDataResultConvertAccessor(ref SharedRef accessor) - { - _accessor = SharedRef.CreateMove(ref accessor); - } - - public void Dispose() - { - _accessor.Destroy(); - } - - public Result WriteExtraData(in SaveDataExtraData extraData) - { - Result rc = _accessor.Get.WriteExtraData(in extraData); - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(rc); - } - - public Result CommitExtraData(bool updateTimeStamp) - { - Result rc = _accessor.Get.CommitExtraData(updateTimeStamp); - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(rc); - } - - public Result ReadExtraData(out SaveDataExtraData extraData) - { - Result rc = _accessor.Get.ReadExtraData(out extraData); - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(rc); - } - - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, - ulong saveDataId) - { - _accessor.Get.RegisterCacheObserver(observer, spaceId, saveDataId); - } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 3fd82d87..4b30b23d 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -20,7 +20,7 @@ public class SaveDataFileSystemServiceImpl private Configuration _config; private EncryptionSeed _encryptionSeed; - private SaveDataFileSystemCacheManager _saveDataFsCacheManager; + private FsSystem.SaveDataFileSystemCacheManager _saveDataFsCacheManager; private SaveDataExtraDataAccessorCacheManager _extraDataCacheManager; // Save data porter manager private bool _isSdCardAccessible; @@ -47,7 +47,7 @@ public class SaveDataFileSystemServiceImpl public SaveDataFileSystemServiceImpl(in Configuration configuration) { _config = configuration; - _saveDataFsCacheManager = new SaveDataFileSystemCacheManager(); + _saveDataFsCacheManager = new FsSystem.SaveDataFileSystemCacheManager(); _extraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); _timeStampGetter = new TimeStampGetter(this); diff --git a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs index c96ea630..7c5e722a 100644 --- a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs +++ b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs @@ -83,7 +83,7 @@ public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAcc throw new NotImplementedException(); } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) + public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) { throw new NotImplementedException(); } diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index 7e610c01..c56ed65c 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -50,7 +50,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess private RandomDataGenerator _randomGenerator; // Additions to support caching - private ISaveDataExtraDataAccessorCacheObserver _cacheObserver; + private ISaveDataExtraDataAccessorObserver _cacheObserver; private SaveDataSpaceId _spaceId; private ulong _saveDataId; @@ -1056,7 +1056,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, + public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) { _cacheObserver = observer; diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs index 3fdc1341..4ee8ccc3 100644 --- a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs @@ -12,5 +12,5 @@ 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); + void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver 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 index cb3c74d7..782d4b96 100644 --- a/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessorCacheObserver.cs @@ -9,8 +9,8 @@ namespace LibHac.FsSystem; /// . When an extra data accessor is disposed, the accessor will /// use this interface to notify the cache manager that it should be removed from the extra data cache. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public interface ISaveDataExtraDataAccessorCacheObserver : IDisposable +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public interface ISaveDataExtraDataAccessorObserver : IDisposable { void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataFileSystem.cs b/src/LibHac/FsSystem/ISaveDataFileSystem.cs new file mode 100644 index 00000000..61fdb234 --- /dev/null +++ b/src/LibHac/FsSystem/ISaveDataFileSystem.cs @@ -0,0 +1,22 @@ +using LibHac.Fs; +using LibHac.Fs.Fsa; + +namespace LibHac.FsSystem; + +// ReSharper disable once InconsistentNaming +public abstract class ISaveDataFileSystem : IFileSystem, ICacheableSaveDataFileSystem, ISaveDataExtraDataAccessor +{ + public abstract bool IsSaveDataFileSystemCacheEnabled(); + public abstract Result RollbackOnlyModified(); + + public abstract Result WriteExtraData(in SaveDataExtraData extraData); + public abstract Result CommitExtraData(bool updateTimeStamp); + public abstract Result ReadExtraData(out SaveDataExtraData extraData); + public abstract void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); +} + +public interface ICacheableSaveDataFileSystem +{ + bool IsSaveDataFileSystemCacheEnabled(); + Result RollbackOnlyModified(); +} \ No newline at end of file From bc7fea5714c98f1404a2ed8514b6eaeb5309b5cb Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 23 Apr 2022 12:17:46 -0700 Subject: [PATCH 2/4] Add an AlignOf function for use in struct layout tests --- tests/LibHac.Tests/Common/Layout.cs | 13 ++++++++++++ tests/LibHac.Tests/Fs/TypeLayoutTests.cs | 18 +++-------------- .../LibHac.Tests/FsSystem/TypeLayoutTests.cs | 20 +------------------ 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/tests/LibHac.Tests/Common/Layout.cs b/tests/LibHac.Tests/Common/Layout.cs index 1a0cb743..dc1842ac 100644 --- a/tests/LibHac.Tests/Common/Layout.cs +++ b/tests/LibHac.Tests/Common/Layout.cs @@ -16,4 +16,17 @@ public class Layout return offset; } + + public static int AlignOf() where T : unmanaged + { + return Unsafe.SizeOf>() - Unsafe.SizeOf(); + } + + private readonly struct AlignOfHelper where T : unmanaged + { +#pragma warning disable CS0169 // Field is never used + private readonly byte _padding; + private readonly T _value; +#pragma warning restore CS0169 + } } \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs index 4a6ef886..5ac2ee69 100644 --- a/tests/LibHac.Tests/Fs/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/Fs/TypeLayoutTests.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using LibHac.Fs; using LibHac.Fs.Impl; using Xunit; @@ -476,21 +475,10 @@ public class TypeLayoutTests Assert.Equal(0, GetOffset(in s, in s.Value)); } - [StructLayout(LayoutKind.Sequential)] - private struct Int64AlignmentTest - { - public byte A; - public Int64 B; - } - [Fact] - public static void Int64Test_Layout() + public static void Int64_Layout() { - var s = new Int64AlignmentTest(); - - Assert.Equal(12, Unsafe.SizeOf()); - - Assert.Equal(0, GetOffset(in s, in s.A)); - Assert.Equal(4, GetOffset(in s, in s.B)); + Assert.Equal(8, Unsafe.SizeOf()); + Assert.Equal(4, AlignOf()); } } \ No newline at end of file diff --git a/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs b/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs index c344430e..1ff05b94 100644 --- a/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs +++ b/tests/LibHac.Tests/FsSystem/TypeLayoutTests.cs @@ -1,5 +1,4 @@ using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using LibHac.FsSystem; using Xunit; using static LibHac.Tests.Common.Layout; @@ -287,6 +286,7 @@ public class TypeLayoutTests HierarchicalIntegrityVerificationLevelInformation s = default; Assert.Equal(0x18, Unsafe.SizeOf()); + Assert.Equal(0x04, AlignOf()); Assert.Equal(0x00, GetOffset(in s, in s.Offset)); Assert.Equal(0x08, GetOffset(in s, in s.Size)); @@ -294,24 +294,6 @@ public class TypeLayoutTests Assert.Equal(0x14, GetOffset(in s, in s.Reserved)); } - [StructLayout(LayoutKind.Sequential)] - private struct HierarchicalIntegrityVerificationLevelInformationAlignmentTest - { - public byte A; - public HierarchicalIntegrityVerificationLevelInformation B; - } - - [Fact] - public static void HierarchicalIntegrityVerificationLevelInformation_Alignment() - { - var s = new HierarchicalIntegrityVerificationLevelInformationAlignmentTest(); - - Assert.Equal(0x1C, Unsafe.SizeOf()); - - Assert.Equal(0, GetOffset(in s, in s.A)); - Assert.Equal(4, GetOffset(in s, in s.B)); - } - [Fact] public static void HierarchicalIntegrityVerificationInformation_Layout() { From 7dfcebfc28f4e9818c53b706a8813a33636d94f8 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sat, 23 Apr 2022 12:38:10 -0700 Subject: [PATCH 3/4] Update DirectorySaveDataFileSystem to implement ISaveDataFileSystem --- src/LibHac/FsSrv/DefaultFsServerObjects.cs | 7 +- .../FsSrv/FileSystemServerInitializer.cs | 14 +- .../FsCreator/SaveDataFileSystemCreator.cs | 4 +- .../SaveDataExtraDataAccessorCacheManager.cs | 2 +- .../FsSrv/SaveDataFileSystemServiceImpl.cs | 4 +- .../ApplicationTemporaryFileSystem.cs | 5 +- src/LibHac/FsSystem/Delegates.cs | 2 +- .../FsSystem/DirectorySaveDataFileSystem.cs | 307 ++++++++---------- .../FsSystem/ISaveDataExtraDataAccessor.cs | 4 +- src/LibHac/FsSystem/ISaveDataFileSystem.cs | 2 +- src/LibHac/HorizonFactory.cs | 12 +- .../FileSystemServerFactory.cs | 7 +- .../DirectorySaveDataFileSystemTests.cs | 7 +- tests/LibHac.Tests/HorizonFactory.cs | 7 +- 14 files changed, 188 insertions(+), 196 deletions(-) diff --git a/src/LibHac/FsSrv/DefaultFsServerObjects.cs b/src/LibHac/FsSrv/DefaultFsServerObjects.cs index 9f0317cd..91d4c3ee 100644 --- a/src/LibHac/FsSrv/DefaultFsServerObjects.cs +++ b/src/LibHac/FsSrv/DefaultFsServerObjects.cs @@ -2,6 +2,7 @@ using LibHac.Common.Keys; using LibHac.FsSrv.FsCreator; using LibHac.FsSrv.Sf; +using LibHac.FsSystem; using IFileSystem = LibHac.Fs.Fsa.IFileSystem; namespace LibHac.FsSrv; @@ -14,7 +15,7 @@ public class DefaultFsServerObjects public EmulatedSdCard SdCard { get; set; } public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet, - FileSystemServer fsServer) + FileSystemServer fsServer, RandomDataGenerator randomGenerator) { var creators = new FileSystemCreatorInterfaces(); var gameCard = new EmulatedGameCard(keySet); @@ -31,7 +32,7 @@ public class DefaultFsServerObjects creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet); creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator(); creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator(); - creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null); + creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, randomGenerator); creators.GameCardStorageCreator = gcStorageCreator; creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard); creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet); @@ -48,4 +49,4 @@ public class DefaultFsServerObjects SdCard = sdCard }; } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/FileSystemServerInitializer.cs b/src/LibHac/FsSrv/FileSystemServerInitializer.cs index 03c6a330..0f02dcf8 100644 --- a/src/LibHac/FsSrv/FileSystemServerInitializer.cs +++ b/src/LibHac/FsSrv/FileSystemServerInitializer.cs @@ -63,13 +63,6 @@ public static class FileSystemServerInitializer private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server, FileSystemServerConfig config) { - var random = new Random(); - RandomDataGenerator randomGenerator = buffer => - { - random.NextBytes(buffer); - return Result.Success; - }; - var bufferManager = new FileSystemBufferManager(); Memory heapBuffer = GC.AllocateArray(BufferManagerHeapSize, true); bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize); @@ -141,7 +134,7 @@ public static class FileSystemServerInitializer saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator; saveFsServiceConfig.ProgramRegistryService = programRegistryService; saveFsServiceConfig.BufferManager = bufferManager; - saveFsServiceConfig.GenerateRandomData = randomGenerator; + saveFsServiceConfig.GenerateRandomData = config.RandomGenerator; saveFsServiceConfig.IsPseudoSaveData = () => true; saveFsServiceConfig.MaxSaveFsCacheCount = 1; saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager; @@ -278,4 +271,9 @@ public class FileSystemServerConfig /// If null, an empty set will be created. /// public ExternalKeySet ExternalKeySet { get; set; } + + /// + /// Used for generating random data for save data. + /// + public RandomDataGenerator RandomGenerator { get; set; } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs index e649e72c..86c0d922 100644 --- a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs @@ -78,8 +78,8 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator using var saveDirFs = new SharedRef( new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs)); - rc = saveDirFs.Get.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported, - isMultiCommitSupported, !openReadOnly); + rc = saveDirFs.Get.Initialize(isJournalingSupported, isMultiCommitSupported, !openReadOnly, + timeStampGetter, _randomGenerator); if (rc.IsFailure()) return rc; outFileSystem.SetByCopy(in saveDirFs); diff --git a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs index abaa6f6c..a895928c 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataExtraDataAccessorCacheManager.cs @@ -77,7 +77,7 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorO public Result Register(in SharedRef accessor, SaveDataSpaceId spaceId, ulong saveDataId) { - accessor.Get.RegisterCacheObserver(this, spaceId, saveDataId); + accessor.Get.RegisterExtraDataAccessorObserver(this, spaceId, saveDataId); var node = new LinkedListNode(new Cache(in accessor, spaceId, saveDataId)); diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index 4b30b23d..dde416ac 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -170,7 +170,7 @@ public class SaveDataFileSystemServiceImpl // Cache the extra data accessor if needed if (cacheExtraData && extraDataAccessor.HasValue) { - extraDataAccessor.Get.RegisterCacheObserver(_extraDataCacheManager, spaceId, saveDataId); + extraDataAccessor.Get.RegisterExtraDataAccessorObserver(_extraDataCacheManager, spaceId, saveDataId); rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); if (rc.IsFailure()) return rc.Miss(); @@ -359,7 +359,7 @@ public class SaveDataFileSystemServiceImpl extraData.TimeStamp = 0; extraData.CommitId = 0; - _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)).IgnoreResult(); + _config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)); extraData.Flags = creationInfo.Flags; extraData.DataSize = creationInfo.Size; diff --git a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs index 7c5e722a..87de79d4 100644 --- a/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs +++ b/src/LibHac/FsSystem/ApplicationTemporaryFileSystem.cs @@ -83,8 +83,9 @@ public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAcc throw new NotImplementedException(); } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId) + public void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, + ulong saveDataId) { throw new NotImplementedException(); } -} +} \ No newline at end of file diff --git a/src/LibHac/FsSystem/Delegates.cs b/src/LibHac/FsSystem/Delegates.cs index 89a10d26..d2a2976d 100644 --- a/src/LibHac/FsSystem/Delegates.cs +++ b/src/LibHac/FsSystem/Delegates.cs @@ -2,4 +2,4 @@ namespace LibHac.FsSystem; -public delegate Result RandomDataGenerator(Span buffer); \ No newline at end of file +public delegate void RandomDataGenerator(Span buffer); \ No newline at end of file diff --git a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs index c56ed65c..dc657340 100644 --- a/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/DirectorySaveDataFileSystem.cs @@ -14,7 +14,7 @@ internal struct DirectorySaveDataFileSystemGlobals public void Initialize(FileSystemClient fsClient) { - SynchronizeDirectoryMutex.Initialize(); + SynchronizeDirectoryMutex = new SdkMutexType(); } } @@ -24,9 +24,9 @@ internal struct DirectorySaveDataFileSystemGlobals /// /// Transactional commits should be atomic as long as the function of the /// underlying is atomic. -/// Based on FS 13.1.0 (nnSdk 13.4.0) +/// Based on FS 14.1.0 (nnSdk 14.3.0) /// -public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor +public class DirectorySaveDataFileSystem : ISaveDataFileSystem { private const int IdealWorkBufferSize = 0x100000; // 1 MiB @@ -35,7 +35,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess private static ReadOnlySpan SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' }; private static ReadOnlySpan LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' }; - private FileSystemClient _fsClient; private IFileSystem _baseFs; private SdkMutexType _mutex; private UniqueRef _uniqueBaseFs; @@ -45,16 +44,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess private bool _isMultiCommitSupported; private bool _isJournalingEnabled; - // Additions to support extra data + private ISaveDataExtraDataAccessorObserver _cacheObserver; + private ulong _saveDataId; + private SaveDataSpaceId _spaceId; + private ISaveDataCommitTimeStampGetter _timeStampGetter; private RandomDataGenerator _randomGenerator; - // Additions to support caching - private ISaveDataExtraDataAccessorObserver _cacheObserver; - private SaveDataSpaceId _spaceId; - private ulong _saveDataId; + // LibHac additions + private FileSystemClient _fsClient; - // Additions to ensure only one directory save data fs is opened at a time + // Addition to ensure only one directory save data fs is opened at a time private UniqueRef _lockFile; private class DirectorySaveDataFile : IFile @@ -116,27 +116,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess } } - /// - /// Create an uninitialized . - /// - /// The base to use. - public DirectorySaveDataFileSystem(IFileSystem baseFileSystem) - { - _baseFs = baseFileSystem; - _mutex.Initialize(); - } - - /// - /// Create an uninitialized . - /// - /// The base to use. - public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem) - { - _baseFs = baseFileSystem.Get; - _mutex.Initialize(); - _uniqueBaseFs = new UniqueRef(ref baseFileSystem); - } - /// /// Create an uninitialized . /// If a is provided a global mutex will be used when synchronizing directories. @@ -145,10 +124,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess /// /// The base to use. /// The to use. May be null. - public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient) + public DirectorySaveDataFileSystem(IFileSystem baseFileSystem, FileSystemClient fsClient = null) { _baseFs = baseFileSystem; - _mutex.Initialize(); + _mutex = new SdkMutexType(); _fsClient = fsClient; } @@ -160,10 +139,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess /// /// The base to use. /// The to use. May be null. - public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem, FileSystemClient fsClient) + public DirectorySaveDataFileSystem(ref UniqueRef baseFileSystem, FileSystemClient fsClient = null) { _baseFs = baseFileSystem.Get; - _mutex.Initialize(); + _mutex = new SdkMutexType(); _uniqueBaseFs = new UniqueRef(ref baseFileSystem); _fsClient = fsClient; } @@ -185,6 +164,14 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess public Path ModifiedPath; public Path SynchronizingPath; + public RetryClosure(DirectorySaveDataFileSystem fs) + { + This = fs; + CommittedPath = new Path(); + ModifiedPath = new Path(); + SynchronizingPath = new Path(); + } + public void Dispose() { CommittedPath.Dispose(); @@ -195,7 +182,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess private delegate Result RetryDelegate(in RetryClosure closure); - private Result RetryFinitelyForTargetLocked(RetryDelegate function, in RetryClosure closure) + private Result RetryFinitelyForTargetLocked(in RetryClosure closure, RetryDelegate function) { const int maxRetryCount = 10; const int retryWaitTimeMs = 100; @@ -228,22 +215,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess } } - public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) - { - return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled); - } - - public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, - bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled) + public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, + bool isJournalingEnabled, ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator) { _isJournalingSupported = isJournalingSupported; _isMultiCommitSupported = isMultiCommitSupported; _isJournalingEnabled = isJournalingEnabled; - _timeStampGetter = timeStampGetter ?? _timeStampGetter; - _randomGenerator = randomGenerator ?? _randomGenerator; + _timeStampGetter = timeStampGetter; + _randomGenerator = randomGenerator; // Open the lock file - Result rc = GetFileSystemLock(); + Result rc = AcquireLockFile(); if (rc.IsFailure()) return rc; using var pathModifiedDirectory = new Path(); @@ -273,8 +255,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess { rc = _baseFs.CreateDirectory(in pathCommittedDirectory); - // Nintendo returns on all failures, but we'll keep going if committed already exists - // to avoid confusing people manually creating savedata in emulators + // Changed: Nintendo returns on all failures, but we'll keep going if committed already + // exists to avoid confusing people manually creating savedata in emulators if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc)) return rc; } @@ -316,7 +298,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } - private Result GetFileSystemLock() + private Result AcquireLockFile() { // Having an open lock file means we already have the lock for the file system. if (_lockFile.HasValue) @@ -326,7 +308,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName); if (rc.IsFailure()) return rc; - rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); + using var lockFile = new UniqueRef(); + rc = _baseFs.OpenFile(ref lockFile.Ref(), in pathLockFile, OpenMode.ReadWrite); if (rc.IsFailure()) { @@ -335,15 +318,16 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess rc = _baseFs.CreateFile(in pathLockFile, 0); if (rc.IsFailure()) return rc; - rc = _baseFs.OpenFile(ref _lockFile, in pathLockFile, OpenMode.ReadWrite); + rc = _baseFs.OpenFile(ref lockFile.Ref(), in pathLockFile, OpenMode.ReadWrite); if (rc.IsFailure()) return rc; } else { - return rc; + return rc.Miss(); } } + _lockFile.Set(ref lockFile.Ref()); return Result.Success; } @@ -552,8 +536,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess // Delete destination dir and recreate it. Result rc = _baseFs.DeleteDirectoryRecursively(destPath); - // Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations - // where a PathNotFound error would mean the save directory was in an invalid state. + // Changed: Nintendo returns all errors unconditionally because SynchronizeDirectory is always called + // in situations where a PathNotFound error would mean the save directory was in an invalid state. // We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally // put the save directory in an invalid state. if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc; @@ -563,7 +547,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess var directoryEntry = new DirectoryEntry(); - // Lock only if initialized with a client + // Changed: Lock only if initialized with a client if (_fsClient is not null) { using ScopedLock scopedLock = @@ -585,63 +569,63 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess } } - protected override Result DoCommit() + private Result DoCommit(bool updateTimeStamp) { - using ScopedLock lk = ScopedLock.Lock(ref _mutex); + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); if (!_isJournalingEnabled || !_isJournalingSupported) return Result.Success; - var closure = new RetryClosure(); - closure.This = this; + using var closure = new RetryClosure(this); - Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName); + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedDirectoryName); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedDirectoryName); + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath.Ref(), CommittedDirectoryName); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingDirectoryName); + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath.Ref(), SynchronizingDirectoryName); if (rc.IsFailure()) return rc; + // All files must be closed before commiting save data. if (_openWritableFileCount > 0) - { - // All files must be closed before commiting save data. return ResultFs.WriteModeFileNotClosed.Log(); - } - - static Result RenameCommittedDir(in RetryClosure closure) - { - return closure.This._baseFs.RenameDirectory(in closure.CommittedPath, - in closure.SynchronizingPath); - } - - static Result SynchronizeWorkingDir(in RetryClosure closure) - { - return closure.This.SynchronizeDirectory(in closure.SynchronizingPath, - in closure.ModifiedPath); - } - - static Result RenameSynchronizingDir(in RetryClosure closure) - { - return closure.This._baseFs.RenameDirectory(in closure.SynchronizingPath, - in closure.CommittedPath); - } // Get rid of the previous commit by renaming the folder. - rc = RetryFinitelyForTargetLocked(RenameCommittedDir, in closure); + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This._baseFs.RenameDirectory(in c.CommittedPath, in c.SynchronizingPath)); if (rc.IsFailure()) return rc; - // If something goes wrong beyond this point, the commit will be - // completed the next time the savedata is opened. + // If something goes wrong beyond this point, the commit of the main data + // will be completed the next time the savedata is opened. - rc = RetryFinitelyForTargetLocked(SynchronizeWorkingDir, in closure); + if (updateTimeStamp && _timeStampGetter is not null) + { + Assert.SdkNotNull(_randomGenerator); + + rc = UpdateExtraDataTimeStamp(); + if (rc.IsFailure()) return rc.Miss(); + } + + rc = CommitExtraDataImpl(); + if (rc.IsFailure()) return rc.Miss(); + + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This.SynchronizeDirectory(in c.SynchronizingPath, in c.ModifiedPath)); if (rc.IsFailure()) return rc; - rc = RetryFinitelyForTargetLocked(RenameSynchronizingDir, in closure); + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This._baseFs.RenameDirectory(in c.SynchronizingPath, in c.CommittedPath)); if (rc.IsFailure()) return rc; - closure.Dispose(); + return Result.Success; + } + + protected override Result DoCommit() + { + Result rc = DoCommit(updateTimeStamp: true); + if (rc.IsFailure()) return rc.Miss(); + return Result.Success; } @@ -655,11 +639,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess protected override Result DoRollback() { - // No old data is kept for non-journaling save data, so there's nothing to rollback to - if (!_isJournalingSupported) - return Result.Success; + // No old data is kept for non-journaling save data, so there's nothing to rollback to in that case + if (_isJournalingSupported) + { + Result rc = Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled, + _timeStampGetter, _randomGenerator); + if (rc.IsFailure()) return rc.Miss(); + } - return Initialize(_isJournalingSupported, _isMultiCommitSupported, _isJournalingEnabled); + return Result.Success; } protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) @@ -694,6 +682,46 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } + public override bool IsSaveDataFileSystemCacheEnabled() + { + return false; + } + + public override Result RollbackOnlyModified() + { + return ResultFs.UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem.Log(); + } + + public override Result WriteExtraData(in SaveDataExtraData extraData) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return WriteExtraDataImpl(in extraData); + } + + public override Result CommitExtraData(bool updateTimeStamp) + { + Result rc = DoCommit(updateTimeStamp); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result ReadExtraData(out SaveDataExtraData extraData) + { + using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); + + return ReadExtraDataImpl(out extraData); + } + + public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, + SaveDataSpaceId spaceId, ulong saveDataId) + { + _cacheObserver = observer; + _spaceId = spaceId; + _saveDataId = saveDataId; + } + private void DecrementWriteOpenFileCount() { // Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile @@ -703,7 +731,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess _openWritableFileCount--; } - // The original class doesn't support extra data. + // The original class doesn't support transactional extra data, + // always writing the extra data directly to the /extradata file. // Everything below this point is a LibHac extension. private static ReadOnlySpan CommittedExtraDataName => // "/ExtraData0" @@ -860,6 +889,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } + private Result GetExtraDataPath(ref Path path) + { + ReadOnlySpan extraDataName = _isJournalingSupported && !_isJournalingEnabled + ? CommittedExtraDataName + : ModifiedExtraDataName; + + return PathFunctions.SetUpFixedPath(ref path, extraDataName); + } + private Result EnsureExtraDataSize(in Path path) { using var file = new UniqueRef(); @@ -902,42 +940,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } - private Result GetExtraDataPath(ref Path path) - { - ReadOnlySpan extraDataName = _isJournalingSupported && !_isJournalingEnabled - ? CommittedExtraDataName - : ModifiedExtraDataName; - - return PathFunctions.SetUpFixedPath(ref path, extraDataName); - } - - public Result WriteExtraData(in SaveDataExtraData extraData) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - return WriteExtraDataImpl(in extraData); - } - - public Result CommitExtraData(bool updateTimeStamp) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - if (updateTimeStamp && _timeStampGetter is not null && _randomGenerator is not null) - { - Result rc = UpdateExtraDataTimeStamp(); - if (rc.IsFailure()) return rc; - } - - return CommitExtraDataImpl(); - } - - public Result ReadExtraData(out SaveDataExtraData extraData) - { - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - return ReadExtraDataImpl(out extraData); - } - private Result UpdateExtraDataTimeStamp() { Assert.SdkRequires(_mutex.IsLockedByCurrentThread()); @@ -987,50 +989,33 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess if (!_isJournalingSupported || !_isJournalingEnabled) return Result.Success; - var closure = new RetryClosure(); - closure.This = this; + using var closure = new RetryClosure(this); - Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName); + Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedExtraDataName); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath, CommittedExtraDataName); + rc = PathFunctions.SetUpFixedPath(ref closure.CommittedPath.Ref(), CommittedExtraDataName); if (rc.IsFailure()) return rc; - rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath, SynchronizingExtraDataName); + rc = PathFunctions.SetUpFixedPath(ref closure.SynchronizingPath.Ref(), SynchronizingExtraDataName); if (rc.IsFailure()) return rc; - static Result RenameCommittedFile(in RetryClosure closure) - { - return closure.This._baseFs.RenameFile(in closure.CommittedPath, - in closure.SynchronizingPath); - } - - static Result SynchronizeWorkingFile(in RetryClosure closure) - { - return closure.This.SynchronizeExtraData(in closure.SynchronizingPath, - in closure.ModifiedPath); - } - - static Result RenameSynchronizingFile(in RetryClosure closure) - { - return closure.This._baseFs.RenameFile(in closure.SynchronizingPath, - in closure.CommittedPath); - } - // Get rid of the previous commit by renaming the file. - rc = RetryFinitelyForTargetLocked(RenameCommittedFile, in closure); + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This._baseFs.RenameFile(in c.CommittedPath, in c.SynchronizingPath)); if (rc.IsFailure()) return rc; // If something goes wrong beyond this point, the commit will be // completed the next time the savedata is opened. - rc = RetryFinitelyForTargetLocked(SynchronizeWorkingFile, in closure); + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This.SynchronizeExtraData(in c.SynchronizingPath, in c.ModifiedPath)); if (rc.IsFailure()) return rc; - rc = RetryFinitelyForTargetLocked(RenameSynchronizingFile, in closure); + rc = RetryFinitelyForTargetLocked(in closure, + (in RetryClosure c) => c.This._baseFs.RenameFile(in c.SynchronizingPath, in c.CommittedPath)); if (rc.IsFailure()) return rc; - closure.Dispose(); return Result.Success; } @@ -1056,14 +1041,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess return Result.Success; } - public void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, - ulong saveDataId) - { - _cacheObserver = observer; - _spaceId = spaceId; - _saveDataId = saveDataId; - } - public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId; public ulong GetSaveDataId() => _saveDataId; } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs index 4ee8ccc3..6b1a71ff 100644 --- a/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs +++ b/src/LibHac/FsSystem/ISaveDataExtraDataAccessor.cs @@ -6,11 +6,11 @@ namespace LibHac.FsSystem; /// /// Provides read/write access to a save data file system's extra data. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) +/// Based on FS 14.1.0 (nnSdk 14.3.0) public interface ISaveDataExtraDataAccessor : IDisposable { Result WriteExtraData(in SaveDataExtraData extraData); Result CommitExtraData(bool updateTimeStamp); Result ReadExtraData(out SaveDataExtraData extraData); - void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); + void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataFileSystem.cs b/src/LibHac/FsSystem/ISaveDataFileSystem.cs index 61fdb234..57c993fb 100644 --- a/src/LibHac/FsSystem/ISaveDataFileSystem.cs +++ b/src/LibHac/FsSystem/ISaveDataFileSystem.cs @@ -12,7 +12,7 @@ public abstract class ISaveDataFileSystem : IFileSystem, ICacheableSaveDataFileS public abstract Result WriteExtraData(in SaveDataExtraData extraData); public abstract Result CommitExtraData(bool updateTimeStamp); public abstract Result ReadExtraData(out SaveDataExtraData extraData); - public abstract void RegisterCacheObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); + public abstract void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId); } public interface ICacheableSaveDataFileSystem diff --git a/src/LibHac/HorizonFactory.cs b/src/LibHac/HorizonFactory.cs index b4beef80..02085a0b 100644 --- a/src/LibHac/HorizonFactory.cs +++ b/src/LibHac/HorizonFactory.cs @@ -1,7 +1,9 @@ -using LibHac.Bcat; +using System; +using LibHac.Bcat; using LibHac.Common.Keys; using LibHac.Fs.Fsa; using LibHac.FsSrv; +using LibHac.FsSystem; namespace LibHac; @@ -15,13 +17,17 @@ public static class HorizonFactory HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer); + var random = new Random(); + RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFileSystem, keySet, fsServer, randomGenerator); var fsServerConfig = new FileSystemServerConfig { DeviceOperator = defaultObjects.DeviceOperator, ExternalKeySet = keySet.ExternalKeySet, FsCreators = defaultObjects.FsCreators, + RandomGenerator = randomGenerator }; FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig); @@ -31,4 +37,4 @@ public static class HorizonFactory return horizon; } -} +} \ No newline at end of file diff --git a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs index bbb61dff..185121da 100644 --- a/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs +++ b/tests/LibHac.Tests/Fs/FileSystemClientTests/FileSystemServerFactory.cs @@ -2,6 +2,7 @@ using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSrv; +using LibHac.FsSystem; using LibHac.Tools.Fs; namespace LibHac.Tests.Fs.FileSystemClientTests; @@ -18,7 +19,10 @@ public static class FileSystemServerFactory HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); + var random = new Random(12345); + RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator); defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted); @@ -26,6 +30,7 @@ public static class FileSystemServerFactory config.FsCreators = defaultObjects.FsCreators; config.DeviceOperator = defaultObjects.DeviceOperator; config.ExternalKeySet = new ExternalKeySet(); + config.RandomGenerator = randomGenerator; FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); return horizon; diff --git a/tests/LibHac.Tests/FsSystem/DirectorySaveDataFileSystemTests.cs b/tests/LibHac.Tests/FsSystem/DirectorySaveDataFileSystemTests.cs index 8707dfdb..ee604bbc 100644 --- a/tests/LibHac.Tests/FsSystem/DirectorySaveDataFileSystemTests.cs +++ b/tests/LibHac.Tests/FsSystem/DirectorySaveDataFileSystemTests.cs @@ -48,8 +48,8 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests FileSystemClient fsClient) { var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient); - Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported, - isJournalingEnabled); + Result rc = obj.Initialize(isJournalingSupported, isMultiCommitSupported, isJournalingEnabled, timeStampGetter, + randomGenerator); if (rc.IsSuccess()) { @@ -521,7 +521,7 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests private int _index; - public Result GenerateRandom(Span output) + public void GenerateRandom(Span output) { if (output.Length != 8) throw new ArgumentException(); @@ -529,7 +529,6 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests Unsafe.As(ref MemoryMarshal.GetReference(output)) = Values[_index]; _index = (_index + 1) % Values.Length; - return Result.Success; } } diff --git a/tests/LibHac.Tests/HorizonFactory.cs b/tests/LibHac.Tests/HorizonFactory.cs index 1371d641..7eca909e 100644 --- a/tests/LibHac.Tests/HorizonFactory.cs +++ b/tests/LibHac.Tests/HorizonFactory.cs @@ -2,6 +2,7 @@ using LibHac.Common.Keys; using LibHac.Fs.Fsa; using LibHac.FsSrv; +using LibHac.FsSystem; using LibHac.Tools.Fs; namespace LibHac.Tests; @@ -18,12 +19,16 @@ public static class HorizonFactory HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient(); var fsServer = new FileSystemServer(fsServerClient); - var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer); + var random = new Random(12345); + RandomDataGenerator randomGenerator = buffer => random.NextBytes(buffer); + + var defaultObjects = DefaultFsServerObjects.GetDefaultEmulatedCreators(rootFs, keySet, fsServer, randomGenerator); var config = new FileSystemServerConfig(); config.FsCreators = defaultObjects.FsCreators; config.DeviceOperator = defaultObjects.DeviceOperator; config.ExternalKeySet = new ExternalKeySet(); + config.RandomGenerator = randomGenerator; FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config); From e24ee1b956ac893ef6d87051d603ae1d951e7c35 Mon Sep 17 00:00:00 2001 From: Alex Barney Date: Sun, 24 Apr 2022 16:51:51 -0700 Subject: [PATCH 4/4] Update IResultConvertFile and use the new save cache manager --- build/CodeGen/results.csv | 1 + src/LibHac/Fs/FsEnums.cs | 6 + src/LibHac/Fs/ResultFs.cs | 2 + .../FsCreator/ISaveDataFileSystemCreator.cs | 8 +- .../FsCreator/SaveDataFileSystemCreator.cs | 68 +++---- .../SaveDataResultConvertFileSystem.cs | 127 +++++++++++++ .../SaveDataResultConverter.cs} | 105 ++--------- .../Impl/SaveDataFileSystemCacheRegister.cs | 2 +- src/LibHac/FsSrv/Impl/SaveDataProperties.cs | 50 ++++- .../FsSrv/SaveDataFileSystemServiceImpl.cs | 128 +++++++------ .../IResultConvertFileSystem.cs | 104 ++++++----- .../ISaveDataFileSystemCacheManager.cs | 17 -- .../SaveDataFileSystemCacheManager.cs | 171 ------------------ .../SaveDataFileSystemCacheRegisterBase.cs | 132 -------------- .../FsSystem/SaveDataFileSystemHolder.cs | 59 ------ 15 files changed, 352 insertions(+), 628 deletions(-) create mode 100644 src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs rename src/LibHac/FsSrv/{Impl/SaveDataResultConvertFileSystem.cs => FsCreator/SaveDataResultConverter.cs} (58%) rename src/LibHac/{FsSrv/Impl => FsSystem}/IResultConvertFileSystem.cs (54%) delete mode 100644 src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs delete mode 100644 src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs delete mode 100644 src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs delete mode 100644 src/LibHac/FsSystem/SaveDataFileSystemHolder.cs diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index bba44f13..9b30f760 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -672,6 +672,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,4301,4499,,,SaveDataCorrupted, 2,4302,,,,UnsupportedSaveDataVersion, 2,4303,,,,InvalidSaveDataEntryType, +2,4304,,,,ReconstructibleSaveDataCorrupted, 2,4311,4319,,,SaveDataFileSystemCorrupted, 2,4312,,,,InvalidJournalIntegritySaveDataHashSize, diff --git a/src/LibHac/Fs/FsEnums.cs b/src/LibHac/Fs/FsEnums.cs index dc10a21e..d0e2d0d7 100644 --- a/src/LibHac/Fs/FsEnums.cs +++ b/src/LibHac/Fs/FsEnums.cs @@ -170,6 +170,12 @@ public enum SaveDataRank : byte Secondary = 1 } +public enum SaveDataFormatType : byte +{ + Normal = 0, + NoJournal = 1 +} + [Flags] public enum SaveDataFlags { diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 047d884b..20ee475b 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -1210,6 +1210,8 @@ public static class ResultFs public static Result.Base UnsupportedSaveDataVersion => new Result.Base(ModuleFs, 4302); /// Error code: 2002-4303; Inner value: 0x219e02 public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303); + /// Error code: 2002-4304; Inner value: 0x21a002 + public static Result.Base ReconstructibleSaveDataCorrupted => new Result.Base(ModuleFs, 4304); /// Error code: 2002-4311; Range: 4311-4319; Inner value: 0x21ae02 public static Result.Base SaveDataFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4311, 4319); } diff --git a/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs index ac66ca69..be6a6c27 100644 --- a/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/ISaveDataFileSystemCreator.cs @@ -10,12 +10,10 @@ public interface ISaveDataFileSystemCreator { Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode); - Result Create(ref SharedRef outFileSystem, - ref SharedRef outExtraDataAccessor, - ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, + Result Create(ref SharedRef outFileSystem, ref SharedRef baseFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool isDeviceUniqueMac, bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, - ISaveDataCommitTimeStampGetter timeStampGetter); + ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible); Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, ref SharedRef baseFileSystem); diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs index 86c0d922..ca94f92d 100644 --- a/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataFileSystemCreator.cs @@ -3,12 +3,9 @@ using System.Runtime.CompilerServices; using LibHac.Common; using LibHac.Common.FixedArrays; using LibHac.Common.Keys; -using LibHac.Diag; using LibHac.Fs; using LibHac.Fs.Fsa; using LibHac.FsSystem; -using LibHac.Tools.FsSystem; -using LibHac.Tools.FsSystem.Save; using LibHac.Util; using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType; @@ -17,10 +14,15 @@ namespace LibHac.FsSrv.FsCreator; public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator { + // Option to disable some restrictions enforced in actual FS. + private static readonly bool EnforceSaveTypeRestrictions = false; + + // ReSharper disable once NotAccessedField.Local private IBufferManager _bufferManager; private RandomDataGenerator _randomGenerator; // LibHac Additions + // ReSharper disable once NotAccessedField.Local private KeySet _keySet; private FileSystemServer _fsServer; @@ -38,54 +40,56 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator throw new NotImplementedException(); } - public Result Create(ref SharedRef outFileSystem, - ref SharedRef outExtraDataAccessor, - ISaveDataFileSystemCacheManager cacheManager, ref SharedRef baseFileSystem, - SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac, + public Result Create(ref SharedRef outFileSystem, ref SharedRef baseFileSystem, + SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool isDeviceUniqueMac, bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared, - ISaveDataCommitTimeStampGetter timeStampGetter) + ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible) { Unsafe.SkipInit(out Array18 saveImageNameBuffer); - Assert.SdkRequiresNotNull(cacheManager); - using var saveImageName = new Path(); Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer.Items, saveDataId); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); rc = baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in saveImageName); if (rc.IsFailure()) { - return ResultFs.PathNotFound.Includes(rc) ? ResultFs.TargetNotFound.LogConverted(rc) : rc; + return ResultFs.PathNotFound.Includes(rc) ? ResultFs.TargetNotFound.LogConverted(rc) : rc.Miss(); } + using var saveDataFs = new SharedRef(); + if (type == DirectoryEntryType.Directory) { - if (!allowDirectorySaveData) - return ResultFs.InvalidSaveDataEntryType.Log(); + if (EnforceSaveTypeRestrictions) + { + if (!allowDirectorySaveData) + return ResultFs.InvalidSaveDataEntryType.Log(); + } - using var baseFs = - new UniqueRef(new SubdirectoryFileSystem(ref baseFileSystem)); + // Get a file system over the save directory + using var baseFs = new UniqueRef(new SubdirectoryFileSystem(ref baseFileSystem)); if (!baseFs.HasValue) return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorA.Log(); rc = baseFs.Get.Initialize(in saveImageName); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); + // Create and initialize the directory save data FS using UniqueRef tempFs = UniqueRef.Create(ref baseFs.Ref()); using var saveDirFs = new SharedRef( new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs)); + if (!saveDirFs.HasValue) + return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorB.Log(); + rc = saveDirFs.Get.Initialize(isJournalingSupported, isMultiCommitSupported, !openReadOnly, timeStampGetter, _randomGenerator); - if (rc.IsFailure()) return rc; + if (rc.IsFailure()) return rc.Miss(); - outFileSystem.SetByCopy(in saveDirFs); - outExtraDataAccessor.SetByCopy(in saveDirFs); - - return Result.Success; + saveDataFs.SetByMove(ref saveDirFs.Ref()); } else { @@ -98,18 +102,16 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator OpenMode.ReadWrite, openType); if (rc.IsFailure()) return rc; - if (!isJournalingSupported) - { - throw new NotImplementedException(); - } - - using var saveFs = new SharedRef(new SaveDataFileSystem(_keySet, fileStorage.Get, - IntegrityCheckLevel.ErrorOnInvalid, false)); - - // Todo: ISaveDataExtraDataAccessor - - return Result.Success; + throw new NotImplementedException(); } + + // Wrap the save FS in a result convert FS and set it as the output FS + using var resultConvertFs = new SharedRef( + new SaveDataResultConvertFileSystem(ref saveDataFs.Ref(), isReconstructible)); + + outFileSystem.SetByMove(ref resultConvertFs.Ref()); + + return Result.Success; } public Result CreateExtraDataAccessor(ref SharedRef outExtraDataAccessor, diff --git a/src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs new file mode 100644 index 00000000..a9bcb457 --- /dev/null +++ b/src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs @@ -0,0 +1,127 @@ +using LibHac.Common; +using LibHac.Fs; +using LibHac.Fs.Fsa; +using LibHac.FsSystem; +using static LibHac.FsSrv.FsCreator.SaveDataResultConverter; + +namespace LibHac.FsSrv.FsCreator; + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataResultConvertFile : IResultConvertFile +{ + private bool _isReconstructible; + + public SaveDataResultConvertFile(ref UniqueRef baseFile, bool isReconstructible) : base(ref baseFile) + { + _isReconstructible = isReconstructible; + } + + protected override Result ConvertResult(Result result) + { + return ConvertSaveDataFsResult(result, _isReconstructible).Ret(); + } +} + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataResultConvertDirectory : IResultConvertDirectory +{ + private bool _isReconstructible; + + public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory, bool isReconstructible) : base( + ref baseDirectory) + { + _isReconstructible = isReconstructible; + } + + protected override Result ConvertResult(Result result) + { + return ConvertSaveDataFsResult(result, _isReconstructible).Ret(); + } +} + +/// +/// Wraps an , converting its returned s +/// to save-data-specific s. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SaveDataResultConvertFileSystem : IResultConvertFileSystem +{ + private bool _isReconstructible; + + public SaveDataResultConvertFileSystem(ref SharedRef baseFileSystem, bool isReconstructible) : + base(ref baseFileSystem) + { + _isReconstructible = isReconstructible; + } + + public override Result WriteExtraData(in SaveDataExtraData extraData) + { + return ConvertSaveDataFsResult(GetFileSystem().WriteExtraData(in extraData), _isReconstructible).Ret(); + } + + public override Result CommitExtraData(bool updateTimeStamp) + { + return ConvertSaveDataFsResult(GetFileSystem().CommitExtraData(updateTimeStamp), _isReconstructible).Ret(); + } + + public override Result ReadExtraData(out SaveDataExtraData extraData) + { + return ConvertSaveDataFsResult(GetFileSystem().ReadExtraData(out extraData), _isReconstructible).Ret(); + } + + public override Result RollbackOnlyModified() + { + return ConvertSaveDataFsResult(GetFileSystem().RollbackOnlyModified(), _isReconstructible).Ret(); + } + + protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) + { + using var file = new UniqueRef(); + Result rc = ConvertResult(GetFileSystem().OpenFile(ref file.Ref(), in path, mode)); + if (rc.IsFailure()) return rc.Miss(); + + using UniqueRef resultConvertFile = + new(new SaveDataResultConvertFile(ref file.Ref(), _isReconstructible)); + + outFile.Set(ref resultConvertFile.Ref()); + return Result.Success; + } + + protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, + OpenDirectoryMode mode) + { + using var directory = new UniqueRef(); + Result rc = ConvertResult(GetFileSystem().OpenDirectory(ref directory.Ref(), in path, mode)); + if (rc.IsFailure()) return rc.Miss(); + + using UniqueRef resultConvertDirectory = + new(new SaveDataResultConvertDirectory(ref directory.Ref(), _isReconstructible)); + + outDirectory.Set(ref resultConvertDirectory.Ref()); + return Result.Success; + } + + protected override Result ConvertResult(Result result) + { + return ConvertSaveDataFsResult(result, _isReconstructible).Ret(); + } + + public override bool IsSaveDataFileSystemCacheEnabled() + { + return GetFileSystem().IsSaveDataFileSystemCacheEnabled(); + } + + public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, + SaveDataSpaceId spaceId, ulong saveDataId) + { + GetFileSystem().RegisterExtraDataAccessorObserver(observer, spaceId, saveDataId); + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs b/src/LibHac/FsSrv/FsCreator/SaveDataResultConverter.cs similarity index 58% rename from src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs rename to src/LibHac/FsSrv/FsCreator/SaveDataResultConverter.cs index e53dffc8..98df4f6b 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataResultConvertFileSystem.cs +++ b/src/LibHac/FsSrv/FsCreator/SaveDataResultConverter.cs @@ -1,15 +1,13 @@ -using LibHac.Common; -using LibHac.Diag; +using LibHac.Diag; using LibHac.Fs; -using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.Impl; +namespace LibHac.FsSrv.FsCreator; /// /// Contains functions for converting internal save data s to external s. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public static class SaveDataResultConvert +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public static class SaveDataResultConverter { private static Result ConvertCorruptedResult(Result result) { @@ -32,8 +30,7 @@ public static class SaveDataResultConvert Assert.SdkAssert(false); } - - if (ResultFs.HostFileSystemCorrupted.Includes(result)) + else if (ResultFs.HostFileSystemCorrupted.Includes(result)) { if (ResultFs.HostEntryCorrupted.Includes(result)) return ResultFs.SaveDataHostEntryCorrupted.LogConverted(result); @@ -49,8 +46,7 @@ public static class SaveDataResultConvert Assert.SdkAssert(false); } - - if (ResultFs.DatabaseCorrupted.Includes(result)) + else if (ResultFs.DatabaseCorrupted.Includes(result)) { if (ResultFs.InvalidAllocationTableBlock.Includes(result)) return ResultFs.InvalidSaveDataAllocationTableBlock.LogConverted(result); @@ -75,8 +71,7 @@ public static class SaveDataResultConvert Assert.SdkAssert(false); } - - if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) + else if (ResultFs.ZeroBitmapFileCorrupted.Includes(result)) { if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result)) return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.LogConverted(result); @@ -87,11 +82,8 @@ public static class SaveDataResultConvert return result; } - public static Result ConvertSaveFsDriverPrivateResult(Result result) + private static Result ConvertResult(Result result) { - if (result.IsSuccess()) - return result; - if (ResultFs.UnsupportedVersion.Includes(result)) return ResultFs.UnsupportedSaveDataVersion.LogConverted(result); @@ -101,7 +93,7 @@ public static class SaveDataResultConvert ResultFs.DatabaseCorrupted.Includes(result) || ResultFs.ZeroBitmapFileCorrupted.Includes(result)) { - return ConvertCorruptedResult(result); + return ConvertCorruptedResult(result).Miss(); } if (ResultFs.FatFileSystemCorrupted.Includes(result)) @@ -116,9 +108,6 @@ public static class SaveDataResultConvert if (ResultFs.AlreadyExists.Includes(result)) return ResultFs.PathAlreadyExists.LogConverted(result); - if (ResultFs.InvalidOffset.Includes(result)) - return ResultFs.OutOfRange.LogConverted(result); - if (ResultFs.IncompatiblePath.Includes(result) || ResultFs.FileNotFound.Includes(result)) { @@ -127,77 +116,19 @@ public static class SaveDataResultConvert return result; } -} -/// -/// Wraps an , converting its returned s -/// to save-data-specific s. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataResultConvertFile : IResultConvertFile -{ - public SaveDataResultConvertFile(ref UniqueRef baseFile) : base(ref baseFile) + public static Result ConvertSaveDataFsResult(Result result, bool isReconstructible) { - } + if (result.IsSuccess()) + return Result.Success; - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result); - } -} + Result convertedResult = ConvertResult(result); -/// -/// Wraps an , converting its returned s -/// to save-data-specific s. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataResultConvertDirectory : IResultConvertDirectory -{ - public SaveDataResultConvertDirectory(ref UniqueRef baseDirectory) : base(ref baseDirectory) - { - } + if (isReconstructible && ResultFs.SaveDataCorrupted.Includes(convertedResult)) + { + return ResultFs.ReconstructibleSaveDataCorrupted.LogConverted(convertedResult); + } - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result); - } -} - -/// -/// Wraps an , converting its returned s -/// to save-data-specific s. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataResultConvertFileSystem : IResultConvertFileSystem -{ - public SaveDataResultConvertFileSystem(ref SharedRef baseFileSystem) - : base(ref baseFileSystem) - { - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - using var file = new UniqueRef(); - Result rc = ConvertResult(BaseFileSystem.Get.OpenFile(ref file.Ref(), path, mode)); - if (rc.IsFailure()) return rc; - - outFile.Reset(new SaveDataResultConvertFile(ref file.Ref())); - return Result.Success; - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - using var directory = new UniqueRef(); - Result rc = ConvertResult(BaseFileSystem.Get.OpenDirectory(ref directory.Ref(), path, mode)); - if (rc.IsFailure()) return rc; - - outDirectory.Reset(new SaveDataResultConvertDirectory(ref directory.Ref())); - return Result.Success; - } - - protected override Result ConvertResult(Result result) - { - return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result); + return convertedResult; } } \ No newline at end of file diff --git a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs index c2166d00..a1fc90ce 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs @@ -7,7 +7,7 @@ namespace LibHac.FsSrv.Impl; /// /// Wraps an . -/// Upon disposal the base file system is returned to the provided . +/// Upon disposal the base file system is returned to the provided . /// /// Based on FS 14.1.0 (nnSdk 14.3.0) public class SaveDataFileSystemCacheRegister : IFileSystem diff --git a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs index 82070e27..dec1956e 100644 --- a/src/LibHac/FsSrv/Impl/SaveDataProperties.cs +++ b/src/LibHac/FsSrv/Impl/SaveDataProperties.cs @@ -8,6 +8,20 @@ public static class SaveDataProperties public const long DefaultSaveDataBlockSize = 0x4000; public const long BcatSaveDataJournalSize = 0x200000; + public static bool IsJournalingSupported(SaveDataFormatType type) + { + switch (type) + { + case SaveDataFormatType.Normal: + return true; + case SaveDataFormatType.NoJournal: + return false; + default: + Abort.UnexpectedDefault(); + return default; + } + } + public static bool IsJournalingSupported(SaveDataType type) { switch (type) @@ -67,7 +81,6 @@ public static class SaveDataProperties switch (type) { case SaveDataType.System: - case SaveDataType.SystemBcat: return true; case SaveDataType.Account: case SaveDataType.Bcat: @@ -86,7 +99,6 @@ public static class SaveDataProperties switch (type) { case SaveDataType.System: - case SaveDataType.SystemBcat: return true; case SaveDataType.Account: case SaveDataType.Bcat: @@ -99,4 +111,36 @@ public static class SaveDataProperties return default; } } -} + + public static bool IsReconstructible(SaveDataType type, SaveDataSpaceId spaceId) + { + switch (spaceId) + { + case SaveDataSpaceId.System: + case SaveDataSpaceId.User: + case SaveDataSpaceId.ProperSystem: + case SaveDataSpaceId.SafeMode: + switch (type) + { + case SaveDataType.System: + case SaveDataType.Account: + case SaveDataType.Device: + return false; + case SaveDataType.Bcat: + case SaveDataType.Temporary: + case SaveDataType.Cache: + return true; + default: + Abort.UnexpectedDefault(); + return default; + } + case SaveDataSpaceId.SdSystem: + case SaveDataSpaceId.Temporary: + case SaveDataSpaceId.SdUser: + return true; + default: + Abort.UnexpectedDefault(); + return default; + } + } +} \ No newline at end of file diff --git a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs index dde416ac..0876cfaa 100644 --- a/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs +++ b/src/LibHac/FsSrv/SaveDataFileSystemServiceImpl.cs @@ -20,7 +20,7 @@ public class SaveDataFileSystemServiceImpl private Configuration _config; private EncryptionSeed _encryptionSeed; - private FsSystem.SaveDataFileSystemCacheManager _saveDataFsCacheManager; + private SaveDataFileSystemCacheManager _saveDataFsCacheManager; private SaveDataExtraDataAccessorCacheManager _extraDataCacheManager; // Save data porter manager private bool _isSdCardAccessible; @@ -47,7 +47,7 @@ public class SaveDataFileSystemServiceImpl public SaveDataFileSystemServiceImpl(in Configuration configuration) { _config = configuration; - _saveDataFsCacheManager = new FsSystem.SaveDataFileSystemCacheManager(); + _saveDataFsCacheManager = new SaveDataFileSystemCacheManager(); _extraDataCacheManager = new SaveDataExtraDataAccessorCacheManager(); _timeStampGetter = new TimeStampGetter(this); @@ -116,6 +116,7 @@ public class SaveDataFileSystemServiceImpl } } + // 14.3.0 public Result OpenSaveDataFileSystem(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData) { @@ -124,66 +125,65 @@ public class SaveDataFileSystemServiceImpl Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, true); if (rc.IsFailure()) return rc.Miss(); - bool allowDirectorySaveData = IsAllowedDirectorySaveData2(spaceId, in saveDataRootPath); + bool isEmulatedOnHost = IsAllowedDirectorySaveData(spaceId, in saveDataRootPath); - // Note: When directory save data is allowed, Nintendo creates the save directory if it doesn't exist. - // This bypasses normal save data creation, leaving the save with empty extra data. - // Instead, we return that the save doesn't exist if the directory is missing. - - using var saveDataFs = new SharedRef(); - using var cachedFs = new SharedRef(); - - // Note: Nintendo doesn't cache directory save data - // if (!allowDirectorySaveData) + if (isEmulatedOnHost) { - // Check if we have the requested file system cached - if (_saveDataFsCacheManager.GetCache(ref cachedFs.Ref(), spaceId, saveDataId)) - { - using var registerBase = new SharedRef( - new SaveDataFileSystemCacheRegisterBase(ref cachedFs.Ref(), - _saveDataFsCacheManager)); - - using var resultConvertFs = new SharedRef( - new SaveDataResultConvertFileSystem(ref registerBase.Ref())); - - saveDataFs.SetByMove(ref resultConvertFs.Ref()); - } - } - - // Create a new file system if it's not in the cache - if (!saveDataFs.HasValue) - { - using UniqueLockRef scopedLock = _extraDataCacheManager.GetScopedLock(); - using var extraDataAccessor = new SharedRef(); - - bool openShared = SaveDataProperties.IsSharedOpenNeeded(type); - bool isMultiCommitSupported = SaveDataProperties.IsMultiCommitSupported(type); - bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type); - bool useDeviceUniqueMac = IsDeviceUniqueMac(spaceId); - - rc = _config.SaveFsCreator.Create(ref saveDataFs.Ref(), ref extraDataAccessor.Ref(), - _saveDataFsCacheManager, ref fileSystem.Ref(), spaceId, saveDataId, allowDirectorySaveData, - useDeviceUniqueMac, isJournalingSupported, isMultiCommitSupported, openReadOnly, openShared, - _timeStampGetter); + // Create the save data directory on the host if needed. + Unsafe.SkipInit(out Array18 saveDirectoryNameBuffer); + using var saveDirectoryName = new Path(); + rc = PathFunctions.SetUpFixedPathSaveId(ref saveDirectoryName.Ref(), saveDirectoryNameBuffer.Items, saveDataId); if (rc.IsFailure()) return rc.Miss(); - // Cache the extra data accessor if needed - if (cacheExtraData && extraDataAccessor.HasValue) + rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveDirectoryName); + if (rc.IsFailure()) return rc.Miss(); + } + + using var saveDataFs = new SharedRef(); + + using (_saveDataFsCacheManager.GetScopedLock()) + using (_extraDataCacheManager.GetScopedLock()) + { + if (isEmulatedOnHost || !_saveDataFsCacheManager.GetCache(ref saveDataFs.Ref(), spaceId, saveDataId)) { - extraDataAccessor.Get.RegisterExtraDataAccessorObserver(_extraDataCacheManager, spaceId, saveDataId); + bool isDeviceUniqueMac = IsDeviceUniqueMac(spaceId); + bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(type); + bool isMultiCommitSupported = SaveDataProperties.IsMultiCommitSupported(type); + bool openShared = SaveDataProperties.IsSharedOpenNeeded(type); + bool isReconstructible = SaveDataProperties.IsReconstructible(type, spaceId); + + rc = _config.SaveFsCreator.Create(ref saveDataFs.Ref(), ref fileSystem.Ref(), spaceId, saveDataId, + isEmulatedOnHost, isDeviceUniqueMac, isJournalingSupported, isMultiCommitSupported, + openReadOnly, openShared, _timeStampGetter, isReconstructible); + if (rc.IsFailure()) return rc.Miss(); + } + + if (!isEmulatedOnHost && cacheExtraData) + { + using SharedRef extraDataAccessor = + SharedRef.CreateCopy(in saveDataFs); rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId); if (rc.IsFailure()) return rc.Miss(); } } + using var registerFs = new SharedRef( + new SaveDataFileSystemCacheRegister(ref saveDataFs.Ref(), _saveDataFsCacheManager, spaceId, saveDataId)); + if (openReadOnly) { - outFileSystem.Reset(new ReadOnlyFileSystem(ref saveDataFs.Ref())); + using SharedRef tempFs = SharedRef.CreateMove(ref registerFs.Ref()); + using var readOnlyFileSystem = new SharedRef(new ReadOnlyFileSystem(ref tempFs.Ref())); + + if (!readOnlyFileSystem.HasValue) + return ResultFs.AllocationMemoryFailedInSaveDataFileSystemServiceImplB.Log(); + + outFileSystem.SetByMove(ref readOnlyFileSystem.Ref()); } else { - outFileSystem.SetByMove(ref saveDataFs.Ref()); + outFileSystem.SetByMove(ref registerFs.Ref()); } return Result.Success; @@ -339,15 +339,15 @@ public class SaveDataFileSystemServiceImpl rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName); if (rc.IsFailure()) return rc.Miss(); - using var saveFileSystem = new SharedRef(); - using var extraDataAccessor = new SharedRef(); + using var saveFileSystem = new SharedRef(); bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(attribute.Type); + bool isReconstructible = SaveDataProperties.IsReconstructible(attribute.Type, creationInfo.SpaceId); - rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref extraDataAccessor.Ref(), - _saveDataFsCacheManager, ref fileSystem.Ref(), creationInfo.SpaceId, saveDataId, - allowDirectorySaveData: true, useDeviceUniqueMac: false, isJournalingSupported, - isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter); + rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref fileSystem.Ref(), creationInfo.SpaceId, + saveDataId, allowDirectorySaveData: true, isDeviceUniqueMac: false, isJournalingSupported, + isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter, + isReconstructible); if (rc.IsFailure()) return rc.Miss(); var extraData = new SaveDataExtraData(); @@ -365,10 +365,10 @@ public class SaveDataFileSystemServiceImpl extraData.DataSize = creationInfo.Size; extraData.JournalSize = creationInfo.JournalSize; - rc = extraDataAccessor.Get.WriteExtraData(in extraData); + rc = saveFileSystem.Get.WriteExtraData(in extraData); if (rc.IsFailure()) return rc.Miss(); - rc = extraDataAccessor.Get.CommitExtraData(true); + rc = saveFileSystem.Get.CommitExtraData(true); if (rc.IsFailure()) return rc.Miss(); } else @@ -552,8 +552,8 @@ public class SaveDataFileSystemServiceImpl using (var tmFileSystem = new SharedRef()) { // Ensure the target save data directory exists - rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath, false, true, - ResultFs.SaveDataRootPathUnavailable.Value); + rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath, + openCaseSensitive: false, ensureRootPathExists: true, ResultFs.SaveDataRootPathUnavailable.Value); if (rc.IsFailure()) return rc.Miss(); } @@ -564,8 +564,8 @@ public class SaveDataFileSystemServiceImpl rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path.Ref()); if (rc.IsFailure()) return rc.Miss(); - rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive, false, - ResultFs.SaveDataRootPathUnavailable.Value); + rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive, + ensureRootPathExists: false, ResultFs.SaveDataRootPathUnavailable.Value); if (rc.IsFailure()) return rc.Miss(); return Result.Success; @@ -696,20 +696,14 @@ public class SaveDataFileSystemServiceImpl throw new NotImplementedException(); } + /// + /// Checks if a save is to be stored on a host device. + /// public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath) { return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath); } - // Todo: remove once file save data is supported - // Used to always allow directory save data in OpenSaveDataFileSystem - public bool IsAllowedDirectorySaveData2(SaveDataSpaceId spaceId, in Path saveDataRootPath) - { - // Todo: remove "|| true" once file save data is supported - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in saveDataRootPath) || true; - } - public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId) { return spaceId == SaveDataSpaceId.System || diff --git a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs b/src/LibHac/FsSystem/IResultConvertFileSystem.cs similarity index 54% rename from src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs rename to src/LibHac/FsSystem/IResultConvertFileSystem.cs index 2f361d21..f65d3525 100644 --- a/src/LibHac/FsSrv/Impl/IResultConvertFileSystem.cs +++ b/src/LibHac/FsSystem/IResultConvertFileSystem.cs @@ -3,61 +3,62 @@ using LibHac.Common; using LibHac.Fs; using LibHac.Fs.Fsa; -namespace LibHac.FsSrv.Impl; +namespace LibHac.FsSystem; // ReSharper disable once InconsistentNaming /// /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) +/// Based on FS 14.1.0 (nnSdk 14.3.0) public abstract class IResultConvertFile : IFile { - protected UniqueRef BaseFile; + private UniqueRef _baseFile; protected IResultConvertFile(ref UniqueRef baseFile) { - BaseFile = new UniqueRef(ref baseFile); + _baseFile = new UniqueRef(ref baseFile); } public override void Dispose() { - BaseFile.Destroy(); + _baseFile.Destroy(); + base.Dispose(); } + protected abstract Result ConvertResult(Result result); + protected override Result DoRead(out long bytesRead, long offset, Span destination, in ReadOption option) { - return ConvertResult(BaseFile.Get.Read(out bytesRead, offset, destination, option)); + return ConvertResult(_baseFile.Get.Read(out bytesRead, offset, destination, in option)).Ret(); } protected override Result DoWrite(long offset, ReadOnlySpan source, in WriteOption option) { - return ConvertResult(BaseFile.Get.Write(offset, source, option)); + return ConvertResult(_baseFile.Get.Write(offset, source, in option)).Ret(); } protected override Result DoFlush() { - return ConvertResult(BaseFile.Get.Flush()); + return ConvertResult(_baseFile.Get.Flush()).Ret(); } protected override Result DoSetSize(long size) { - return ConvertResult(BaseFile.Get.SetSize(size)); + return ConvertResult(_baseFile.Get.SetSize(size)).Ret(); } protected override Result DoGetSize(out long size) { - return ConvertResult(BaseFile.Get.GetSize(out size)); + return ConvertResult(_baseFile.Get.GetSize(out size)).Ret(); } protected override Result DoOperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) { - return ConvertResult(BaseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer)); + return ConvertResult(_baseFile.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer)).Ret(); } - - protected abstract Result ConvertResult(Result result); } // ReSharper disable once InconsistentNaming @@ -65,33 +66,34 @@ public abstract class IResultConvertFile : IFile /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) +/// Based on FS 14.1.0 (nnSdk 14.3.0) public abstract class IResultConvertDirectory : IDirectory { - protected UniqueRef BaseDirectory; + private UniqueRef _baseDirectory; protected IResultConvertDirectory(ref UniqueRef baseDirectory) { - BaseDirectory = new UniqueRef(ref baseDirectory); + _baseDirectory = new UniqueRef(ref baseDirectory); } public override void Dispose() { - BaseDirectory.Destroy(); + _baseDirectory.Destroy(); + base.Dispose(); } + protected abstract Result ConvertResult(Result result); + protected override Result DoRead(out long entriesRead, Span entryBuffer) { - return ConvertResult(BaseDirectory.Get.Read(out entriesRead, entryBuffer)); + return ConvertResult(_baseDirectory.Get.Read(out entriesRead, entryBuffer)).Ret(); } protected override Result DoGetEntryCount(out long entryCount) { - return ConvertResult(BaseDirectory.Get.GetEntryCount(out entryCount)); + return ConvertResult(_baseDirectory.Get.GetEntryCount(out entryCount)).Ret(); } - - protected abstract Result ConvertResult(Result result); } // ReSharper disable once InconsistentNaming @@ -99,114 +101,110 @@ public abstract class IResultConvertDirectory : IDirectory /// Wraps an , converting its returned s to different /// s based on the function. /// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public abstract class IResultConvertFileSystem : IFileSystem +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public abstract class IResultConvertFileSystem : ISaveDataFileSystem where T : IFileSystem { - protected SharedRef BaseFileSystem; + private SharedRef _baseFileSystem; - protected IResultConvertFileSystem(ref SharedRef baseFileSystem) + protected IResultConvertFileSystem(ref SharedRef baseFileSystem) { - BaseFileSystem = SharedRef.CreateMove(ref baseFileSystem); + _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); } public override void Dispose() { - BaseFileSystem.Destroy(); + _baseFileSystem.Destroy(); + base.Dispose(); } + protected T GetFileSystem() => _baseFileSystem.Get; + + protected abstract Result ConvertResult(Result result); + protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) { - return ConvertResult(BaseFileSystem.Get.CreateFile(path, size, option)); + return ConvertResult(_baseFileSystem.Get.CreateFile(in path, size)).Ret(); } protected override Result DoDeleteFile(in Path path) { - return ConvertResult(BaseFileSystem.Get.DeleteFile(path)); + return ConvertResult(_baseFileSystem.Get.DeleteFile(in path)).Ret(); } protected override Result DoCreateDirectory(in Path path) { - return ConvertResult(BaseFileSystem.Get.CreateDirectory(path)); + return ConvertResult(_baseFileSystem.Get.CreateDirectory(in path)).Ret(); } protected override Result DoDeleteDirectory(in Path path) { - return ConvertResult(BaseFileSystem.Get.DeleteDirectory(path)); + return ConvertResult(_baseFileSystem.Get.DeleteDirectory(in path)).Ret(); } protected override Result DoDeleteDirectoryRecursively(in Path path) { - return ConvertResult(BaseFileSystem.Get.DeleteDirectoryRecursively(path)); + return ConvertResult(_baseFileSystem.Get.DeleteDirectoryRecursively(in path)).Ret(); } protected override Result DoCleanDirectoryRecursively(in Path path) { - return ConvertResult(BaseFileSystem.Get.CleanDirectoryRecursively(path)); + return ConvertResult(_baseFileSystem.Get.CleanDirectoryRecursively(in path)).Ret(); } protected override Result DoRenameFile(in Path currentPath, in Path newPath) { - return ConvertResult(BaseFileSystem.Get.RenameFile(currentPath, newPath)); + return ConvertResult(_baseFileSystem.Get.RenameFile(in currentPath, in newPath)).Ret(); } protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) { - return ConvertResult(BaseFileSystem.Get.RenameDirectory(currentPath, newPath)); + return ConvertResult(_baseFileSystem.Get.RenameDirectory(in currentPath, in newPath)).Ret(); } protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) { - return ConvertResult(BaseFileSystem.Get.GetEntryType(out entryType, path)); + return ConvertResult(_baseFileSystem.Get.GetEntryType(out entryType, in path)).Ret(); } - // Note: The original code uses templates to determine which type of IFile/IDirectory to return. To make things - // easier in C# these two functions have been made abstract functions. - protected abstract override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode); - - protected abstract override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode); - protected override Result DoCommit() { - return ConvertResult(BaseFileSystem.Get.Commit()); + return ConvertResult(_baseFileSystem.Get.Commit()).Ret(); } protected override Result DoCommitProvisionally(long counter) { - return ConvertResult(BaseFileSystem.Get.CommitProvisionally(counter)); + return ConvertResult(_baseFileSystem.Get.CommitProvisionally(counter)).Ret(); } protected override Result DoRollback() { - return ConvertResult(BaseFileSystem.Get.Rollback()); + return ConvertResult(_baseFileSystem.Get.Rollback()).Ret(); } protected override Result DoFlush() { - return ConvertResult(BaseFileSystem.Get.Flush()); + return ConvertResult(_baseFileSystem.Get.Flush()).Ret(); } protected override Result DoGetFileTimeStampRaw(out FileTimeStampRaw timeStamp, in Path path) { - return ConvertResult(BaseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, path)); + return ConvertResult(_baseFileSystem.Get.GetFileTimeStampRaw(out timeStamp, in path)).Ret(); } protected override Result DoQueryEntry(Span outBuffer, ReadOnlySpan inBuffer, QueryId queryId, in Path path) { - return ConvertResult(BaseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, path)); + return ConvertResult(_baseFileSystem.Get.QueryEntry(outBuffer, inBuffer, queryId, in path)).Ret(); } protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) { - return ConvertResult(BaseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path)); + return ConvertResult(_baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, in path)).Ret(); } protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) { - return ConvertResult(BaseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path)); + return ConvertResult(_baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, in path)).Ret(); } - - protected abstract Result ConvertResult(Result result); } \ No newline at end of file diff --git a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs deleted file mode 100644 index 5f053e83..00000000 --- a/src/LibHac/FsSystem/ISaveDataFileSystemCacheManager.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using LibHac.Common; -using LibHac.Fs; - -namespace LibHac.FsSystem; - -/// -/// Provides a mechanism for caching save data file systems. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public interface ISaveDataFileSystemCacheManager : IDisposable -{ - bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId); - void Register(ref SharedRef fileSystem); - void Register(ref SharedRef fileSystem); - void Unregister(SaveDataSpaceId spaceId, ulong saveDataId); -} \ No newline at end of file diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs deleted file mode 100644 index 1c19a119..00000000 --- a/src/LibHac/FsSystem/SaveDataFileSystemCacheManager.cs +++ /dev/null @@ -1,171 +0,0 @@ -using LibHac.Common; -using LibHac.Diag; -using LibHac.Fs; -using LibHac.Os; - -namespace LibHac.FsSystem; - -/// -/// Manages a list of cached save data file systems. Each file system is registered and retrieved -/// based on its save data ID and save data space ID. -/// -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager -{ - /// - /// Holds a single cached file system identified by its save data ID and save data space ID. - /// - /// Based on FS 13.1.0 (nnSdk 13.4.0) - [NonCopyable] - private struct Cache - { - // Note: Nintendo only supports caching SaveDataFileSystem. We support DirectorySaveDataFileSystem too, - // so we use a wrapper class to simplify the logic here. - private SharedRef _fileSystem; - private ulong _saveDataId; - private SaveDataSpaceId _spaceId; - - public void Dispose() - { - _fileSystem.Destroy(); - } - - public bool IsCached(SaveDataSpaceId spaceId, ulong saveDataId) - { - return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId; - } - - public SharedRef Move() - { - return SharedRef.CreateMove(ref _fileSystem); - } - - public void Register(ref SharedRef fileSystem) - { - _spaceId = fileSystem.Get.GetSaveDataSpaceId(); - _saveDataId = fileSystem.Get.GetSaveDataId(); - - _fileSystem.SetByMove(ref fileSystem); - } - - public void Unregister() - { - _fileSystem.Reset(); - } - } - - private SdkRecursiveMutexType _mutex; - private Cache[] _cachedFileSystems; - private int _maxCachedFileSystemCount; - private int _nextCacheIndex; - - public SaveDataFileSystemCacheManager() - { - _mutex = new SdkRecursiveMutexType(); - } - - public void Dispose() - { - Cache[] caches = Shared.Move(ref _cachedFileSystems); - - if (caches is not null) - { - for (int i = 0; i < caches.Length; i++) - { - caches[i].Dispose(); - } - } - } - - public Result Initialize(int maxCacheCount) - { - Assert.SdkRequiresGreaterEqual(maxCacheCount, 0); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - Assert.SdkAssert(_cachedFileSystems is null); - - _maxCachedFileSystemCount = maxCacheCount; - if (maxCacheCount > 0) - { - // Note: The original checks for overflow here - _cachedFileSystems = new Cache[maxCacheCount]; - } - - return Result.Success; - } - - public bool GetCache(ref SharedRef outFileSystem, SaveDataSpaceId spaceId, - ulong saveDataId) - { - Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - for (int i = 0; i < _maxCachedFileSystemCount; i++) - { - if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) - { - using SharedRef cachedFs = _cachedFileSystems[i].Move(); - outFileSystem.SetByMove(ref cachedFs.Ref()); - - return true; - } - } - - return false; - } - - public void Register(ref SharedRef fileSystem) - { - // Don't cache temporary save data - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - fileSystem.Reset(); - } - - public void Register(ref SharedRef fileSystem) - { - if (_maxCachedFileSystemCount <= 0) - return; - - Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0); - Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex); - - if (fileSystem.Get.GetSaveDataSpaceId() == SaveDataSpaceId.SdSystem) - { - // Don't cache system save data - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - fileSystem.Reset(); - } - else - { - Result rc = fileSystem.Get.RollbackOnlyModified(); - if (rc.IsFailure()) return; - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - _cachedFileSystems[_nextCacheIndex].Register(ref fileSystem); - _nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount; - } - } - - public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId) - { - Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0); - - using ScopedLock scopedLock = ScopedLock.Lock(ref _mutex); - - for (int i = 0; i < _maxCachedFileSystemCount; i++) - { - if (_cachedFileSystems[i].IsCached(spaceId, saveDataId)) - { - _cachedFileSystems[i].Unregister(); - } - } - } - - public UniqueLockRef GetScopedLock() - { - return new UniqueLockRef(ref _mutex); - } -} \ No newline at end of file diff --git a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs b/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs deleted file mode 100644 index 459ada71..00000000 --- a/src/LibHac/FsSystem/SaveDataFileSystemCacheRegisterBase.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using LibHac.Common; -using LibHac.Diag; -using LibHac.Fs; -using LibHac.Fs.Fsa; -using LibHac.Tools.FsSystem.Save; - -namespace LibHac.FsSystem; - -/// -/// Wraps a save data . -/// Upon disposal the base file system is returned to the provided . -/// -/// The type of the base file system. Must be one of , -/// or . -/// Based on FS 13.1.0 (nnSdk 13.4.0) -public class SaveDataFileSystemCacheRegisterBase : IFileSystem where T : IFileSystem -{ - private SharedRef _baseFileSystem; - private ISaveDataFileSystemCacheManager _cacheManager; - - public SaveDataFileSystemCacheRegisterBase(ref SharedRef baseFileSystem, - ISaveDataFileSystemCacheManager cacheManager) - { - if (typeof(T) != typeof(SaveDataFileSystemHolder) && typeof(T) != typeof(ApplicationTemporaryFileSystem)) - { - throw new NotSupportedException( - $"The file system type of a {nameof(SaveDataFileSystemCacheRegisterBase)} must be {nameof(SaveDataFileSystemHolder)} or {nameof(ApplicationTemporaryFileSystem)}."); - } - - _baseFileSystem = SharedRef.CreateMove(ref baseFileSystem); - _cacheManager = cacheManager; - } - - public override void Dispose() - { - if (typeof(T) == typeof(SaveDataFileSystemHolder)) - { - _cacheManager.Register(ref Unsafe.As, SharedRef>(ref _baseFileSystem)); - } - else if (typeof(T) == typeof(ApplicationTemporaryFileSystem)) - { - _cacheManager.Register(ref Unsafe.As, SharedRef>(ref _baseFileSystem)); - } - else - { - Assert.SdkAssert(false, "Invalid save data file system type."); - } - } - - protected override Result DoOpenFile(ref UniqueRef outFile, in Path path, OpenMode mode) - { - return _baseFileSystem.Get.OpenFile(ref outFile, path, mode); - } - - protected override Result DoOpenDirectory(ref UniqueRef outDirectory, in Path path, - OpenDirectoryMode mode) - { - return _baseFileSystem.Get.OpenDirectory(ref outDirectory, path, mode); - } - - protected override Result DoGetEntryType(out DirectoryEntryType entryType, in Path path) - { - return _baseFileSystem.Get.GetEntryType(out entryType, path); - } - - protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option) - { - return _baseFileSystem.Get.CreateFile(path, size, option); - } - - protected override Result DoDeleteFile(in Path path) - { - return _baseFileSystem.Get.DeleteFile(path); - } - - protected override Result DoCreateDirectory(in Path path) - { - return _baseFileSystem.Get.CreateDirectory(path); - } - - protected override Result DoDeleteDirectory(in Path path) - { - return _baseFileSystem.Get.DeleteDirectory(path); - } - - protected override Result DoDeleteDirectoryRecursively(in Path path) - { - return _baseFileSystem.Get.DeleteDirectoryRecursively(path); - } - - protected override Result DoCleanDirectoryRecursively(in Path path) - { - return _baseFileSystem.Get.CleanDirectoryRecursively(path); - } - - protected override Result DoRenameFile(in Path currentPath, in Path newPath) - { - return _baseFileSystem.Get.RenameFile(currentPath, newPath); - } - - protected override Result DoRenameDirectory(in Path currentPath, in Path newPath) - { - return _baseFileSystem.Get.RenameDirectory(currentPath, newPath); - } - - protected override Result DoCommit() - { - return _baseFileSystem.Get.Commit(); - } - - protected override Result DoCommitProvisionally(long counter) - { - return _baseFileSystem.Get.CommitProvisionally(counter); - } - - protected override Result DoRollback() - { - return _baseFileSystem.Get.Rollback(); - } - - protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path) - { - return _baseFileSystem.Get.GetFreeSpaceSize(out freeSpace, path); - } - - protected override Result DoGetTotalSpaceSize(out long totalSpace, in Path path) - { - return _baseFileSystem.Get.GetTotalSpaceSize(out totalSpace, path); - } -} \ No newline at end of file diff --git a/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs b/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs deleted file mode 100644 index 5b5d35ff..00000000 --- a/src/LibHac/FsSystem/SaveDataFileSystemHolder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using LibHac.Common; -using LibHac.Diag; -using LibHac.Fs; -using LibHac.Fs.Fsa; - -namespace LibHac.FsSystem; - -/// -/// Holds a file system for adding to the save data file system cache. -/// -/// Nintendo uses concrete types in instead of an interface. -/// This class allows to be cached in a way that changes the original -/// design as little as possible. -/// -public class SaveDataFileSystemHolder : ForwardingFileSystem -{ - public SaveDataFileSystemHolder(ref SharedRef baseFileSystem) : base(ref baseFileSystem) - { - Assert.SdkRequires(BaseFileSystem.Get.GetType() == typeof(SaveDataFileSystemHolder) || - BaseFileSystem.Get.GetType() == typeof(ApplicationTemporaryFileSystem)); - } - - public SaveDataSpaceId GetSaveDataSpaceId() - { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataSpaceId(); - } - - throw new NotImplementedException(); - } - - public ulong GetSaveDataId() - { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).GetSaveDataId(); - } - - throw new NotImplementedException(); - } - - public Result RollbackOnlyModified() - { - IFileSystem baseFs = BaseFileSystem.Get; - - if (baseFs.GetType() == typeof(DirectorySaveDataFileSystem)) - { - return ((DirectorySaveDataFileSystem)baseFs).Rollback(); - } - - throw new NotImplementedException(); - } -} \ No newline at end of file