mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Implement the updated save data file system cache from 14.0.0 (PR #245)
14.0.0 brought an updated save data file system cache that will allow caching more file system types. Instead of only working with concrete types, `SaveDataFileSystemCacheManager` can cache any type implementing `ISaveDataFileSystem`. Currently `SaveDataFileSystem`, `ApplicationTemporaryFileSystem`, `DirectorySaveDataFileSystem` and `SaveDataInternalStorageFileSystem` implement this interface.
This commit is contained in:
commit
e13661a4dd
@ -672,6 +672,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
|
|||||||
2,4301,4499,,,SaveDataCorrupted,
|
2,4301,4499,,,SaveDataCorrupted,
|
||||||
2,4302,,,,UnsupportedSaveDataVersion,
|
2,4302,,,,UnsupportedSaveDataVersion,
|
||||||
2,4303,,,,InvalidSaveDataEntryType,
|
2,4303,,,,InvalidSaveDataEntryType,
|
||||||
|
2,4304,,,,ReconstructibleSaveDataCorrupted,
|
||||||
|
|
||||||
2,4311,4319,,,SaveDataFileSystemCorrupted,
|
2,4311,4319,,,SaveDataFileSystemCorrupted,
|
||||||
2,4312,,,,InvalidJournalIntegritySaveDataHashSize,
|
2,4312,,,,InvalidJournalIntegritySaveDataHashSize,
|
||||||
|
|
@ -170,6 +170,12 @@ public enum SaveDataRank : byte
|
|||||||
Secondary = 1
|
Secondary = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SaveDataFormatType : byte
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
NoJournal = 1
|
||||||
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum SaveDataFlags
|
public enum SaveDataFlags
|
||||||
{
|
{
|
||||||
|
@ -1210,6 +1210,8 @@ public static class ResultFs
|
|||||||
public static Result.Base UnsupportedSaveDataVersion => new Result.Base(ModuleFs, 4302);
|
public static Result.Base UnsupportedSaveDataVersion => new Result.Base(ModuleFs, 4302);
|
||||||
/// <summary>Error code: 2002-4303; Inner value: 0x219e02</summary>
|
/// <summary>Error code: 2002-4303; Inner value: 0x219e02</summary>
|
||||||
public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303);
|
public static Result.Base InvalidSaveDataEntryType => new Result.Base(ModuleFs, 4303);
|
||||||
|
/// <summary>Error code: 2002-4304; Inner value: 0x21a002</summary>
|
||||||
|
public static Result.Base ReconstructibleSaveDataCorrupted => new Result.Base(ModuleFs, 4304);
|
||||||
|
|
||||||
/// <summary>Error code: 2002-4311; Range: 4311-4319; Inner value: 0x21ae02</summary>
|
/// <summary>Error code: 2002-4311; Range: 4311-4319; Inner value: 0x21ae02</summary>
|
||||||
public static Result.Base SaveDataFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4311, 4319); }
|
public static Result.Base SaveDataFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4311, 4319); }
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using LibHac.Common.Keys;
|
using LibHac.Common.Keys;
|
||||||
using LibHac.FsSrv.FsCreator;
|
using LibHac.FsSrv.FsCreator;
|
||||||
using LibHac.FsSrv.Sf;
|
using LibHac.FsSrv.Sf;
|
||||||
|
using LibHac.FsSystem;
|
||||||
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
using IFileSystem = LibHac.Fs.Fsa.IFileSystem;
|
||||||
|
|
||||||
namespace LibHac.FsSrv;
|
namespace LibHac.FsSrv;
|
||||||
@ -14,7 +15,7 @@ public class DefaultFsServerObjects
|
|||||||
public EmulatedSdCard SdCard { get; set; }
|
public EmulatedSdCard SdCard { get; set; }
|
||||||
|
|
||||||
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet,
|
public static DefaultFsServerObjects GetDefaultEmulatedCreators(IFileSystem rootFileSystem, KeySet keySet,
|
||||||
FileSystemServer fsServer)
|
FileSystemServer fsServer, RandomDataGenerator randomGenerator)
|
||||||
{
|
{
|
||||||
var creators = new FileSystemCreatorInterfaces();
|
var creators = new FileSystemCreatorInterfaces();
|
||||||
var gameCard = new EmulatedGameCard(keySet);
|
var gameCard = new EmulatedGameCard(keySet);
|
||||||
@ -31,7 +32,7 @@ public class DefaultFsServerObjects
|
|||||||
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
|
creators.StorageOnNcaCreator = new StorageOnNcaCreator(keySet);
|
||||||
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
|
creators.TargetManagerFileSystemCreator = new TargetManagerFileSystemCreator();
|
||||||
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
|
creators.SubDirectoryFileSystemCreator = new SubDirectoryFileSystemCreator();
|
||||||
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, null);
|
creators.SaveDataFileSystemCreator = new SaveDataFileSystemCreator(fsServer, keySet, null, randomGenerator);
|
||||||
creators.GameCardStorageCreator = gcStorageCreator;
|
creators.GameCardStorageCreator = gcStorageCreator;
|
||||||
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
|
creators.GameCardFileSystemCreator = new EmulatedGameCardFsCreator(gcStorageCreator, gameCard);
|
||||||
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
|
creators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator(keySet);
|
||||||
|
@ -63,13 +63,6 @@ public static class FileSystemServerInitializer
|
|||||||
private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server,
|
private static FileSystemProxyConfiguration InitializeFileSystemProxy(FileSystemServer server,
|
||||||
FileSystemServerConfig config)
|
FileSystemServerConfig config)
|
||||||
{
|
{
|
||||||
var random = new Random();
|
|
||||||
RandomDataGenerator randomGenerator = buffer =>
|
|
||||||
{
|
|
||||||
random.NextBytes(buffer);
|
|
||||||
return Result.Success;
|
|
||||||
};
|
|
||||||
|
|
||||||
var bufferManager = new FileSystemBufferManager();
|
var bufferManager = new FileSystemBufferManager();
|
||||||
Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true);
|
Memory<byte> heapBuffer = GC.AllocateArray<byte>(BufferManagerHeapSize, true);
|
||||||
bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize);
|
bufferManager.Initialize(BufferManagerCacheSize, heapBuffer, BufferManagerBlockSize);
|
||||||
@ -141,7 +134,7 @@ public static class FileSystemServerInitializer
|
|||||||
saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator;
|
saveFsServiceConfig.EncryptedFsCreator = config.FsCreators.EncryptedFileSystemCreator;
|
||||||
saveFsServiceConfig.ProgramRegistryService = programRegistryService;
|
saveFsServiceConfig.ProgramRegistryService = programRegistryService;
|
||||||
saveFsServiceConfig.BufferManager = bufferManager;
|
saveFsServiceConfig.BufferManager = bufferManager;
|
||||||
saveFsServiceConfig.GenerateRandomData = randomGenerator;
|
saveFsServiceConfig.GenerateRandomData = config.RandomGenerator;
|
||||||
saveFsServiceConfig.IsPseudoSaveData = () => true;
|
saveFsServiceConfig.IsPseudoSaveData = () => true;
|
||||||
saveFsServiceConfig.MaxSaveFsCacheCount = 1;
|
saveFsServiceConfig.MaxSaveFsCacheCount = 1;
|
||||||
saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager;
|
saveFsServiceConfig.SaveIndexerManager = saveDataIndexerManager;
|
||||||
@ -278,4 +271,9 @@ public class FileSystemServerConfig
|
|||||||
/// If null, an empty set will be created.
|
/// If null, an empty set will be created.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ExternalKeySet ExternalKeySet { get; set; }
|
public ExternalKeySet ExternalKeySet { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used for generating random data for save data.
|
||||||
|
/// </summary>
|
||||||
|
public RandomDataGenerator RandomGenerator { get; set; }
|
||||||
}
|
}
|
@ -10,12 +10,10 @@ public interface ISaveDataFileSystemCreator
|
|||||||
{
|
{
|
||||||
Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode);
|
Result CreateFile(out IFile file, IFileSystem sourceFileSystem, ulong saveDataId, OpenMode openMode);
|
||||||
|
|
||||||
Result Create(ref SharedRef<IFileSystem> outFileSystem,
|
Result Create(ref SharedRef<ISaveDataFileSystem> outFileSystem, ref SharedRef<IFileSystem> baseFileSystem,
|
||||||
ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool isDeviceUniqueMac,
|
||||||
ISaveDataFileSystemCacheManager cacheManager, ref SharedRef<IFileSystem> baseFileSystem,
|
|
||||||
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
|
|
||||||
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
|
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
|
||||||
ISaveDataCommitTimeStampGetter timeStampGetter);
|
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible);
|
||||||
|
|
||||||
Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
||||||
ref SharedRef<IFileSystem> baseFileSystem);
|
ref SharedRef<IFileSystem> baseFileSystem);
|
||||||
|
@ -3,12 +3,9 @@ using System.Runtime.CompilerServices;
|
|||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.Common.FixedArrays;
|
using LibHac.Common.FixedArrays;
|
||||||
using LibHac.Common.Keys;
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Diag;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.Save;
|
|
||||||
using LibHac.Util;
|
using LibHac.Util;
|
||||||
|
|
||||||
using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType;
|
using OpenType = LibHac.FsSrv.SaveDataOpenTypeSetFileStorage.OpenType;
|
||||||
@ -17,10 +14,15 @@ namespace LibHac.FsSrv.FsCreator;
|
|||||||
|
|
||||||
public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
|
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 IBufferManager _bufferManager;
|
||||||
private RandomDataGenerator _randomGenerator;
|
private RandomDataGenerator _randomGenerator;
|
||||||
|
|
||||||
// LibHac Additions
|
// LibHac Additions
|
||||||
|
// ReSharper disable once NotAccessedField.Local
|
||||||
private KeySet _keySet;
|
private KeySet _keySet;
|
||||||
private FileSystemServer _fsServer;
|
private FileSystemServer _fsServer;
|
||||||
|
|
||||||
@ -38,54 +40,56 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Create(ref SharedRef<IFileSystem> outFileSystem,
|
public Result Create(ref SharedRef<ISaveDataFileSystem> outFileSystem, ref SharedRef<IFileSystem> baseFileSystem,
|
||||||
ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool isDeviceUniqueMac,
|
||||||
ISaveDataFileSystemCacheManager cacheManager, ref SharedRef<IFileSystem> baseFileSystem,
|
|
||||||
SaveDataSpaceId spaceId, ulong saveDataId, bool allowDirectorySaveData, bool useDeviceUniqueMac,
|
|
||||||
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
|
bool isJournalingSupported, bool isMultiCommitSupported, bool openReadOnly, bool openShared,
|
||||||
ISaveDataCommitTimeStampGetter timeStampGetter)
|
ISaveDataCommitTimeStampGetter timeStampGetter, bool isReconstructible)
|
||||||
{
|
{
|
||||||
Unsafe.SkipInit(out Array18<byte> saveImageNameBuffer);
|
Unsafe.SkipInit(out Array18<byte> saveImageNameBuffer);
|
||||||
|
|
||||||
Assert.SdkRequiresNotNull(cacheManager);
|
|
||||||
|
|
||||||
using var saveImageName = new Path();
|
using var saveImageName = new Path();
|
||||||
Result rc = PathFunctions.SetUpFixedPathSaveId(ref saveImageName.Ref(), saveImageNameBuffer.Items, saveDataId);
|
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);
|
rc = baseFileSystem.Get.GetEntryType(out DirectoryEntryType type, in saveImageName);
|
||||||
|
|
||||||
if (rc.IsFailure())
|
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<ISaveDataFileSystem>();
|
||||||
|
|
||||||
if (type == DirectoryEntryType.Directory)
|
if (type == DirectoryEntryType.Directory)
|
||||||
|
{
|
||||||
|
if (EnforceSaveTypeRestrictions)
|
||||||
{
|
{
|
||||||
if (!allowDirectorySaveData)
|
if (!allowDirectorySaveData)
|
||||||
return ResultFs.InvalidSaveDataEntryType.Log();
|
return ResultFs.InvalidSaveDataEntryType.Log();
|
||||||
|
}
|
||||||
|
|
||||||
using var baseFs =
|
// Get a file system over the save directory
|
||||||
new UniqueRef<SubdirectoryFileSystem>(new SubdirectoryFileSystem(ref baseFileSystem));
|
using var baseFs = new UniqueRef<SubdirectoryFileSystem>(new SubdirectoryFileSystem(ref baseFileSystem));
|
||||||
|
|
||||||
if (!baseFs.HasValue)
|
if (!baseFs.HasValue)
|
||||||
return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorA.Log();
|
return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorA.Log();
|
||||||
|
|
||||||
rc = baseFs.Get.Initialize(in saveImageName);
|
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<IFileSystem> tempFs = UniqueRef<IFileSystem>.Create(ref baseFs.Ref());
|
using UniqueRef<IFileSystem> tempFs = UniqueRef<IFileSystem>.Create(ref baseFs.Ref());
|
||||||
using var saveDirFs = new SharedRef<DirectorySaveDataFileSystem>(
|
using var saveDirFs = new SharedRef<DirectorySaveDataFileSystem>(
|
||||||
new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs));
|
new DirectorySaveDataFileSystem(ref tempFs.Ref(), _fsServer.Hos.Fs));
|
||||||
|
|
||||||
rc = saveDirFs.Get.Initialize(timeStampGetter, _randomGenerator, isJournalingSupported,
|
if (!saveDirFs.HasValue)
|
||||||
isMultiCommitSupported, !openReadOnly);
|
return ResultFs.AllocationMemoryFailedInSaveDataFileSystemCreatorB.Log();
|
||||||
if (rc.IsFailure()) return rc;
|
|
||||||
|
|
||||||
outFileSystem.SetByCopy(in saveDirFs);
|
rc = saveDirFs.Get.Initialize(isJournalingSupported, isMultiCommitSupported, !openReadOnly,
|
||||||
outExtraDataAccessor.SetByCopy(in saveDirFs);
|
timeStampGetter, _randomGenerator);
|
||||||
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
return Result.Success;
|
saveDataFs.SetByMove(ref saveDirFs.Ref());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -98,19 +102,17 @@ public class SaveDataFileSystemCreator : ISaveDataFileSystemCreator
|
|||||||
OpenMode.ReadWrite, openType);
|
OpenMode.ReadWrite, openType);
|
||||||
if (rc.IsFailure()) return rc;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
if (!isJournalingSupported)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
using var saveFs = new SharedRef<SaveDataFileSystem>(new SaveDataFileSystem(_keySet, fileStorage.Get,
|
// Wrap the save FS in a result convert FS and set it as the output FS
|
||||||
IntegrityCheckLevel.ErrorOnInvalid, false));
|
using var resultConvertFs = new SharedRef<SaveDataResultConvertFileSystem>(
|
||||||
|
new SaveDataResultConvertFileSystem(ref saveDataFs.Ref(), isReconstructible));
|
||||||
|
|
||||||
// Todo: ISaveDataExtraDataAccessor
|
outFileSystem.SetByMove(ref resultConvertFs.Ref());
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
public Result CreateExtraDataAccessor(ref SharedRef<ISaveDataExtraDataAccessor> outExtraDataAccessor,
|
||||||
ref SharedRef<IFileSystem> baseFileSystem)
|
ref SharedRef<IFileSystem> baseFileSystem)
|
||||||
|
127
src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs
Normal file
127
src/LibHac/FsSrv/FsCreator/SaveDataResultConvertFileSystem.cs
Normal file
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an <see cref="IFile"/>, converting its returned <see cref="Result"/>s
|
||||||
|
/// to save-data-specific <see cref="Result"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||||
|
public class SaveDataResultConvertFile : IResultConvertFile
|
||||||
|
{
|
||||||
|
private bool _isReconstructible;
|
||||||
|
|
||||||
|
public SaveDataResultConvertFile(ref UniqueRef<IFile> baseFile, bool isReconstructible) : base(ref baseFile)
|
||||||
|
{
|
||||||
|
_isReconstructible = isReconstructible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result ConvertResult(Result result)
|
||||||
|
{
|
||||||
|
return ConvertSaveDataFsResult(result, _isReconstructible).Ret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an <see cref="IDirectory"/>, converting its returned <see cref="Result"/>s
|
||||||
|
/// to save-data-specific <see cref="Result"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||||
|
public class SaveDataResultConvertDirectory : IResultConvertDirectory
|
||||||
|
{
|
||||||
|
private bool _isReconstructible;
|
||||||
|
|
||||||
|
public SaveDataResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory, bool isReconstructible) : base(
|
||||||
|
ref baseDirectory)
|
||||||
|
{
|
||||||
|
_isReconstructible = isReconstructible;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result ConvertResult(Result result)
|
||||||
|
{
|
||||||
|
return ConvertSaveDataFsResult(result, _isReconstructible).Ret();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an <see cref="ISaveDataFileSystem"/>, converting its returned <see cref="Result"/>s
|
||||||
|
/// to save-data-specific <see cref="Result"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||||
|
public class SaveDataResultConvertFileSystem : IResultConvertFileSystem<ISaveDataFileSystem>
|
||||||
|
{
|
||||||
|
private bool _isReconstructible;
|
||||||
|
|
||||||
|
public SaveDataResultConvertFileSystem(ref SharedRef<ISaveDataFileSystem> 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<IFile> outFile, in Path path, OpenMode mode)
|
||||||
|
{
|
||||||
|
using var file = new UniqueRef<IFile>();
|
||||||
|
Result rc = ConvertResult(GetFileSystem().OpenFile(ref file.Ref(), in path, mode));
|
||||||
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
|
using UniqueRef<SaveDataResultConvertFile> resultConvertFile =
|
||||||
|
new(new SaveDataResultConvertFile(ref file.Ref(), _isReconstructible));
|
||||||
|
|
||||||
|
outFile.Set(ref resultConvertFile.Ref());
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, in Path path,
|
||||||
|
OpenDirectoryMode mode)
|
||||||
|
{
|
||||||
|
using var directory = new UniqueRef<IDirectory>();
|
||||||
|
Result rc = ConvertResult(GetFileSystem().OpenDirectory(ref directory.Ref(), in path, mode));
|
||||||
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
|
using UniqueRef<SaveDataResultConvertDirectory> 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);
|
||||||
|
}
|
||||||
|
}
|
134
src/LibHac/FsSrv/FsCreator/SaveDataResultConverter.cs
Normal file
134
src/LibHac/FsSrv/FsCreator/SaveDataResultConverter.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using LibHac.Diag;
|
||||||
|
using LibHac.Fs;
|
||||||
|
|
||||||
|
namespace LibHac.FsSrv.FsCreator;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains functions for converting internal save data <see cref="Result"/>s to external <see cref="Result"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||||
|
public static class SaveDataResultConverter
|
||||||
|
{
|
||||||
|
private static Result ConvertCorruptedResult(Result result)
|
||||||
|
{
|
||||||
|
if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result))
|
||||||
|
{
|
||||||
|
if (ResultFs.IncorrectIntegrityVerificationMagicCode.Includes(result))
|
||||||
|
return ResultFs.IncorrectSaveDataIntegrityVerificationMagicCode.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidZeroHash.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataZeroHash.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.NonRealDataVerificationFailed.Includes(result))
|
||||||
|
return ResultFs.SaveDataNonRealDataVerificationFailed.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.ClearedRealDataVerificationFailed.Includes(result))
|
||||||
|
return ResultFs.ClearedSaveDataRealDataVerificationFailed.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result))
|
||||||
|
return ResultFs.UnclearedSaveDataRealDataVerificationFailed.LogConverted(result);
|
||||||
|
|
||||||
|
Assert.SdkAssert(false);
|
||||||
|
}
|
||||||
|
else if (ResultFs.HostFileSystemCorrupted.Includes(result))
|
||||||
|
{
|
||||||
|
if (ResultFs.HostEntryCorrupted.Includes(result))
|
||||||
|
return ResultFs.SaveDataHostEntryCorrupted.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.HostFileDataCorrupted.Includes(result))
|
||||||
|
return ResultFs.SaveDataHostFileDataCorrupted.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.HostFileCorrupted.Includes(result))
|
||||||
|
return ResultFs.SaveDataHostFileCorrupted.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidHostHandle.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataHostHandle.LogConverted(result);
|
||||||
|
|
||||||
|
Assert.SdkAssert(false);
|
||||||
|
}
|
||||||
|
else if (ResultFs.DatabaseCorrupted.Includes(result))
|
||||||
|
{
|
||||||
|
if (ResultFs.InvalidAllocationTableBlock.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataAllocationTableBlock.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidKeyValueListElementIndex.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataKeyValueListElementIndex.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidAllocationTableChainEntry.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataAllocationTableChainEntry.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidAllocationTableOffset.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataAllocationTableOffset.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidAllocationTableBlockCount.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataAllocationTableBlockCount.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidKeyValueListEntryIndex.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataKeyValueListEntryIndex.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.InvalidBitmapIndex.Includes(result))
|
||||||
|
return ResultFs.InvalidSaveDataBitmapIndex.LogConverted(result);
|
||||||
|
|
||||||
|
Assert.SdkAssert(false);
|
||||||
|
}
|
||||||
|
else if (ResultFs.ZeroBitmapFileCorrupted.Includes(result))
|
||||||
|
{
|
||||||
|
if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result))
|
||||||
|
return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.LogConverted(result);
|
||||||
|
|
||||||
|
Assert.SdkAssert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Result ConvertResult(Result result)
|
||||||
|
{
|
||||||
|
if (ResultFs.UnsupportedVersion.Includes(result))
|
||||||
|
return ResultFs.UnsupportedSaveDataVersion.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) ||
|
||||||
|
ResultFs.BuiltInStorageCorrupted.Includes(result) ||
|
||||||
|
ResultFs.HostFileSystemCorrupted.Includes(result) ||
|
||||||
|
ResultFs.DatabaseCorrupted.Includes(result) ||
|
||||||
|
ResultFs.ZeroBitmapFileCorrupted.Includes(result))
|
||||||
|
{
|
||||||
|
return ConvertCorruptedResult(result).Miss();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ResultFs.FatFileSystemCorrupted.Includes(result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
if (ResultFs.NotFound.Includes(result))
|
||||||
|
return ResultFs.PathNotFound.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.AllocationTableFull.Includes(result))
|
||||||
|
return ResultFs.UsableSpaceNotEnough.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.AlreadyExists.Includes(result))
|
||||||
|
return ResultFs.PathAlreadyExists.LogConverted(result);
|
||||||
|
|
||||||
|
if (ResultFs.IncompatiblePath.Includes(result) ||
|
||||||
|
ResultFs.FileNotFound.Includes(result))
|
||||||
|
{
|
||||||
|
return ResultFs.PathNotFound.LogConverted(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Result ConvertSaveDataFsResult(Result result, bool isReconstructible)
|
||||||
|
{
|
||||||
|
if (result.IsSuccess())
|
||||||
|
return Result.Success;
|
||||||
|
|
||||||
|
Result convertedResult = ConvertResult(result);
|
||||||
|
|
||||||
|
if (isReconstructible && ResultFs.SaveDataCorrupted.Includes(convertedResult))
|
||||||
|
{
|
||||||
|
return ResultFs.ReconstructibleSaveDataCorrupted.LogConverted(convertedResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedResult;
|
||||||
|
}
|
||||||
|
}
|
@ -10,13 +10,13 @@ namespace LibHac.FsSrv.Impl;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds the <see cref="ISaveDataExtraDataAccessor"/>s for opened save data file systems.
|
/// Holds the <see cref="ISaveDataExtraDataAccessor"/>s for opened save data file systems.
|
||||||
/// </summary>
|
/// </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 class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorCacheObserver
|
public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorObserver
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Holds a single cached extra data accessor identified by its save data ID and save data space ID.
|
/// Holds a single cached extra data accessor identified by its save data ID and save data space ID.
|
||||||
/// </summary>
|
/// </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>
|
||||||
[NonCopyable]
|
[NonCopyable]
|
||||||
private struct Cache : IDisposable
|
private struct Cache : IDisposable
|
||||||
{
|
{
|
||||||
@ -77,13 +77,14 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorC
|
|||||||
public Result Register(in SharedRef<ISaveDataExtraDataAccessor> accessor, SaveDataSpaceId spaceId,
|
public Result Register(in SharedRef<ISaveDataExtraDataAccessor> accessor, SaveDataSpaceId spaceId,
|
||||||
ulong saveDataId)
|
ulong saveDataId)
|
||||||
{
|
{
|
||||||
|
accessor.Get.RegisterExtraDataAccessorObserver(this, spaceId, saveDataId);
|
||||||
|
|
||||||
var node = new LinkedListNode<Cache>(new Cache(in accessor, spaceId, saveDataId));
|
var node = new LinkedListNode<Cache>(new Cache(in accessor, spaceId, saveDataId));
|
||||||
|
|
||||||
using (ScopedLock.Lock(ref _mutex))
|
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||||
{
|
|
||||||
UnregisterImpl(spaceId, saveDataId);
|
UnregisterImpl(spaceId, saveDataId);
|
||||||
_accessorList.AddLast(node);
|
_accessorList.AddLast(node);
|
||||||
}
|
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
@ -134,7 +135,7 @@ public class SaveDataExtraDataAccessorCacheManager : ISaveDataExtraDataAccessorC
|
|||||||
if (!accessor.HasValue)
|
if (!accessor.HasValue)
|
||||||
return ResultFs.TargetNotFound.Log();
|
return ResultFs.TargetNotFound.Log();
|
||||||
|
|
||||||
outAccessor.Reset(new SaveDataExtraDataResultConvertAccessor(ref accessor.Ref()));
|
outAccessor.SetByMove(ref accessor.Ref());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,27 +1,23 @@
|
|||||||
using LibHac.Common;
|
using System;
|
||||||
|
using LibHac.Common;
|
||||||
using LibHac.Diag;
|
using LibHac.Diag;
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
|
using LibHac.FsSystem;
|
||||||
using LibHac.Os;
|
using LibHac.Os;
|
||||||
|
|
||||||
namespace LibHac.FsSystem;
|
namespace LibHac.FsSrv.Impl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manages a list of cached save data file systems. Each file system is registered and retrieved
|
/// 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 its save data ID and save data space ID.
|
||||||
/// </summary>
|
/// </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 class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
public class SaveDataFileSystemCacheManager : IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Holds a single cached file system identified by its save data ID and save data space ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
[NonCopyable]
|
[NonCopyable]
|
||||||
private struct Cache
|
private struct Cache
|
||||||
{
|
{
|
||||||
// Note: Nintendo only supports caching SaveDataFileSystem. We support DirectorySaveDataFileSystem too,
|
private SharedRef<ISaveDataFileSystem> _fileSystem;
|
||||||
// so we use a wrapper class to simplify the logic here.
|
|
||||||
private SharedRef<SaveDataFileSystemHolder> _fileSystem;
|
|
||||||
private ulong _saveDataId;
|
private ulong _saveDataId;
|
||||||
private SaveDataSpaceId _spaceId;
|
private SaveDataSpaceId _spaceId;
|
||||||
|
|
||||||
@ -35,17 +31,16 @@ public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
|||||||
return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId;
|
return _fileSystem.HasValue && _spaceId == spaceId && _saveDataId == saveDataId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedRef<SaveDataFileSystemHolder> Move()
|
public SharedRef<ISaveDataFileSystem> Move()
|
||||||
{
|
{
|
||||||
return SharedRef<SaveDataFileSystemHolder>.CreateMove(ref _fileSystem);
|
return SharedRef<ISaveDataFileSystem>.CreateMove(ref _fileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Register(ref SharedRef<SaveDataFileSystemHolder> fileSystem)
|
public void Register(ref SharedRef<ISaveDataFileSystem> fileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
_spaceId = fileSystem.Get.GetSaveDataSpaceId();
|
|
||||||
_saveDataId = fileSystem.Get.GetSaveDataId();
|
|
||||||
|
|
||||||
_fileSystem.SetByMove(ref fileSystem);
|
_fileSystem.SetByMove(ref fileSystem);
|
||||||
|
_spaceId = spaceId;
|
||||||
|
_saveDataId = saveDataId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Unregister()
|
public void Unregister()
|
||||||
@ -95,8 +90,12 @@ public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool GetCache(ref SharedRef<SaveDataFileSystemHolder> outFileSystem, SaveDataSpaceId spaceId,
|
public UniqueLockRef<SdkRecursiveMutexType> GetScopedLock()
|
||||||
ulong saveDataId)
|
{
|
||||||
|
return new UniqueLockRef<SdkRecursiveMutexType>(ref _mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetCache(ref SharedRef<ISaveDataFileSystem> outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0);
|
Assert.SdkRequiresGreaterEqual(_maxCachedFileSystemCount, 0);
|
||||||
|
|
||||||
@ -106,7 +105,7 @@ public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
|||||||
{
|
{
|
||||||
if (_cachedFileSystems[i].IsCached(spaceId, saveDataId))
|
if (_cachedFileSystems[i].IsCached(spaceId, saveDataId))
|
||||||
{
|
{
|
||||||
using SharedRef<SaveDataFileSystemHolder> cachedFs = _cachedFileSystems[i].Move();
|
using SharedRef<ISaveDataFileSystem> cachedFs = _cachedFileSystems[i].Move();
|
||||||
outFileSystem.SetByMove(ref cachedFs.Ref());
|
outFileSystem.SetByMove(ref cachedFs.Ref());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -116,38 +115,38 @@ public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Register(ref SharedRef<ApplicationTemporaryFileSystem> fileSystem)
|
public void Register(ref SharedRef<ISaveDataFileSystem> fileSystem, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
// Don't cache temporary save data
|
Assert.SdkRequiresNotNull(in fileSystem);
|
||||||
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
|
||||||
fileSystem.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Register(ref SharedRef<SaveDataFileSystemHolder> fileSystem)
|
|
||||||
{
|
|
||||||
if (_maxCachedFileSystemCount <= 0)
|
if (_maxCachedFileSystemCount <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0);
|
Assert.SdkRequiresGreaterEqual(_nextCacheIndex, 0);
|
||||||
Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex);
|
Assert.SdkRequiresGreater(_maxCachedFileSystemCount, _nextCacheIndex);
|
||||||
|
|
||||||
if (fileSystem.Get.GetSaveDataSpaceId() == SaveDataSpaceId.SdSystem)
|
if (!fileSystem.Get.IsSaveDataFileSystemCacheEnabled())
|
||||||
|
{
|
||||||
|
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||||
|
fileSystem.Reset();
|
||||||
|
}
|
||||||
|
else if (spaceId == SaveDataSpaceId.SdSystem)
|
||||||
{
|
{
|
||||||
// Don't cache system save data
|
|
||||||
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||||
fileSystem.Reset();
|
fileSystem.Reset();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Result rc = fileSystem.Get.RollbackOnlyModified();
|
Result rc = fileSystem.Get.RollbackOnlyModified();
|
||||||
if (rc.IsFailure()) return;
|
if (rc.IsSuccess())
|
||||||
|
{
|
||||||
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
using ScopedLock<SdkRecursiveMutexType> scopedLock = ScopedLock.Lock(ref _mutex);
|
||||||
|
|
||||||
_cachedFileSystems[_nextCacheIndex].Register(ref fileSystem);
|
_cachedFileSystems[_nextCacheIndex].Register(ref fileSystem, spaceId, saveDataId);
|
||||||
_nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount;
|
_nextCacheIndex = (_nextCacheIndex + 1) % _maxCachedFileSystemCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId)
|
public void Unregister(SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
{
|
{
|
||||||
@ -163,9 +162,4 @@ public class SaveDataFileSystemCacheManager : ISaveDataFileSystemCacheManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public UniqueLockRef<SdkRecursiveMutexType> GetScopedLock()
|
|
||||||
{
|
|
||||||
return new UniqueLockRef<SdkRecursiveMutexType>(ref _mutex);
|
|
||||||
}
|
|
||||||
}
|
}
|
117
src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs
Normal file
117
src/LibHac/FsSrv/Impl/SaveDataFileSystemCacheRegister.cs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
|
||||||
|
namespace LibHac.FsSrv.Impl;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wraps an <see cref="ISaveDataFileSystem"/>.
|
||||||
|
/// Upon disposal the base file system is returned to the provided <see cref="SaveDataFileSystemCacheManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
|
||||||
|
public class SaveDataFileSystemCacheRegister : IFileSystem
|
||||||
|
{
|
||||||
|
private SharedRef<ISaveDataFileSystem> _baseFileSystem;
|
||||||
|
private SaveDataFileSystemCacheManager _cacheManager;
|
||||||
|
private SaveDataSpaceId _spaceId;
|
||||||
|
private ulong _saveDataId;
|
||||||
|
|
||||||
|
public SaveDataFileSystemCacheRegister(ref SharedRef<ISaveDataFileSystem> baseFileSystem,
|
||||||
|
SaveDataFileSystemCacheManager cacheManager, SaveDataSpaceId spaceId, ulong saveDataId)
|
||||||
|
{
|
||||||
|
_baseFileSystem = SharedRef<ISaveDataFileSystem>.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<IFile> outFile, in Path path, OpenMode mode)
|
||||||
|
{
|
||||||
|
return _baseFileSystem.Get.OpenFile(ref outFile, in path, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> 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);
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,20 @@ public static class SaveDataProperties
|
|||||||
public const long DefaultSaveDataBlockSize = 0x4000;
|
public const long DefaultSaveDataBlockSize = 0x4000;
|
||||||
public const long BcatSaveDataJournalSize = 0x200000;
|
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)
|
public static bool IsJournalingSupported(SaveDataType type)
|
||||||
{
|
{
|
||||||
switch (type)
|
switch (type)
|
||||||
@ -67,7 +81,6 @@ public static class SaveDataProperties
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case SaveDataType.System:
|
case SaveDataType.System:
|
||||||
case SaveDataType.SystemBcat:
|
|
||||||
return true;
|
return true;
|
||||||
case SaveDataType.Account:
|
case SaveDataType.Account:
|
||||||
case SaveDataType.Bcat:
|
case SaveDataType.Bcat:
|
||||||
@ -86,7 +99,6 @@ public static class SaveDataProperties
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case SaveDataType.System:
|
case SaveDataType.System:
|
||||||
case SaveDataType.SystemBcat:
|
|
||||||
return true;
|
return true;
|
||||||
case SaveDataType.Account:
|
case SaveDataType.Account:
|
||||||
case SaveDataType.Bcat:
|
case SaveDataType.Bcat:
|
||||||
@ -99,4 +111,36 @@ public static class SaveDataProperties
|
|||||||
return default;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,248 +0,0 @@
|
|||||||
using LibHac.Common;
|
|
||||||
using LibHac.Diag;
|
|
||||||
using LibHac.Fs;
|
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
|
|
||||||
namespace LibHac.FsSrv.Impl;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Contains functions for converting internal save data <see cref="Result"/>s to external <see cref="Result"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public static class SaveDataResultConvert
|
|
||||||
{
|
|
||||||
private static Result ConvertCorruptedResult(Result result)
|
|
||||||
{
|
|
||||||
if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result))
|
|
||||||
{
|
|
||||||
if (ResultFs.IncorrectIntegrityVerificationMagicCode.Includes(result))
|
|
||||||
return ResultFs.IncorrectSaveDataIntegrityVerificationMagicCode.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidZeroHash.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataZeroHash.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.NonRealDataVerificationFailed.Includes(result))
|
|
||||||
return ResultFs.SaveDataNonRealDataVerificationFailed.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.ClearedRealDataVerificationFailed.Includes(result))
|
|
||||||
return ResultFs.ClearedSaveDataRealDataVerificationFailed.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.UnclearedRealDataVerificationFailed.Includes(result))
|
|
||||||
return ResultFs.UnclearedSaveDataRealDataVerificationFailed.LogConverted(result);
|
|
||||||
|
|
||||||
Assert.SdkAssert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ResultFs.HostFileSystemCorrupted.Includes(result))
|
|
||||||
{
|
|
||||||
if (ResultFs.HostEntryCorrupted.Includes(result))
|
|
||||||
return ResultFs.SaveDataHostEntryCorrupted.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.HostFileDataCorrupted.Includes(result))
|
|
||||||
return ResultFs.SaveDataHostFileDataCorrupted.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.HostFileCorrupted.Includes(result))
|
|
||||||
return ResultFs.SaveDataHostFileCorrupted.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidHostHandle.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataHostHandle.LogConverted(result);
|
|
||||||
|
|
||||||
Assert.SdkAssert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ResultFs.DatabaseCorrupted.Includes(result))
|
|
||||||
{
|
|
||||||
if (ResultFs.InvalidAllocationTableBlock.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataAllocationTableBlock.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidKeyValueListElementIndex.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataKeyValueListElementIndex.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidAllocationTableChainEntry.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataAllocationTableChainEntry.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidAllocationTableOffset.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataAllocationTableOffset.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidAllocationTableBlockCount.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataAllocationTableBlockCount.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidKeyValueListEntryIndex.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataKeyValueListEntryIndex.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.InvalidBitmapIndex.Includes(result))
|
|
||||||
return ResultFs.InvalidSaveDataBitmapIndex.LogConverted(result);
|
|
||||||
|
|
||||||
Assert.SdkAssert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ResultFs.ZeroBitmapFileCorrupted.Includes(result))
|
|
||||||
{
|
|
||||||
if (ResultFs.IncompleteBlockInZeroBitmapHashStorageFile.Includes(result))
|
|
||||||
return ResultFs.IncompleteBlockInZeroBitmapHashStorageFileSaveData.LogConverted(result);
|
|
||||||
|
|
||||||
Assert.SdkAssert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Result ConvertSaveFsDriverPrivateResult(Result result)
|
|
||||||
{
|
|
||||||
if (result.IsSuccess())
|
|
||||||
return result;
|
|
||||||
|
|
||||||
if (ResultFs.UnsupportedVersion.Includes(result))
|
|
||||||
return ResultFs.UnsupportedSaveDataVersion.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.IntegrityVerificationStorageCorrupted.Includes(result) ||
|
|
||||||
ResultFs.BuiltInStorageCorrupted.Includes(result) ||
|
|
||||||
ResultFs.HostFileSystemCorrupted.Includes(result) ||
|
|
||||||
ResultFs.DatabaseCorrupted.Includes(result) ||
|
|
||||||
ResultFs.ZeroBitmapFileCorrupted.Includes(result))
|
|
||||||
{
|
|
||||||
return ConvertCorruptedResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ResultFs.FatFileSystemCorrupted.Includes(result))
|
|
||||||
return result;
|
|
||||||
|
|
||||||
if (ResultFs.NotFound.Includes(result))
|
|
||||||
return ResultFs.PathNotFound.LogConverted(result);
|
|
||||||
|
|
||||||
if (ResultFs.AllocationTableFull.Includes(result))
|
|
||||||
return ResultFs.UsableSpaceNotEnough.LogConverted(result);
|
|
||||||
|
|
||||||
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))
|
|
||||||
{
|
|
||||||
return ResultFs.PathNotFound.LogConverted(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wraps an <see cref="IFile"/>, converting its returned <see cref="Result"/>s
|
|
||||||
/// to save-data-specific <see cref="Result"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public class SaveDataResultConvertFile : IResultConvertFile
|
|
||||||
{
|
|
||||||
public SaveDataResultConvertFile(ref UniqueRef<IFile> baseFile) : base(ref baseFile)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Result ConvertResult(Result result)
|
|
||||||
{
|
|
||||||
return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wraps an <see cref="IDirectory"/>, converting its returned <see cref="Result"/>s
|
|
||||||
/// to save-data-specific <see cref="Result"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public class SaveDataResultConvertDirectory : IResultConvertDirectory
|
|
||||||
{
|
|
||||||
public SaveDataResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory) : base(ref baseDirectory)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Result ConvertResult(Result result)
|
|
||||||
{
|
|
||||||
return SaveDataResultConvert.ConvertSaveFsDriverPrivateResult(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wraps an <see cref="IFileSystem"/>, converting its returned <see cref="Result"/>s
|
|
||||||
/// to save-data-specific <see cref="Result"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public class SaveDataResultConvertFileSystem : IResultConvertFileSystem
|
|
||||||
{
|
|
||||||
public SaveDataResultConvertFileSystem(ref SharedRef<IFileSystem> baseFileSystem)
|
|
||||||
: base(ref baseFileSystem)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, in Path path, OpenMode mode)
|
|
||||||
{
|
|
||||||
using var file = new UniqueRef<IFile>();
|
|
||||||
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<IDirectory> outDirectory, in Path path,
|
|
||||||
OpenDirectoryMode mode)
|
|
||||||
{
|
|
||||||
using var directory = new UniqueRef<IDirectory>();
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wraps an <see cref="ISaveDataExtraDataAccessor"/>, converting its returned <see cref="Result"/>s
|
|
||||||
/// to save-data-specific <see cref="Result"/>s.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public class SaveDataExtraDataResultConvertAccessor : ISaveDataExtraDataAccessor
|
|
||||||
{
|
|
||||||
private SharedRef<ISaveDataExtraDataAccessor> _accessor;
|
|
||||||
|
|
||||||
public SaveDataExtraDataResultConvertAccessor(ref SharedRef<ISaveDataExtraDataAccessor> accessor)
|
|
||||||
{
|
|
||||||
_accessor = SharedRef<ISaveDataExtraDataAccessor>.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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -116,6 +116,7 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 14.3.0
|
||||||
public Result OpenSaveDataFileSystem(ref SharedRef<IFileSystem> outFileSystem, SaveDataSpaceId spaceId,
|
public Result OpenSaveDataFileSystem(ref SharedRef<IFileSystem> outFileSystem, SaveDataSpaceId spaceId,
|
||||||
ulong saveDataId, in Path saveDataRootPath, bool openReadOnly, SaveDataType type, bool cacheExtraData)
|
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);
|
Result rc = OpenSaveDataDirectoryFileSystem(ref fileSystem.Ref(), spaceId, in saveDataRootPath, true);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
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.
|
if (isEmulatedOnHost)
|
||||||
// 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<IFileSystem>();
|
|
||||||
using var cachedFs = new SharedRef<SaveDataFileSystemHolder>();
|
|
||||||
|
|
||||||
// Note: Nintendo doesn't cache directory save data
|
|
||||||
// if (!allowDirectorySaveData)
|
|
||||||
{
|
{
|
||||||
// Check if we have the requested file system cached
|
// Create the save data directory on the host if needed.
|
||||||
if (_saveDataFsCacheManager.GetCache(ref cachedFs.Ref(), spaceId, saveDataId))
|
Unsafe.SkipInit(out Array18<byte> saveDirectoryNameBuffer);
|
||||||
{
|
using var saveDirectoryName = new Path();
|
||||||
using var registerBase = new SharedRef<IFileSystem>(
|
rc = PathFunctions.SetUpFixedPathSaveId(ref saveDirectoryName.Ref(), saveDirectoryNameBuffer.Items, saveDataId);
|
||||||
new SaveDataFileSystemCacheRegisterBase<SaveDataFileSystemHolder>(ref cachedFs.Ref(),
|
|
||||||
_saveDataFsCacheManager));
|
|
||||||
|
|
||||||
using var resultConvertFs = new SharedRef<SaveDataResultConvertFileSystem>(
|
|
||||||
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<SdkRecursiveMutexType> scopedLock = _extraDataCacheManager.GetScopedLock();
|
|
||||||
using var extraDataAccessor = new SharedRef<ISaveDataExtraDataAccessor>();
|
|
||||||
|
|
||||||
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);
|
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
// Cache the extra data accessor if needed
|
rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveDirectoryName);
|
||||||
if (cacheExtraData && extraDataAccessor.HasValue)
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
}
|
||||||
|
|
||||||
|
using var saveDataFs = new SharedRef<ISaveDataFileSystem>();
|
||||||
|
|
||||||
|
using (_saveDataFsCacheManager.GetScopedLock())
|
||||||
|
using (_extraDataCacheManager.GetScopedLock())
|
||||||
{
|
{
|
||||||
extraDataAccessor.Get.RegisterCacheObserver(_extraDataCacheManager, spaceId, saveDataId);
|
if (isEmulatedOnHost || !_saveDataFsCacheManager.GetCache(ref saveDataFs.Ref(), 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<ISaveDataExtraDataAccessor> extraDataAccessor =
|
||||||
|
SharedRef<ISaveDataExtraDataAccessor>.CreateCopy(in saveDataFs);
|
||||||
|
|
||||||
rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId);
|
rc = _extraDataCacheManager.Register(in extraDataAccessor, spaceId, saveDataId);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var registerFs = new SharedRef<SaveDataFileSystemCacheRegister>(
|
||||||
|
new SaveDataFileSystemCacheRegister(ref saveDataFs.Ref(), _saveDataFsCacheManager, spaceId, saveDataId));
|
||||||
|
|
||||||
if (openReadOnly)
|
if (openReadOnly)
|
||||||
{
|
{
|
||||||
outFileSystem.Reset(new ReadOnlyFileSystem(ref saveDataFs.Ref()));
|
using SharedRef<IFileSystem> tempFs = SharedRef<IFileSystem>.CreateMove(ref registerFs.Ref());
|
||||||
|
using var readOnlyFileSystem = new SharedRef<ReadOnlyFileSystem>(new ReadOnlyFileSystem(ref tempFs.Ref()));
|
||||||
|
|
||||||
|
if (!readOnlyFileSystem.HasValue)
|
||||||
|
return ResultFs.AllocationMemoryFailedInSaveDataFileSystemServiceImplB.Log();
|
||||||
|
|
||||||
|
outFileSystem.SetByMove(ref readOnlyFileSystem.Ref());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outFileSystem.SetByMove(ref saveDataFs.Ref());
|
outFileSystem.SetByMove(ref registerFs.Ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
@ -339,15 +339,15 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName);
|
rc = FsSystem.Utility.EnsureDirectory(fileSystem.Get, in saveImageName);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
using var saveFileSystem = new SharedRef<IFileSystem>();
|
using var saveFileSystem = new SharedRef<ISaveDataFileSystem>();
|
||||||
using var extraDataAccessor = new SharedRef<ISaveDataExtraDataAccessor>();
|
|
||||||
|
|
||||||
bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(attribute.Type);
|
bool isJournalingSupported = SaveDataProperties.IsJournalingSupported(attribute.Type);
|
||||||
|
bool isReconstructible = SaveDataProperties.IsReconstructible(attribute.Type, creationInfo.SpaceId);
|
||||||
|
|
||||||
rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref extraDataAccessor.Ref(),
|
rc = _config.SaveFsCreator.Create(ref saveFileSystem.Ref(), ref fileSystem.Ref(), creationInfo.SpaceId,
|
||||||
_saveDataFsCacheManager, ref fileSystem.Ref(), creationInfo.SpaceId, saveDataId,
|
saveDataId, allowDirectorySaveData: true, isDeviceUniqueMac: false, isJournalingSupported,
|
||||||
allowDirectorySaveData: true, useDeviceUniqueMac: false, isJournalingSupported,
|
isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter,
|
||||||
isMultiCommitSupported: false, openReadOnly: false, openShared: false, _timeStampGetter);
|
isReconstructible);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
var extraData = new SaveDataExtraData();
|
var extraData = new SaveDataExtraData();
|
||||||
@ -359,16 +359,16 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
extraData.TimeStamp = 0;
|
extraData.TimeStamp = 0;
|
||||||
|
|
||||||
extraData.CommitId = 0;
|
extraData.CommitId = 0;
|
||||||
_config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId)).IgnoreResult();
|
_config.GenerateRandomData(SpanHelpers.AsByteSpan(ref extraData.CommitId));
|
||||||
|
|
||||||
extraData.Flags = creationInfo.Flags;
|
extraData.Flags = creationInfo.Flags;
|
||||||
extraData.DataSize = creationInfo.Size;
|
extraData.DataSize = creationInfo.Size;
|
||||||
extraData.JournalSize = creationInfo.JournalSize;
|
extraData.JournalSize = creationInfo.JournalSize;
|
||||||
|
|
||||||
rc = extraDataAccessor.Get.WriteExtraData(in extraData);
|
rc = saveFileSystem.Get.WriteExtraData(in extraData);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
rc = extraDataAccessor.Get.CommitExtraData(true);
|
rc = saveFileSystem.Get.CommitExtraData(true);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -552,8 +552,8 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
using (var tmFileSystem = new SharedRef<IFileSystem>())
|
using (var tmFileSystem = new SharedRef<IFileSystem>())
|
||||||
{
|
{
|
||||||
// Ensure the target save data directory exists
|
// Ensure the target save data directory exists
|
||||||
rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath, false, true,
|
rc = _config.TargetManagerFsCreator.Create(ref tmFileSystem.Ref(), in saveDataRootPath,
|
||||||
ResultFs.SaveDataRootPathUnavailable.Value);
|
openCaseSensitive: false, ensureRootPathExists: true, ResultFs.SaveDataRootPathUnavailable.Value);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,8 +564,8 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path.Ref());
|
rc = _config.TargetManagerFsCreator.NormalizeCaseOfPath(out bool isTargetFsCaseSensitive, ref path.Ref());
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive, false,
|
rc = _config.TargetManagerFsCreator.Create(ref outFileSystem, in path, isTargetFsCaseSensitive,
|
||||||
ResultFs.SaveDataRootPathUnavailable.Value);
|
ensureRootPathExists: false, ResultFs.SaveDataRootPathUnavailable.Value);
|
||||||
if (rc.IsFailure()) return rc.Miss();
|
if (rc.IsFailure()) return rc.Miss();
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
@ -696,20 +696,14 @@ public class SaveDataFileSystemServiceImpl
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a save is to be stored on a host device.
|
||||||
|
/// </summary>
|
||||||
public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath)
|
public bool IsAllowedDirectorySaveData(SaveDataSpaceId spaceId, in Path saveDataRootPath)
|
||||||
{
|
{
|
||||||
return spaceId == SaveDataSpaceId.User && IsSaveEmulated(in 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)
|
public bool IsDeviceUniqueMac(SaveDataSpaceId spaceId)
|
||||||
{
|
{
|
||||||
return spaceId == SaveDataSpaceId.System ||
|
return spaceId == SaveDataSpaceId.System ||
|
||||||
|
@ -83,7 +83,8 @@ public class ApplicationTemporaryFileSystem : IFileSystem, ISaveDataExtraDataAcc
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId)
|
public void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId,
|
||||||
|
ulong saveDataId)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace LibHac.FsSystem;
|
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)
|
public void Initialize(FileSystemClient fsClient)
|
||||||
{
|
{
|
||||||
SynchronizeDirectoryMutex.Initialize();
|
SynchronizeDirectoryMutex = new SdkMutexType();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,9 +24,9 @@ internal struct DirectorySaveDataFileSystemGlobals
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the
|
/// Transactional commits should be atomic as long as the <see cref="IFileSystem.RenameDirectory"/> function of the
|
||||||
/// underlying <see cref="IFileSystem"/> is atomic.
|
/// 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>
|
/// </remarks>
|
||||||
public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccessor
|
public class DirectorySaveDataFileSystem : ISaveDataFileSystem
|
||||||
{
|
{
|
||||||
private const int IdealWorkBufferSize = 0x100000; // 1 MiB
|
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> SynchronizingDirectoryName => new[] { (byte)'/', (byte)'_' };
|
||||||
private static ReadOnlySpan<byte> LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' };
|
private static ReadOnlySpan<byte> LockFileName => new[] { (byte)'/', (byte)'.', (byte)'l', (byte)'o', (byte)'c', (byte)'k' };
|
||||||
|
|
||||||
private FileSystemClient _fsClient;
|
|
||||||
private IFileSystem _baseFs;
|
private IFileSystem _baseFs;
|
||||||
private SdkMutexType _mutex;
|
private SdkMutexType _mutex;
|
||||||
private UniqueRef<IFileSystem> _uniqueBaseFs;
|
private UniqueRef<IFileSystem> _uniqueBaseFs;
|
||||||
@ -45,16 +44,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
private bool _isMultiCommitSupported;
|
private bool _isMultiCommitSupported;
|
||||||
private bool _isJournalingEnabled;
|
private bool _isJournalingEnabled;
|
||||||
|
|
||||||
// Additions to support extra data
|
private ISaveDataExtraDataAccessorObserver _cacheObserver;
|
||||||
|
private ulong _saveDataId;
|
||||||
|
private SaveDataSpaceId _spaceId;
|
||||||
|
|
||||||
private ISaveDataCommitTimeStampGetter _timeStampGetter;
|
private ISaveDataCommitTimeStampGetter _timeStampGetter;
|
||||||
private RandomDataGenerator _randomGenerator;
|
private RandomDataGenerator _randomGenerator;
|
||||||
|
|
||||||
// Additions to support caching
|
// LibHac additions
|
||||||
private ISaveDataExtraDataAccessorCacheObserver _cacheObserver;
|
private FileSystemClient _fsClient;
|
||||||
private SaveDataSpaceId _spaceId;
|
|
||||||
private ulong _saveDataId;
|
|
||||||
|
|
||||||
// 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 UniqueRef<IFile> _lockFile;
|
||||||
|
|
||||||
private class DirectorySaveDataFile : IFile
|
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>
|
/// <summary>
|
||||||
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
|
/// Create an uninitialized <see cref="DirectorySaveDataFileSystem"/>.
|
||||||
/// If a <see cref="FileSystemClient"/> is provided a global mutex will be used when synchronizing directories.
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||||
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</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;
|
_baseFs = baseFileSystem;
|
||||||
_mutex.Initialize();
|
_mutex = new SdkMutexType();
|
||||||
_fsClient = fsClient;
|
_fsClient = fsClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,10 +139,10 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
/// <param name="baseFileSystem">The base <see cref="IFileSystem"/> to use.</param>
|
||||||
/// <param name="fsClient">The <see cref="FileSystemClient"/> to use. May be null.</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;
|
_baseFs = baseFileSystem.Get;
|
||||||
_mutex.Initialize();
|
_mutex = new SdkMutexType();
|
||||||
_uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem);
|
_uniqueBaseFs = new UniqueRef<IFileSystem>(ref baseFileSystem);
|
||||||
_fsClient = fsClient;
|
_fsClient = fsClient;
|
||||||
}
|
}
|
||||||
@ -185,6 +164,14 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
public Path ModifiedPath;
|
public Path ModifiedPath;
|
||||||
public Path SynchronizingPath;
|
public Path SynchronizingPath;
|
||||||
|
|
||||||
|
public RetryClosure(DirectorySaveDataFileSystem fs)
|
||||||
|
{
|
||||||
|
This = fs;
|
||||||
|
CommittedPath = new Path();
|
||||||
|
ModifiedPath = new Path();
|
||||||
|
SynchronizingPath = new Path();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
CommittedPath.Dispose();
|
CommittedPath.Dispose();
|
||||||
@ -195,7 +182,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
|
|
||||||
private delegate Result RetryDelegate(in RetryClosure closure);
|
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 maxRetryCount = 10;
|
||||||
const int retryWaitTimeMs = 100;
|
const int retryWaitTimeMs = 100;
|
||||||
@ -228,22 +215,17 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
|
public Result Initialize(bool isJournalingSupported, bool isMultiCommitSupported,
|
||||||
{
|
bool isJournalingEnabled, ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator)
|
||||||
return Initialize(null, null, isJournalingSupported, isMultiCommitSupported, isJournalingEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Result Initialize(ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator,
|
|
||||||
bool isJournalingSupported, bool isMultiCommitSupported, bool isJournalingEnabled)
|
|
||||||
{
|
{
|
||||||
_isJournalingSupported = isJournalingSupported;
|
_isJournalingSupported = isJournalingSupported;
|
||||||
_isMultiCommitSupported = isMultiCommitSupported;
|
_isMultiCommitSupported = isMultiCommitSupported;
|
||||||
_isJournalingEnabled = isJournalingEnabled;
|
_isJournalingEnabled = isJournalingEnabled;
|
||||||
_timeStampGetter = timeStampGetter ?? _timeStampGetter;
|
_timeStampGetter = timeStampGetter;
|
||||||
_randomGenerator = randomGenerator ?? _randomGenerator;
|
_randomGenerator = randomGenerator;
|
||||||
|
|
||||||
// Open the lock file
|
// Open the lock file
|
||||||
Result rc = GetFileSystemLock();
|
Result rc = AcquireLockFile();
|
||||||
if (rc.IsFailure()) return rc;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
using var pathModifiedDirectory = new Path();
|
using var pathModifiedDirectory = new Path();
|
||||||
@ -273,8 +255,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
{
|
{
|
||||||
rc = _baseFs.CreateDirectory(in pathCommittedDirectory);
|
rc = _baseFs.CreateDirectory(in pathCommittedDirectory);
|
||||||
|
|
||||||
// Nintendo returns on all failures, but we'll keep going if committed already exists
|
// Changed: Nintendo returns on all failures, but we'll keep going if committed already
|
||||||
// to avoid confusing people manually creating savedata in emulators
|
// exists to avoid confusing people manually creating savedata in emulators
|
||||||
if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc))
|
if (rc.IsFailure() && !ResultFs.PathAlreadyExists.Includes(rc))
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
@ -316,7 +298,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result GetFileSystemLock()
|
private Result AcquireLockFile()
|
||||||
{
|
{
|
||||||
// Having an open lock file means we already have the lock for the file system.
|
// Having an open lock file means we already have the lock for the file system.
|
||||||
if (_lockFile.HasValue)
|
if (_lockFile.HasValue)
|
||||||
@ -326,7 +308,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName);
|
Result rc = PathFunctions.SetUpFixedPath(ref pathLockFile.Ref(), LockFileName);
|
||||||
if (rc.IsFailure()) return rc;
|
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())
|
if (rc.IsFailure())
|
||||||
{
|
{
|
||||||
@ -335,15 +318,16 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
rc = _baseFs.CreateFile(in pathLockFile, 0);
|
rc = _baseFs.CreateFile(in pathLockFile, 0);
|
||||||
if (rc.IsFailure()) return rc;
|
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;
|
if (rc.IsFailure()) return rc;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return rc;
|
return rc.Miss();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_lockFile.Set(ref lockFile.Ref());
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,8 +536,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
// Delete destination dir and recreate it.
|
// Delete destination dir and recreate it.
|
||||||
Result rc = _baseFs.DeleteDirectoryRecursively(destPath);
|
Result rc = _baseFs.DeleteDirectoryRecursively(destPath);
|
||||||
|
|
||||||
// Nintendo returns all errors unconditionally because SynchronizeDirectory is always called in situations
|
// Changed: Nintendo returns all errors unconditionally because SynchronizeDirectory is always called
|
||||||
// where a PathNotFound error would mean the save directory was in an invalid state.
|
// 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
|
// We'll ignore PathNotFound errors to be more user-friendly to users who might accidentally
|
||||||
// put the save directory in an invalid state.
|
// put the save directory in an invalid state.
|
||||||
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc;
|
if (rc.IsFailure() && !ResultFs.PathNotFound.Includes(rc)) return rc;
|
||||||
@ -563,7 +547,7 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
|
|
||||||
var directoryEntry = new DirectoryEntry();
|
var directoryEntry = new DirectoryEntry();
|
||||||
|
|
||||||
// Lock only if initialized with a client
|
// Changed: Lock only if initialized with a client
|
||||||
if (_fsClient is not null)
|
if (_fsClient is not null)
|
||||||
{
|
{
|
||||||
using ScopedLock<SdkMutexType> scopedLock =
|
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)
|
if (!_isJournalingEnabled || !_isJournalingSupported)
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var closure = new RetryClosure();
|
using var closure = new RetryClosure(this);
|
||||||
closure.This = this;
|
|
||||||
|
|
||||||
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedDirectoryName);
|
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedDirectoryName);
|
||||||
if (rc.IsFailure()) return rc;
|
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;
|
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;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
if (_openWritableFileCount > 0)
|
|
||||||
{
|
|
||||||
// All files must be closed before commiting save data.
|
// All files must be closed before commiting save data.
|
||||||
|
if (_openWritableFileCount > 0)
|
||||||
return ResultFs.WriteModeFileNotClosed.Log();
|
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.
|
// 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 (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
// If something goes wrong beyond this point, the commit will be
|
// If something goes wrong beyond this point, the commit of the main data
|
||||||
// completed the next time the savedata is opened.
|
// 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;
|
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;
|
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;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,11 +639,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
|
|
||||||
protected override Result DoRollback()
|
protected override Result DoRollback()
|
||||||
{
|
{
|
||||||
// No old data is kept for non-journaling save data, so there's nothing to rollback to
|
// No old data is kept for non-journaling save data, so there's nothing to rollback to in that case
|
||||||
if (!_isJournalingSupported)
|
if (_isJournalingSupported)
|
||||||
return Result.Success;
|
{
|
||||||
|
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)
|
protected override Result DoGetFreeSpaceSize(out long freeSpace, in Path path)
|
||||||
@ -694,6 +682,46 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
return Result.Success;
|
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()
|
private void DecrementWriteOpenFileCount()
|
||||||
{
|
{
|
||||||
// Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile
|
// Todo?: Calling OpenFile when outFile already contains a DirectorySaveDataFile
|
||||||
@ -703,7 +731,8 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
_openWritableFileCount--;
|
_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.
|
// Everything below this point is a LibHac extension.
|
||||||
|
|
||||||
private static ReadOnlySpan<byte> CommittedExtraDataName => // "/ExtraData0"
|
private static ReadOnlySpan<byte> CommittedExtraDataName => // "/ExtraData0"
|
||||||
@ -860,6 +889,15 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
return Result.Success;
|
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)
|
private Result EnsureExtraDataSize(in Path path)
|
||||||
{
|
{
|
||||||
using var file = new UniqueRef<IFile>();
|
using var file = new UniqueRef<IFile>();
|
||||||
@ -902,42 +940,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
return Result.Success;
|
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()
|
private Result UpdateExtraDataTimeStamp()
|
||||||
{
|
{
|
||||||
Assert.SdkRequires(_mutex.IsLockedByCurrentThread());
|
Assert.SdkRequires(_mutex.IsLockedByCurrentThread());
|
||||||
@ -987,50 +989,33 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
if (!_isJournalingSupported || !_isJournalingEnabled)
|
if (!_isJournalingSupported || !_isJournalingEnabled)
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
|
|
||||||
var closure = new RetryClosure();
|
using var closure = new RetryClosure(this);
|
||||||
closure.This = this;
|
|
||||||
|
|
||||||
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath, ModifiedExtraDataName);
|
Result rc = PathFunctions.SetUpFixedPath(ref closure.ModifiedPath.Ref(), ModifiedExtraDataName);
|
||||||
if (rc.IsFailure()) return rc;
|
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;
|
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;
|
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.
|
// 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 (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
// If something goes wrong beyond this point, the commit will be
|
// If something goes wrong beyond this point, the commit will be
|
||||||
// completed the next time the savedata is opened.
|
// 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;
|
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;
|
if (rc.IsFailure()) return rc;
|
||||||
|
|
||||||
closure.Dispose();
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1056,14 +1041,6 @@ public class DirectorySaveDataFileSystem : IFileSystem, ISaveDataExtraDataAccess
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId,
|
|
||||||
ulong saveDataId)
|
|
||||||
{
|
|
||||||
_cacheObserver = observer;
|
|
||||||
_spaceId = spaceId;
|
|
||||||
_saveDataId = saveDataId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId;
|
public SaveDataSpaceId GetSaveDataSpaceId() => _spaceId;
|
||||||
public ulong GetSaveDataId() => _saveDataId;
|
public ulong GetSaveDataId() => _saveDataId;
|
||||||
}
|
}
|
@ -3,61 +3,62 @@ using LibHac.Common;
|
|||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
|
|
||||||
namespace LibHac.FsSrv.Impl;
|
namespace LibHac.FsSystem;
|
||||||
|
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Wraps an <see cref="IFile"/>, converting its returned <see cref="Result"/>s to different
|
/// Wraps an <see cref="IFile"/>, converting its returned <see cref="Result"/>s to different
|
||||||
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
||||||
/// </summary>
|
/// </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 abstract class IResultConvertFile : IFile
|
public abstract class IResultConvertFile : IFile
|
||||||
{
|
{
|
||||||
protected UniqueRef<IFile> BaseFile;
|
private UniqueRef<IFile> _baseFile;
|
||||||
|
|
||||||
protected IResultConvertFile(ref UniqueRef<IFile> baseFile)
|
protected IResultConvertFile(ref UniqueRef<IFile> baseFile)
|
||||||
{
|
{
|
||||||
BaseFile = new UniqueRef<IFile>(ref baseFile);
|
_baseFile = new UniqueRef<IFile>(ref baseFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
BaseFile.Destroy();
|
_baseFile.Destroy();
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Result ConvertResult(Result result);
|
||||||
|
|
||||||
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
|
protected override Result DoRead(out long bytesRead, long offset, Span<byte> 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<byte> source, in WriteOption option)
|
protected override Result DoWrite(long offset, ReadOnlySpan<byte> 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()
|
protected override Result DoFlush()
|
||||||
{
|
{
|
||||||
return ConvertResult(BaseFile.Get.Flush());
|
return ConvertResult(_baseFile.Get.Flush()).Ret();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Result DoSetSize(long size)
|
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)
|
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<byte> outBuffer, OperationId operationId, long offset, long size,
|
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
|
||||||
ReadOnlySpan<byte> inBuffer)
|
ReadOnlySpan<byte> 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
|
// ReSharper disable once InconsistentNaming
|
||||||
@ -65,33 +66,34 @@ public abstract class IResultConvertFile : IFile
|
|||||||
/// Wraps an <see cref="IDirectory"/>, converting its returned <see cref="Result"/>s to different
|
/// Wraps an <see cref="IDirectory"/>, converting its returned <see cref="Result"/>s to different
|
||||||
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
||||||
/// </summary>
|
/// </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 abstract class IResultConvertDirectory : IDirectory
|
public abstract class IResultConvertDirectory : IDirectory
|
||||||
{
|
{
|
||||||
protected UniqueRef<IDirectory> BaseDirectory;
|
private UniqueRef<IDirectory> _baseDirectory;
|
||||||
|
|
||||||
protected IResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory)
|
protected IResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory)
|
||||||
{
|
{
|
||||||
BaseDirectory = new UniqueRef<IDirectory>(ref baseDirectory);
|
_baseDirectory = new UniqueRef<IDirectory>(ref baseDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
BaseDirectory.Destroy();
|
_baseDirectory.Destroy();
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract Result ConvertResult(Result result);
|
||||||
|
|
||||||
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> entryBuffer)
|
protected override Result DoRead(out long entriesRead, Span<DirectoryEntry> 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)
|
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
|
// ReSharper disable once InconsistentNaming
|
||||||
@ -99,114 +101,110 @@ public abstract class IResultConvertDirectory : IDirectory
|
|||||||
/// Wraps an <see cref="IFileSystem"/>, converting its returned <see cref="Result"/>s to different
|
/// Wraps an <see cref="IFileSystem"/>, converting its returned <see cref="Result"/>s to different
|
||||||
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
/// <see cref="Result"/>s based on the <see cref="ConvertResult"/> function.
|
||||||
/// </summary>
|
/// </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 abstract class IResultConvertFileSystem : IFileSystem
|
public abstract class IResultConvertFileSystem<T> : ISaveDataFileSystem where T : IFileSystem
|
||||||
{
|
{
|
||||||
protected SharedRef<IFileSystem> BaseFileSystem;
|
private SharedRef<T> _baseFileSystem;
|
||||||
|
|
||||||
protected IResultConvertFileSystem(ref SharedRef<IFileSystem> baseFileSystem)
|
protected IResultConvertFileSystem(ref SharedRef<T> baseFileSystem)
|
||||||
{
|
{
|
||||||
BaseFileSystem = SharedRef<IFileSystem>.CreateMove(ref baseFileSystem);
|
_baseFileSystem = SharedRef<T>.CreateMove(ref baseFileSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
BaseFileSystem.Destroy();
|
_baseFileSystem.Destroy();
|
||||||
|
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected T GetFileSystem() => _baseFileSystem.Get;
|
||||||
|
|
||||||
|
protected abstract Result ConvertResult(Result result);
|
||||||
|
|
||||||
protected override Result DoCreateFile(in Path path, long size, CreateFileOptions option)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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<IFile> outFile, in Path path, OpenMode mode);
|
|
||||||
|
|
||||||
protected abstract override Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, in Path path,
|
|
||||||
OpenDirectoryMode mode);
|
|
||||||
|
|
||||||
protected override Result DoCommit()
|
protected override Result DoCommit()
|
||||||
{
|
{
|
||||||
return ConvertResult(BaseFileSystem.Get.Commit());
|
return ConvertResult(_baseFileSystem.Get.Commit()).Ret();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Result DoCommitProvisionally(long counter)
|
protected override Result DoCommitProvisionally(long counter)
|
||||||
{
|
{
|
||||||
return ConvertResult(BaseFileSystem.Get.CommitProvisionally(counter));
|
return ConvertResult(_baseFileSystem.Get.CommitProvisionally(counter)).Ret();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Result DoRollback()
|
protected override Result DoRollback()
|
||||||
{
|
{
|
||||||
return ConvertResult(BaseFileSystem.Get.Rollback());
|
return ConvertResult(_baseFileSystem.Get.Rollback()).Ret();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Result DoFlush()
|
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)
|
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<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
protected override Result DoQueryEntry(Span<byte> outBuffer, ReadOnlySpan<byte> inBuffer, QueryId queryId,
|
||||||
in Path path)
|
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)
|
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)
|
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);
|
|
||||||
}
|
}
|
@ -6,11 +6,11 @@ namespace LibHac.FsSystem;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides read/write access to a save data file system's extra data.
|
/// Provides read/write access to a save data file system's extra data.
|
||||||
/// </summary>
|
/// </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
|
public interface ISaveDataExtraDataAccessor : IDisposable
|
||||||
{
|
{
|
||||||
Result WriteExtraData(in SaveDataExtraData extraData);
|
Result WriteExtraData(in SaveDataExtraData extraData);
|
||||||
Result CommitExtraData(bool updateTimeStamp);
|
Result CommitExtraData(bool updateTimeStamp);
|
||||||
Result ReadExtraData(out SaveDataExtraData extraData);
|
Result ReadExtraData(out SaveDataExtraData extraData);
|
||||||
void RegisterCacheObserver(ISaveDataExtraDataAccessorCacheObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
|
void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
|
||||||
}
|
}
|
@ -9,8 +9,8 @@ namespace LibHac.FsSystem;
|
|||||||
/// <see cref="SaveDataExtraDataAccessorCacheManager"/>. When an extra data accessor is disposed, the accessor will
|
/// <see cref="SaveDataExtraDataAccessorCacheManager"/>. 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.
|
/// use this interface to notify the cache manager that it should be removed from the extra data cache.
|
||||||
/// </summary>
|
/// </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 ISaveDataExtraDataAccessorCacheObserver : IDisposable
|
public interface ISaveDataExtraDataAccessorObserver : IDisposable
|
||||||
{
|
{
|
||||||
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
|
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
|
||||||
}
|
}
|
22
src/LibHac/FsSystem/ISaveDataFileSystem.cs
Normal file
22
src/LibHac/FsSystem/ISaveDataFileSystem.cs
Normal file
@ -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 RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ICacheableSaveDataFileSystem
|
||||||
|
{
|
||||||
|
bool IsSaveDataFileSystemCacheEnabled();
|
||||||
|
Result RollbackOnlyModified();
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Fs;
|
|
||||||
|
|
||||||
namespace LibHac.FsSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Provides a mechanism for caching save data file systems.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public interface ISaveDataFileSystemCacheManager : IDisposable
|
|
||||||
{
|
|
||||||
bool GetCache(ref SharedRef<SaveDataFileSystemHolder> outFileSystem, SaveDataSpaceId spaceId, ulong saveDataId);
|
|
||||||
void Register(ref SharedRef<SaveDataFileSystemHolder> fileSystem);
|
|
||||||
void Register(ref SharedRef<ApplicationTemporaryFileSystem> fileSystem);
|
|
||||||
void Unregister(SaveDataSpaceId spaceId, ulong saveDataId);
|
|
||||||
}
|
|
@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wraps a save data <see cref="IFileSystem"/>.
|
|
||||||
/// Upon disposal the base file system is returned to the provided <see cref="ISaveDataFileSystemCacheManager"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the base file system. Must be one of <see cref="SaveDataFileSystem"/>,
|
|
||||||
/// <see cref="ApplicationTemporaryFileSystem"/> or <see cref="DirectorySaveDataFileSystem"/>.</typeparam>
|
|
||||||
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
|
|
||||||
public class SaveDataFileSystemCacheRegisterBase<T> : IFileSystem where T : IFileSystem
|
|
||||||
{
|
|
||||||
private SharedRef<T> _baseFileSystem;
|
|
||||||
private ISaveDataFileSystemCacheManager _cacheManager;
|
|
||||||
|
|
||||||
public SaveDataFileSystemCacheRegisterBase(ref SharedRef<T> baseFileSystem,
|
|
||||||
ISaveDataFileSystemCacheManager cacheManager)
|
|
||||||
{
|
|
||||||
if (typeof(T) != typeof(SaveDataFileSystemHolder) && typeof(T) != typeof(ApplicationTemporaryFileSystem))
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(
|
|
||||||
$"The file system type of a {nameof(SaveDataFileSystemCacheRegisterBase<T>)} must be {nameof(SaveDataFileSystemHolder)} or {nameof(ApplicationTemporaryFileSystem)}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_baseFileSystem = SharedRef<T>.CreateMove(ref baseFileSystem);
|
|
||||||
_cacheManager = cacheManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Dispose()
|
|
||||||
{
|
|
||||||
if (typeof(T) == typeof(SaveDataFileSystemHolder))
|
|
||||||
{
|
|
||||||
_cacheManager.Register(ref Unsafe.As<SharedRef<T>, SharedRef<SaveDataFileSystemHolder>>(ref _baseFileSystem));
|
|
||||||
}
|
|
||||||
else if (typeof(T) == typeof(ApplicationTemporaryFileSystem))
|
|
||||||
{
|
|
||||||
_cacheManager.Register(ref Unsafe.As<SharedRef<T>, SharedRef<ApplicationTemporaryFileSystem>>(ref _baseFileSystem));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.SdkAssert(false, "Invalid save data file system type.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, in Path path, OpenMode mode)
|
|
||||||
{
|
|
||||||
return _baseFileSystem.Get.OpenFile(ref outFile, path, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> 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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
using System;
|
|
||||||
using LibHac.Common;
|
|
||||||
using LibHac.Diag;
|
|
||||||
using LibHac.Fs;
|
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
|
|
||||||
namespace LibHac.FsSystem;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Holds a file system for adding to the save data file system cache.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks> Nintendo uses concrete types in <see cref="ISaveDataFileSystemCacheManager"/> instead of an interface.
|
|
||||||
/// This class allows <see cref="DirectorySaveDataFileSystem"/> to be cached in a way that changes the original
|
|
||||||
/// design as little as possible.
|
|
||||||
/// </remarks>
|
|
||||||
public class SaveDataFileSystemHolder : ForwardingFileSystem
|
|
||||||
{
|
|
||||||
public SaveDataFileSystemHolder(ref SharedRef<IFileSystem> 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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,9 @@
|
|||||||
using LibHac.Bcat;
|
using System;
|
||||||
|
using LibHac.Bcat;
|
||||||
using LibHac.Common.Keys;
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSrv;
|
using LibHac.FsSrv;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
|
||||||
namespace LibHac;
|
namespace LibHac;
|
||||||
|
|
||||||
@ -15,13 +17,17 @@ public static class HorizonFactory
|
|||||||
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||||
var fsServer = new FileSystemServer(fsServerClient);
|
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
|
var fsServerConfig = new FileSystemServerConfig
|
||||||
{
|
{
|
||||||
DeviceOperator = defaultObjects.DeviceOperator,
|
DeviceOperator = defaultObjects.DeviceOperator,
|
||||||
ExternalKeySet = keySet.ExternalKeySet,
|
ExternalKeySet = keySet.ExternalKeySet,
|
||||||
FsCreators = defaultObjects.FsCreators,
|
FsCreators = defaultObjects.FsCreators,
|
||||||
|
RandomGenerator = randomGenerator
|
||||||
};
|
};
|
||||||
|
|
||||||
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
|
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, fsServerConfig);
|
||||||
|
@ -16,4 +16,17 @@ public class Layout
|
|||||||
|
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int AlignOf<T>() where T : unmanaged
|
||||||
|
{
|
||||||
|
return Unsafe.SizeOf<AlignOfHelper<T>>() - Unsafe.SizeOf<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct AlignOfHelper<T> where T : unmanaged
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0169 // Field is never used
|
||||||
|
private readonly byte _padding;
|
||||||
|
private readonly T _value;
|
||||||
|
#pragma warning restore CS0169
|
||||||
|
}
|
||||||
}
|
}
|
@ -2,6 +2,7 @@
|
|||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSrv;
|
using LibHac.FsSrv;
|
||||||
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
|
|
||||||
namespace LibHac.Tests.Fs.FileSystemClientTests;
|
namespace LibHac.Tests.Fs.FileSystemClientTests;
|
||||||
@ -18,7 +19,10 @@ public static class FileSystemServerFactory
|
|||||||
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||||
var fsServer = new FileSystemServer(fsServerClient);
|
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);
|
defaultObjects.SdCard.SetSdCardInsertionStatus(sdCardInserted);
|
||||||
|
|
||||||
@ -26,6 +30,7 @@ public static class FileSystemServerFactory
|
|||||||
config.FsCreators = defaultObjects.FsCreators;
|
config.FsCreators = defaultObjects.FsCreators;
|
||||||
config.DeviceOperator = defaultObjects.DeviceOperator;
|
config.DeviceOperator = defaultObjects.DeviceOperator;
|
||||||
config.ExternalKeySet = new ExternalKeySet();
|
config.ExternalKeySet = new ExternalKeySet();
|
||||||
|
config.RandomGenerator = randomGenerator;
|
||||||
|
|
||||||
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
|
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
|
||||||
return horizon;
|
return horizon;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using LibHac.Fs;
|
using LibHac.Fs;
|
||||||
using LibHac.Fs.Impl;
|
using LibHac.Fs.Impl;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@ -476,21 +475,10 @@ public class TypeLayoutTests
|
|||||||
Assert.Equal(0, GetOffset(in s, in s.Value));
|
Assert.Equal(0, GetOffset(in s, in s.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
private struct Int64AlignmentTest
|
|
||||||
{
|
|
||||||
public byte A;
|
|
||||||
public Int64 B;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void Int64Test_Layout()
|
public static void Int64_Layout()
|
||||||
{
|
{
|
||||||
var s = new Int64AlignmentTest();
|
Assert.Equal(8, Unsafe.SizeOf<Int64>());
|
||||||
|
Assert.Equal(4, AlignOf<Int64>());
|
||||||
Assert.Equal(12, Unsafe.SizeOf<Int64AlignmentTest>());
|
|
||||||
|
|
||||||
Assert.Equal(0, GetOffset(in s, in s.A));
|
|
||||||
Assert.Equal(4, GetOffset(in s, in s.B));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -48,8 +48,8 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
|
|||||||
FileSystemClient fsClient)
|
FileSystemClient fsClient)
|
||||||
{
|
{
|
||||||
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
|
var obj = new DirectorySaveDataFileSystem(baseFileSystem, fsClient);
|
||||||
Result rc = obj.Initialize(timeStampGetter, randomGenerator, isJournalingSupported, isMultiCommitSupported,
|
Result rc = obj.Initialize(isJournalingSupported, isMultiCommitSupported, isJournalingEnabled, timeStampGetter,
|
||||||
isJournalingEnabled);
|
randomGenerator);
|
||||||
|
|
||||||
if (rc.IsSuccess())
|
if (rc.IsSuccess())
|
||||||
{
|
{
|
||||||
@ -521,7 +521,7 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
|
|||||||
|
|
||||||
private int _index;
|
private int _index;
|
||||||
|
|
||||||
public Result GenerateRandom(Span<byte> output)
|
public void GenerateRandom(Span<byte> output)
|
||||||
{
|
{
|
||||||
if (output.Length != 8)
|
if (output.Length != 8)
|
||||||
throw new ArgumentException();
|
throw new ArgumentException();
|
||||||
@ -529,7 +529,6 @@ public class DirectorySaveDataFileSystemTests : CommittableIFileSystemTests
|
|||||||
Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index];
|
Unsafe.As<byte, long>(ref MemoryMarshal.GetReference(output)) = Values[_index];
|
||||||
|
|
||||||
_index = (_index + 1) % Values.Length;
|
_index = (_index + 1) % Values.Length;
|
||||||
return Result.Success;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using static LibHac.Tests.Common.Layout;
|
using static LibHac.Tests.Common.Layout;
|
||||||
@ -287,6 +286,7 @@ public class TypeLayoutTests
|
|||||||
HierarchicalIntegrityVerificationLevelInformation s = default;
|
HierarchicalIntegrityVerificationLevelInformation s = default;
|
||||||
|
|
||||||
Assert.Equal(0x18, Unsafe.SizeOf<HierarchicalIntegrityVerificationLevelInformation>());
|
Assert.Equal(0x18, Unsafe.SizeOf<HierarchicalIntegrityVerificationLevelInformation>());
|
||||||
|
Assert.Equal(0x04, AlignOf<HierarchicalIntegrityVerificationLevelInformation>());
|
||||||
|
|
||||||
Assert.Equal(0x00, GetOffset(in s, in s.Offset));
|
Assert.Equal(0x00, GetOffset(in s, in s.Offset));
|
||||||
Assert.Equal(0x08, GetOffset(in s, in s.Size));
|
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));
|
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<HierarchicalIntegrityVerificationLevelInformationAlignmentTest>());
|
|
||||||
|
|
||||||
Assert.Equal(0, GetOffset(in s, in s.A));
|
|
||||||
Assert.Equal(4, GetOffset(in s, in s.B));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public static void HierarchicalIntegrityVerificationInformation_Layout()
|
public static void HierarchicalIntegrityVerificationInformation_Layout()
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using LibHac.Common.Keys;
|
using LibHac.Common.Keys;
|
||||||
using LibHac.Fs.Fsa;
|
using LibHac.Fs.Fsa;
|
||||||
using LibHac.FsSrv;
|
using LibHac.FsSrv;
|
||||||
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
|
|
||||||
namespace LibHac.Tests;
|
namespace LibHac.Tests;
|
||||||
@ -18,12 +19,16 @@ public static class HorizonFactory
|
|||||||
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
HorizonClient fsServerClient = horizon.CreatePrivilegedHorizonClient();
|
||||||
var fsServer = new FileSystemServer(fsServerClient);
|
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();
|
var config = new FileSystemServerConfig();
|
||||||
config.FsCreators = defaultObjects.FsCreators;
|
config.FsCreators = defaultObjects.FsCreators;
|
||||||
config.DeviceOperator = defaultObjects.DeviceOperator;
|
config.DeviceOperator = defaultObjects.DeviceOperator;
|
||||||
config.ExternalKeySet = new ExternalKeySet();
|
config.ExternalKeySet = new ExternalKeySet();
|
||||||
|
config.RandomGenerator = randomGenerator;
|
||||||
|
|
||||||
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
|
FileSystemServerInitializer.InitializeWithConfig(fsServerClient, fsServer, config);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user