Implement SaveDataFileSystem

This commit is contained in:
Alex Barney 2024-03-09 17:47:17 -07:00
parent 2b2b7471ea
commit 2c14770ceb
6 changed files with 181 additions and 54 deletions

View file

@ -1068,6 +1068,7 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary
2,6395,,,,UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem,
2,6396,,,,UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem,
2,6397,,,,UnsupportedOperateRangeForRegionSwitchStorage,
2,6398,,,,UnsupportedOperateRangeForSaveDataFile,
2,6400,6449,,,PermissionDenied,
2,6403,,,,HostFileSystemOperationDisabled,Returned when opening a host FS on a retail device.

1 Module DescriptionStart DescriptionEnd Flags Namespace Name Summary
1068 3 506 505 OutOfAddressSpace OutOfTransferMemory
1069 3 510 506 SessionClosedForReceive OutOfAddressSpace
1070 3 511 510 SessionClosedForReply SessionClosedForReceive
1071 3 511 SessionClosedForReply
1072 3 512 ReceiveListBroken
1073 4 9 InvalidHandle
1074 4 2001 InvalidArgument

View file

@ -1960,6 +1960,8 @@ public static class ResultFs
public static Result.Base UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6396);
/// <summary>Error code: 2002-6397; Inner value: 0x31fa02</summary>
public static Result.Base UnsupportedOperateRangeForRegionSwitchStorage => new Result.Base(ModuleFs, 6397);
/// <summary>Error code: 2002-6398; Inner value: 0x31fc02</summary>
public static Result.Base UnsupportedOperateRangeForSaveDataFile => new Result.Base(ModuleFs, 6398);
/// <summary>Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002</summary>
public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); }

View file

@ -10,7 +10,7 @@ namespace LibHac.FsSystem;
/// 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.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public abstract class IResultConvertFile : IFile
{
private UniqueRef<IFile> _baseFile;
@ -66,7 +66,7 @@ public abstract class IResultConvertFile : IFile
/// 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.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public abstract class IResultConvertDirectory : IDirectory
{
private UniqueRef<IDirectory> _baseDirectory;
@ -101,7 +101,7 @@ public abstract class IResultConvertDirectory : IDirectory
/// 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.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public abstract class IResultConvertFileSystem<T> : ISaveDataFileSystem where T : IFileSystem
{
private SharedRef<T> _baseFileSystem;

View file

@ -1,7 +1,7 @@
// ReSharper disable UnusedMember.Local UnusedType.Local
#pragma warning disable CS0169 // Field is never used
using System;
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Diag;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using LibHac.FsSystem.Save;
@ -9,52 +9,92 @@ using LibHac.Os;
namespace LibHac.FsSystem;
/// <summary>
/// Wraps an <see cref="IFile"/> opened by a <see cref="SaveDataFileSystem"/>
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
file class SaveDataFile : IFile
{
private UniqueRef<IFile> _file;
public SaveDataFile(ref UniqueRef<IFile> file)
{
throw new NotImplementedException();
_file = new UniqueRef<IFile>(ref file);
Assert.SdkRequiresNotNull(in _file);
}
public override void Dispose()
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
_file.Destroy();
base.Dispose();
}
protected override Result DoRead(out long bytesRead, long offset, Span<byte> destination, in ReadOption option)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
Result res = _file.Get.Read(out bytesRead, offset, destination);
if (res.IsFailure())
{
if (ResultFs.InvalidSaveDataFileReadOffset.Includes(res))
{
return ResultFs.OutOfRange.LogConverted(res);
}
return res.Miss();
}
return Result.Success;
}
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source, in WriteOption option)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
return _file.Get.Write(offset, source, in option).Ret();
}
protected override Result DoFlush()
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
return _file.Get.Flush().Ret();
}
protected override Result DoSetSize(long size)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
return _file.Get.SetSize(size).Ret();
}
protected override Result DoGetSize(out long size)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
return _file.Get.GetSize(out size).Ret();
}
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> inBuffer)
{
throw new NotImplementedException();
Assert.SdkRequiresNotNull(in _file);
if (operationId == OperationId.InvalidateCache)
return ResultFs.UnsupportedOperateRangeForSaveDataFile.Log();
return _file.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer).Ret();
}
}
/// <summary>
/// Reads and writes to the file system inside a journal integrity save data image file.
/// </summary>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystemHolder
{
private SharedRef<IStorage> _baseStorage;
@ -69,39 +109,75 @@ public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystem
public SaveDataFileSystem()
{
throw new NotImplementedException();
_baseStorage = new SharedRef<IStorage>();
_saveFsDriver = new JournalIntegritySaveDataFileSystemDriver();
_cacheObserver = null;
_mutex = new SdkMutex();
_canCommitProvisionally = false;
}
public override void Dispose()
{
throw new NotImplementedException();
_saveFsDriver.FinalizeObject();
_cacheObserver?.Unregister(_spaceId, _saveDataId);
_saveFsDriver.Dispose();
_baseStorage.Destroy();
base.Dispose();
}
public static Result ExtractParameters(out JournalIntegritySaveDataParameters outParam, IStorage saveStorage,
IBufferManager bufferManager, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out outParam);
using var fileSystem = new JournalIntegritySaveDataFileSystemDriver();
Result res = saveStorage.GetSize(out long size);
if (res.IsFailure()) return res.Miss();
using var saveSubStorage = new ValueSubStorage(saveStorage, 0, size);
res = fileSystem.Initialize(in saveSubStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion);
if (res.IsFailure()) return res.Miss();
fileSystem.ExtractParameters(out outParam);
return Result.Success;
}
public Result Initialize(IStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion, bool canCommitProvisionally)
{
throw new NotImplementedException();
return Initialize(baseStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, timeStampGetter: null,
randomGenerator: null, minimumVersion, canCommitProvisionally).Ret();
}
public Result Initialize(IStorage baseStorage, IBufferManager bufferManager, IMacGenerator macGenerator,
IHash256GeneratorFactorySelector hashGeneratorFactorySelector, ISaveDataCommitTimeStampGetter timeStampGetter,
RandomDataGenerator randomGenerator, uint minimumVersion, bool canCommitProvisionally)
{
throw new NotImplementedException();
Result res = baseStorage.GetSize(out long size);
if (res.IsFailure()) return res.Miss();
using var baseSubStorage = new ValueSubStorage(baseStorage, 0, size);
res = _saveFsDriver.Initialize(in baseSubStorage, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion);
if (res.IsFailure()) return res.Miss();
_commitTimeStampGetter = timeStampGetter;
_randomGeneratorForCommit = randomGenerator;
_canCommitProvisionally = canCommitProvisionally;
return Result.Success;
}
public Result Initialize(ref readonly SharedRef<IStorage> baseStorage, IBufferManager bufferManager,
IMacGenerator macGenerator, IHash256GeneratorFactorySelector hashGeneratorFactorySelector, uint minimumVersion,
bool canCommitProvisionally)
{
throw new NotImplementedException();
_baseStorage.SetByCopy(in baseStorage);
return Initialize(_baseStorage.Get, bufferManager, macGenerator, hashGeneratorFactorySelector, minimumVersion,
canCommitProvisionally).Ret();
}
public Result Initialize(ref readonly SharedRef<IStorage> baseStorage, IBufferManager bufferManager,
@ -109,136 +185,184 @@ public class SaveDataFileSystem : ISaveDataFileSystem, InternalStorageFileSystem
ISaveDataCommitTimeStampGetter timeStampGetter, RandomDataGenerator randomGenerator, uint minimumVersion,
bool canCommitProvisionally)
{
throw new NotImplementedException();
_baseStorage.SetByCopy(in baseStorage);
return Initialize(_baseStorage.Get, bufferManager, macGenerator, hashGeneratorFactorySelector, timeStampGetter,
randomGenerator, minimumVersion, canCommitProvisionally).Ret();
}
protected override Result DoCreateFile(ref readonly Path path, long size, CreateFileOptions option)
{
throw new NotImplementedException();
return _saveFsDriver.CreateFile(in path, size, option).Ret();
}
protected override Result DoDeleteFile(ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.DeleteFile(in path).Ret();
}
protected override Result DoCreateDirectory(ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.CreateDirectory(in path).Ret();
}
protected override Result DoDeleteDirectory(ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.DeleteDirectory(in path).Ret();
}
protected override Result DoDeleteDirectoryRecursively(ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.DeleteDirectoryRecursively(in path).Ret();
}
protected override Result DoCleanDirectoryRecursively(ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.CleanDirectoryRecursively(in path).Ret();
}
protected override Result DoRenameFile(ref readonly Path currentPath, ref readonly Path newPath)
{
throw new NotImplementedException();
return _saveFsDriver.RenameFile(in currentPath, in newPath).Ret();
}
protected override Result DoRenameDirectory(ref readonly Path currentPath, ref readonly Path newPath)
{
throw new NotImplementedException();
return _saveFsDriver.RenameDirectory(in currentPath, in newPath).Ret();
}
protected override Result DoGetEntryType(out DirectoryEntryType entryType, ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.GetEntryType(out entryType, in path).Ret();
}
protected override Result DoOpenFile(ref UniqueRef<IFile> outFile, ref readonly Path path, OpenMode mode)
{
throw new NotImplementedException();
using var file = new UniqueRef<IFile>();
Result res = _saveFsDriver.OpenFile(ref file.Ref, in path, mode);
if (res.IsFailure()) return res.Miss();
using var wrapperFile = new UniqueRef<SaveDataFile>(new SaveDataFile(ref file.Ref));
outFile.Set(ref wrapperFile.Ref);
return Result.Success;
}
protected override Result DoOpenDirectory(ref UniqueRef<IDirectory> outDirectory, ref readonly Path path, OpenDirectoryMode mode)
{
throw new NotImplementedException();
return _saveFsDriver.OpenDirectory(ref outDirectory, in path, mode).Ret();
}
private Result DoCommit(bool updateTimeStamp)
{
throw new NotImplementedException();
if (updateTimeStamp && _commitTimeStampGetter is not null)
{
Assert.SdkNotNull(_randomGeneratorForCommit);
Result res = ReadExtraData(out SaveDataExtraData extraData);
if (res.IsFailure()) return res.Miss();
res = _commitTimeStampGetter.Get(out long timeStamp);
if (res.IsSuccess())
extraData.TimeStamp = timeStamp;
long commitId = 0;
do
{
_randomGeneratorForCommit(SpanHelpers.AsByteSpan(ref commitId));
} while (commitId == 0 || commitId == extraData.CommitId);
extraData.CommitId = commitId;
res = WriteExtraData(in extraData);
if (res.IsFailure()) return res.Miss();
}
return _saveFsDriver.Commit().Ret();
}
protected override Result DoCommit()
{
throw new NotImplementedException();
return DoCommit(updateTimeStamp: true).Ret();
}
public long GetCounterForBundledCommit()
{
throw new NotImplementedException();
return _saveFsDriver.GetCounterForBundledCommit();
}
protected override Result DoCommitProvisionally(long counter)
{
throw new NotImplementedException();
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
if (!_canCommitProvisionally)
return ResultFs.UnsupportedCommitProvisionallyForSaveDataFileSystem.Log();
return _saveFsDriver.CommitProvisionally(counter).Ret();
}
protected override Result DoRollback()
{
throw new NotImplementedException();
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
return _saveFsDriver.Rollback().Ret();
}
protected override Result DoGetFreeSpaceSize(out long freeSpace, ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.GetFreeSpaceSize(out freeSpace, in path).Ret();
}
protected override Result DoGetTotalSpaceSize(out long totalSpace, ref readonly Path path)
{
throw new NotImplementedException();
return _saveFsDriver.GetTotalSpaceSize(out totalSpace, in path).Ret();
}
protected override Result DoGetFileSystemAttribute(out FileSystemAttribute outAttribute)
{
throw new NotImplementedException();
return _saveFsDriver.GetFileSystemAttribute(out outAttribute).Ret();
}
public override Result WriteExtraData(in SaveDataExtraData extraData)
{
throw new NotImplementedException();
return _saveFsDriver.WriteExtraData(in Unsafe.As<SaveDataExtraData, JournalIntegritySaveDataFileSystem.ExtraData>(ref Unsafe.AsRef(in extraData))).Ret();
}
public override Result CommitExtraData(bool updateTimeStamp)
{
throw new NotImplementedException();
return DoCommit(updateTimeStamp).Ret();
}
public override Result ReadExtraData(out SaveDataExtraData extraData)
{
throw new NotImplementedException();
UnsafeHelpers.SkipParamInit(out extraData);
_saveFsDriver.ReadExtraData(out Unsafe.As<SaveDataExtraData, JournalIntegritySaveDataFileSystem.ExtraData>(ref extraData));
return Result.Success;
}
public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer, SaveDataSpaceId spaceId, ulong saveDataId)
public override void RegisterExtraDataAccessorObserver(ISaveDataExtraDataAccessorObserver observer,
SaveDataSpaceId spaceId, ulong saveDataId)
{
throw new NotImplementedException();
_cacheObserver = observer;
_spaceId = spaceId;
_saveDataId = saveDataId;
}
public override bool IsSaveDataFileSystemCacheEnabled()
{
throw new NotImplementedException();
return true;
}
public override Result RollbackOnlyModified()
{
throw new NotImplementedException();
using ScopedLock<SdkMutex> scopedLock = ScopedLock.Lock(ref _mutex);
return _saveFsDriver.RollbackOnlyModified().Ret();
}
public IInternalStorageFileSystem GetInternalStorageFileSystem()
{
throw new NotImplementedException();
return _saveFsDriver;
}
}

View file

@ -9,7 +9,7 @@ namespace LibHac.FsSystem;
/// Wraps an <see cref="IFile"/>, converting its returned <see cref="Result"/>s
/// to save-data-specific <see cref="Result"/>s.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class SaveDataResultConvertFile : IResultConvertFile
{
private bool _isReconstructible;
@ -29,13 +29,13 @@ public class SaveDataResultConvertFile : IResultConvertFile
/// Wraps an <see cref="IDirectory"/>, converting its returned <see cref="Result"/>s
/// to save-data-specific <see cref="Result"/>s.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class SaveDataResultConvertDirectory : IResultConvertDirectory
{
private bool _isReconstructible;
public SaveDataResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory, bool isReconstructible) : base(
ref baseDirectory)
public SaveDataResultConvertDirectory(ref UniqueRef<IDirectory> baseDirectory, bool isReconstructible)
: base(ref baseDirectory)
{
_isReconstructible = isReconstructible;
}
@ -50,7 +50,7 @@ public class SaveDataResultConvertDirectory : IResultConvertDirectory
/// Wraps an <see cref="ISaveDataFileSystem"/>, converting its returned <see cref="Result"/>s
/// to save-data-specific <see cref="Result"/>s.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class SaveDataResultConvertFileSystem : IResultConvertFileSystem<ISaveDataFileSystem>
{
private bool _isReconstructible;

View file

@ -112,7 +112,7 @@ public class SwitchStorage : IStorage
/// the provided <see cref="Region"/> will be forwarded to one <see cref="IStorage"/>, and requests outside
/// will be forwarded to the other.
/// </summary>
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
/// <remarks>Based on nnSdk 17.5.0 (FS 17.0.0)</remarks>
public class RegionSwitchStorage : IStorage
{
public struct Region