diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index e390c181..bba44f13 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -1064,6 +1064,9 @@ Module,DescriptionStart,DescriptionEnd,Flags,Namespace,Name,Summary 2,6386,,,,UnsupportedSetSizeForZeroBitmapHashStorageFile, 2,6387,,,,UnsupportedWriteForCompressedStorage, 2,6388,,,,UnsupportedOperateRangeForCompressedStorage, +2,6395,,,,UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem, +2,6396,,,,UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem, +2,6397,,,,UnsupportedOperateRangeForRegionSwitchStorage, 2,6400,6449,,,PermissionDenied, 2,6403,,,,PermissionDeniedForCreateHostFileSystem,Returned when opening a host FS on a retail device. diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index 50943f44..047d884b 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -1952,6 +1952,12 @@ public static class ResultFs public static Result.Base UnsupportedWriteForCompressedStorage => new Result.Base(ModuleFs, 6387); /// Error code: 2002-6388; Inner value: 0x31e802 public static Result.Base UnsupportedOperateRangeForCompressedStorage => new Result.Base(ModuleFs, 6388); + /// Error code: 2002-6395; Inner value: 0x31f602 + public static Result.Base UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem => new Result.Base(ModuleFs, 6395); + /// Error code: 2002-6396; Inner value: 0x31f802 + public static Result.Base UnsupportedRollbackOnlyModifiedForDirectorySaveDataFileSystem => new Result.Base(ModuleFs, 6396); + /// Error code: 2002-6397; Inner value: 0x31fa02 + public static Result.Base UnsupportedOperateRangeForRegionSwitchStorage => new Result.Base(ModuleFs, 6397); /// Error code: 2002-6400; Range: 6400-6449; Inner value: 0x320002 public static Result.Base PermissionDenied { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6400, 6449); } diff --git a/src/LibHac/FsSystem/SwitchStorage.cs b/src/LibHac/FsSystem/SwitchStorage.cs new file mode 100644 index 00000000..342ee012 --- /dev/null +++ b/src/LibHac/FsSystem/SwitchStorage.cs @@ -0,0 +1,321 @@ +using System; +using System.Runtime.CompilerServices; +using LibHac.Common; +using LibHac.Fs; + +namespace LibHac.FsSystem; + +/// +/// An that will switch between forwarding requests to one of two different base +/// s. On each request the provided storage selection function will be called and the request +/// will be forwarded to the appropriate based on the return value. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class SwitchStorage : IStorage +{ + private SharedRef _trueStorage; + private SharedRef _falseStorage; + private Func _storageSelectionFunction; + + public SwitchStorage(in SharedRef trueStorage, in SharedRef falseStorage, + Func storageSelectionFunction) + { + _trueStorage = SharedRef.CreateCopy(in trueStorage); + _falseStorage = SharedRef.CreateCopy(in falseStorage); + _storageSelectionFunction = storageSelectionFunction; + } + + public override void Dispose() + { + _trueStorage.Destroy(); + _falseStorage.Destroy(); + + base.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IStorage SelectStorage() + { + return (_storageSelectionFunction() ? _trueStorage : _falseStorage).Get; + } + + public override Result Read(long offset, Span destination) + { + Result rc = SelectStorage().Read(offset, destination); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result Write(long offset, ReadOnlySpan source) + { + Result rc = SelectStorage().Write(offset, source); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result Flush() + { + Result rc = SelectStorage().Flush(); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result GetSize(out long size) + { + Result rc = SelectStorage().GetSize(out size); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result SetSize(long size) + { + Result rc = SelectStorage().SetSize(size); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, + ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + { + Result rc = _trueStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + rc = _falseStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + case OperationId.QueryRange: + { + Result rc = SelectStorage().OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + default: + return ResultFs.UnsupportedOperateRangeForSwitchStorage.Log(); + } + } +} + +/// +/// Takes a and two base s upon construction. Requests inside +/// the provided will be forwarded to one , and requests outside +/// will be forwarded to the other. +/// +/// Based on FS 14.1.0 (nnSdk 14.3.0) +public class RegionSwitchStorage : IStorage +{ + public struct Region + { + public long Offset; + public long Size; + } + + private SharedRef _insideRegionStorage; + private SharedRef _outsideRegionStorage; + private Region _region; + + public RegionSwitchStorage(in SharedRef insideRegionStorage, + in SharedRef outsideRegionStorage, Region region) + { + _insideRegionStorage = SharedRef.CreateCopy(in insideRegionStorage); + _outsideRegionStorage = SharedRef.CreateCopy(in outsideRegionStorage); + _region = region; + } + + public override void Dispose() + { + _insideRegionStorage.Destroy(); + _outsideRegionStorage.Destroy(); + + base.Dispose(); + } + + /// + /// Checks if the requested range is inside or outside the . + /// + /// The size past the start of the range until entering or exiting the . + /// The offset of the range to check. + /// The size of the range to check. + /// the start of the range is inside the ; + /// otherwise . + private bool CheckRegions(out long currentSize, long offset, long size) + { + if (_region.Offset > offset) + { + // The requested start offset is before the region's start offset. + // Check if the requested end offset is inside the region. + if (offset + size < _region.Offset) + { + // The request is completely outside the region. + currentSize = size; + } + else + { + // The request ends inside the region. Calculate the length of the request outside the region. + currentSize = _region.Offset - offset; + } + + return false; + } + + if (_region.Offset + _region.Size > offset) + { + // The requested start offset is inside the region. + // Check if the requested end offset is also inside the region. + if (offset + size < _region.Offset + _region.Size) + { + // The request is completely within the region. + currentSize = size; + } + else + { + // The request ends outside the region. Calculate the length of the request inside the region. + currentSize = _region.Offset + _region.Size - offset; + } + + return true; + } + + // The request starts after the end of the region. + currentSize = size; + return false; + } + + public override Result Read(long offset, Span destination) + { + int bytesRead = 0; + while (bytesRead < destination.Length) + { + if (CheckRegions(out long currentSize, offset + bytesRead, destination.Length - bytesRead)) + { + Result rc = _insideRegionStorage.Get.Read(offset + bytesRead, + destination.Slice(bytesRead, (int)currentSize)); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + Result rc = _outsideRegionStorage.Get.Read(offset + bytesRead, + destination.Slice(bytesRead, (int)currentSize)); + if (rc.IsFailure()) return rc.Miss(); + } + + bytesRead += (int)currentSize; + } + + return Result.Success; + } + + public override Result Write(long offset, ReadOnlySpan source) + { + int bytesWritten = 0; + while (bytesWritten < source.Length) + { + if (CheckRegions(out long currentSize, offset + bytesWritten, source.Length - bytesWritten)) + { + Result rc = _insideRegionStorage.Get.Write(offset + bytesWritten, + source.Slice(bytesWritten, (int)currentSize)); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + Result rc = _outsideRegionStorage.Get.Write(offset + bytesWritten, + source.Slice(bytesWritten, (int)currentSize)); + if (rc.IsFailure()) return rc.Miss(); + } + + bytesWritten += (int)currentSize; + } + + return Result.Success; + } + + public override Result Flush() + { + Result rc = _insideRegionStorage.Get.Flush(); + if (rc.IsFailure()) return rc.Miss(); + + rc = _outsideRegionStorage.Get.Flush(); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result GetSize(out long size) + { + Result rc = _insideRegionStorage.Get.GetSize(out size); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result SetSize(long size) + { + Result rc = _insideRegionStorage.Get.SetSize(size); + if (rc.IsFailure()) return rc.Miss(); + + rc = _outsideRegionStorage.Get.SetSize(size); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + + public override Result OperateRange(Span outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan inBuffer) + { + switch (operationId) + { + case OperationId.InvalidateCache: + { + Result rc = _insideRegionStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + rc = _outsideRegionStorage.Get.OperateRange(outBuffer, operationId, offset, size, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + + return Result.Success; + } + case OperationId.QueryRange: + { + Unsafe.SkipInit(out QueryRangeInfo mergedInfo); + mergedInfo.Clear(); + + long bytesProcessed = 0; + while (bytesProcessed < size) + { + Unsafe.SkipInit(out QueryRangeInfo currentInfo); + + if (CheckRegions(out long currentSize, offset + bytesProcessed, size - bytesProcessed)) + { + Result rc = _insideRegionStorage.Get.OperateRange(SpanHelpers.AsByteSpan(ref currentInfo), + operationId, offset + bytesProcessed, currentSize, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + } + else + { + Result rc = _outsideRegionStorage.Get.OperateRange(SpanHelpers.AsByteSpan(ref currentInfo), + operationId, offset + bytesProcessed, currentSize, inBuffer); + if (rc.IsFailure()) return rc.Miss(); + } + + mergedInfo.Merge(in currentInfo); + bytesProcessed += currentSize; + } + + SpanHelpers.AsByteSpan(ref mergedInfo).CopyTo(outBuffer); + return Result.Success; + } + default: + return ResultFs.UnsupportedOperateRangeForRegionSwitchStorage.Log(); + } + } +} \ No newline at end of file