mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add SwitchStorage and RegionSwitchStorage
This commit is contained in:
parent
f8b9c3557e
commit
99ad308b84
3 changed files with 330 additions and 0 deletions
|
@ -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.
|
||||
|
|
|
|
@ -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); }
|
||||
|
|
321
src/LibHac/FsSystem/SwitchStorage.cs
Normal file
321
src/LibHac/FsSystem/SwitchStorage.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue