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);