Add SwitchStorage and RegionSwitchStorage

This commit is contained in:
Alex Barney 2022-04-19 22:31:34 -07:00
parent f8b9c3557e
commit 99ad308b84
3 changed files with 330 additions and 0 deletions

View file

@ -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.

1 Module DescriptionStart DescriptionEnd Flags Namespace Name Summary
1064 3 506 503 OutOfAddressSpace InvalidTransferMemoryState
1065 3 510 504 SessionClosedForReceive InvalidTransferMemorySize
1066 3 511 505 SessionClosedForReply OutOfTransferMemory
1067 3 506 OutOfAddressSpace
1068 3 510 SessionClosedForReceive
1069 3 511 SessionClosedForReply
1070 3 512 ReceiveListBroken
1071 4 9 InvalidHandle
1072 4 2001 InvalidArgument

View file

@ -1952,6 +1952,12 @@ public static class ResultFs
public static Result.Base UnsupportedWriteForCompressedStorage => new Result.Base(ModuleFs, 6387);
/// <summary>Error code: 2002-6388; Inner value: 0x31e802</summary>
public static Result.Base UnsupportedOperateRangeForCompressedStorage => new Result.Base(ModuleFs, 6388);
/// <summary>Error code: 2002-6395; Inner value: 0x31f602</summary>
public static Result.Base UnsupportedRollbackOnlyModifiedForApplicationTemporaryFileSystem => new Result.Base(ModuleFs, 6395);
/// <summary>Error code: 2002-6396; Inner value: 0x31f802</summary>
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-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

@ -0,0 +1,321 @@
using System;
using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.FsSystem;
/// <summary>
/// An <see cref="IStorage"/> that will switch between forwarding requests to one of two different base
/// <see cref="IStorage"/>s. On each request the provided storage selection function will be called and the request
/// will be forwarded to the appropriate <see cref="IStorage"/> based on the return value.
/// </summary>
/// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class SwitchStorage : IStorage
{
private SharedRef<IStorage> _trueStorage;
private SharedRef<IStorage> _falseStorage;
private Func<bool> _storageSelectionFunction;
public SwitchStorage(in SharedRef<IStorage> trueStorage, in SharedRef<IStorage> falseStorage,
Func<bool> storageSelectionFunction)
{
_trueStorage = SharedRef<IStorage>.CreateCopy(in trueStorage);
_falseStorage = SharedRef<IStorage>.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<byte> destination)
{
Result rc = SelectStorage().Read(offset, destination);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public override Result Write(long offset, ReadOnlySpan<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size,
ReadOnlySpan<byte> 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();
}
}
}
/// <summary>
/// Takes a <see cref="Region"/> and two base <see cref="IStorage"/>s upon construction. Requests inside
/// 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 FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class RegionSwitchStorage : IStorage
{
public struct Region
{
public long Offset;
public long Size;
}
private SharedRef<IStorage> _insideRegionStorage;
private SharedRef<IStorage> _outsideRegionStorage;
private Region _region;
public RegionSwitchStorage(in SharedRef<IStorage> insideRegionStorage,
in SharedRef<IStorage> outsideRegionStorage, Region region)
{
_insideRegionStorage = SharedRef<IStorage>.CreateCopy(in insideRegionStorage);
_outsideRegionStorage = SharedRef<IStorage>.CreateCopy(in outsideRegionStorage);
_region = region;
}
public override void Dispose()
{
_insideRegionStorage.Destroy();
_outsideRegionStorage.Destroy();
base.Dispose();
}
/// <summary>
/// Checks if the requested range is inside or outside the <see cref="Region"/>.
/// </summary>
/// <param name="currentSize">The size past the start of the range until entering or exiting the <see cref="Region"/>.</param>
/// <param name="offset">The offset of the range to check.</param>
/// <param name="size">The size of the range to check.</param>
/// <returns><see langword="true"/> the start of the range is inside the <see cref="Region"/>;
/// otherwise <see langword="false"/>.</returns>
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<byte> 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<byte> 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<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> 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();
}
}
}