mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Update DirectorySaveDataFileSystem to implement ISaveDataFileSystem
This commit is contained in:
parent
bc7fea5714
commit
7dfcebfc28
14 changed files with 188 additions and 196 deletions
|
@ -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);
|
||||
|
|
|
@ -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<byte> heapBuffer = GC.AllocateArray<byte>(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.
|
||||
/// </summary>
|
||||
public ExternalKeySet ExternalKeySet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Used for generating random data for save data.
|
||||
/// </summary>
|
||||
public RandomDataGenerator RandomGenerator { get; set; }
|
||||
}
|
|
@ -78,8 +78,8 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
|
|||
using var saveDirFs = new SharedRef<DirectorySaveDataFileSystem>(
|
||||
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);
|
||||
|
|
|
@ -77,7 +77,7 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorO
|
|||
public Result Register(in SharedRef<ISaveDataExtraDataAccessor> accessor, SaveDataSpaceId spaceId,
|
||||
ulong saveDataId)
|
||||
{
|
||||
accessor.Get.RegisterCacheObserver(this, spaceId, saveDataId);
|
||||
accessor.Get.RegisterExtraDataAccessorObserver(this, spaceId, saveDataId);
|
||||
|
||||
var node = new LinkedListNode<Cache>(new Cache(in accessor, spaceId, saveDataId));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -83,7 +83,8 @@ 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();
|
||||
}
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
namespace LibHac.FsSystem;
|
||||
|
||||
public delegate Result RandomDataGenerator(Span<byte> buffer);
|
||||
public delegate void RandomDataGenerator(Span<byte> buffer);
|
|
@ -14,7 +14,7 @@ internal struct DirectorySaveDataFileSystemGlobals
|
|||
|
||||
public void Initialize(FileSystemClient fsClient)
|
||||
{
|
||||
SynchronizeDirectoryMutex.Initialize();
|
||||
SynchronizeDirectoryMutex = new SdkMutexType();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,9 +24,9 @@ internal struct DirectorySaveDataFileSystemGlobals
|
|||
/// <remarks>
|
||||
/// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the
|
||||
/// underlying <see cref="IFileSystem"/> is atomic.
|
||||
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para>
|
||||
/// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para>
|
||||
/// </remarks>
|
||||
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<byte> SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' };
|
||||
private static ReadOnlySpan<byte> LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' };
|
||||
|
||||
private FileSystemClient _fsClient;
|
||||
private IFileSystem _baseFs;
|
||||
private SdkMutexType _mutex;
|
||||
private UniqueRef<IFileSystem> _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<IFile> _lockFile;
|
||||
|
||||
private class DirectorySaveDataFile : IFile
|
||||
|
@ -116,27 +116,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||
public DirectorySaveDataFileSystem(IFileSystem baseFileSystem)
|
||||
{
|
||||
_baseFs = baseFileSystem;
|
||||
_mutex.Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
|
||||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||
public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem)
|
||||
{
|
||||
_baseFs = baseFileSystem.Get;
|
||||
_mutex.Initialize();
|
||||
_uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
|
||||
/// If a <see cref="FileSystemClient"/> is provided a global mutex will be used when synchronizing directories.
|
||||
|
@ -145,10 +124,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param>
|
||||
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
|
|||
/// </summary>
|
||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</param>
|
||||
public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem, FileSystemClient fsClient)
|
||||
public DirectorySaveDataFileSystem(ref UniqueRef<IFileSystem> baseFileSystem, FileSystemClient fsClient = null)
|
||||
{
|
||||
_baseFs = baseFileSystem.Get;
|
||||
_mutex.Initialize();
|
||||
_mutex = new SdkMutexType();
|
||||
_uniqueBaseFs = new UniqueRef<IFileSystem>(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<IFile>();
|
||||
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<SdkMutexType> scopedLock =
|
||||
|
@ -585,63 +569,63 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||
}
|
||||
}
|
||||
|
||||
protected override Result DoCommit()
|
||||
private Result DoCommit(bool updateTimeStamp)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> lk = ScopedLock.Lock(ref _mutex);
|
||||
using ScopedLock<SdkMutexType> 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<SdkMutexType> 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<SdkMutexType> 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<byte> CommittedExtraDataName => // "/ExtraData0"
|
||||
|
@ -860,6 +889,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result GetExtraDataPath(ref Path path)
|
||||
{
|
||||
ReadOnlySpan<byte> extraDataName = _isJournalingSupported && !_isJournalingEnabled
|
||||
? CommittedExtraDataName
|
||||
: ModifiedExtraDataName;
|
||||
|
||||
return PathFunctions.SetUpFixedPath(ref path, extraDataName);
|
||||
}
|
||||
|
||||
private Result EnsureExtraDataSize(in Path path)
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
@ -902,42 +940,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||
return Result.Success;
|
||||
}
|
||||
|
||||
private Result GetExtraDataPath(ref Path path)
|
||||
{
|
||||
ReadOnlySpan<byte> extraDataName = _isJournalingSupported && !_isJournalingEnabled
|
||||
? CommittedExtraDataName
|
||||
: ModifiedExtraDataName;
|
||||
|
||||
return PathFunctions.SetUpFixedPath(ref path, extraDataName);
|
||||
}
|
||||
|
||||
public Result WriteExtraData(in SaveDataExtraData extraData)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||
|
||||
return WriteExtraDataImpl(in extraData);
|
||||
}
|
||||
|
||||
public Result CommitExtraData(bool updateTimeStamp)
|
||||
{
|
||||
using ScopedLock<SdkMutexType> 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<SdkMutexType> 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;
|
||||
}
|
|
@ -6,11 +6,11 @@ namespace LibHac.FsSystem;
|
|||
/// <summary>
|
||||
/// Provides read/write access to a save data file system's extra data.
|
||||
/// </summary>
|
||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
||||
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||
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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<byte> output)
|
||||
public void GenerateRandom(Span<byte> output)
|
||||
{
|
||||
if (output.Length != 8)
|
||||
throw new ArgumentException();
|
||||
|
@ -529,7 +529,6 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
|
|||
Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index];
|
||||
|
||||
_index = (_index + 1) % Values.Length;
|
||||
return Result.Success;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue