Skip to content

Commit

Permalink
Add SwitchStorage and RegionSwitchStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
Thealexbarney committed Apr 20, 2022
1 parent f8b9c35 commit 99ad308
Show file tree
Hide file tree
Showing 3 changed files with 330 additions and 0 deletions.
3 changes: 3 additions & 0 deletions build/CodeGen/results.csv
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions src/LibHac/Fs/ResultFs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
321 changes: 321 additions & 0 deletions src/LibHac/FsSystem/SwitchStorage.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
}

0 comments on commit 99ad308

Please sign in to comment.