Update IStorage range check functions for 14.0.0

This commit is contained in:
Alex Barney 2022-04-14 14:18:24 -07:00
parent 7dd137da6a
commit fc3fb188c7
12 changed files with 236 additions and 124 deletions

View file

@ -11,7 +11,7 @@ namespace LibHac.Fs;
/// <summary> /// <summary>
/// Allows interacting with an <see cref="IFile"/> via an <see cref="IStorage"/> interface. /// Allows interacting with an <see cref="IFile"/> via an <see cref="IStorage"/> interface.
/// </summary> /// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class FileStorage : IStorage public class FileStorage : IStorage
{ {
private const long InvalidSize = -1; private const long InvalidSize = -1;
@ -60,8 +60,8 @@ public class FileStorage : IStorage
Result rc = UpdateSize(); Result rc = UpdateSize();
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, _fileSize)) rc = CheckAccessRange(offset, destination.Length, _fileSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _baseFile.Read(out _, offset, destination, ReadOption.None); return _baseFile.Read(out _, offset, destination, ReadOption.None);
} }
@ -74,8 +74,8 @@ public class FileStorage : IStorage
Result rc = UpdateSize(); Result rc = UpdateSize();
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, _fileSize)) rc = CheckAccessRange(offset, source.Length, _fileSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _baseFile.Write(offset, source, WriteOption.None); return _baseFile.Write(offset, source, WriteOption.None);
} }
@ -104,35 +104,37 @@ public class FileStorage : IStorage
public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer) public override Result OperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
{ {
if (operationId == OperationId.InvalidateCache) switch (operationId)
{ {
Result rc = _baseFile.OperateRange(OperationId.InvalidateCache, offset, size); case OperationId.InvalidateCache:
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
if (operationId == OperationId.QueryRange)
{
if (size == 0)
{ {
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>()) Result rc = _baseFile.OperateRange(OperationId.InvalidateCache, offset, size);
return ResultFs.InvalidSize.Log(); if (rc.IsFailure()) return rc.Miss();
SpanHelpers.AsStruct<QueryRangeInfo>(outBuffer).Clear();
return Result.Success; return Result.Success;
} }
case OperationId.QueryRange:
{
if (size == 0)
{
if (outBuffer.Length != Unsafe.SizeOf<QueryRangeInfo>())
return ResultFs.InvalidSize.Log();
Result rc = UpdateSize(); SpanHelpers.AsStruct<QueryRangeInfo>(outBuffer).Clear();
if (rc.IsFailure()) return rc.Miss(); return Result.Success;
}
if (!CheckOffsetAndSize(offset, size)) Result rc = UpdateSize();
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer); rc = CheckOffsetAndSize(offset, size);
if (rc.IsFailure()) return rc.Miss();
return _baseFile.OperateRange(outBuffer, operationId, offset, size, inBuffer);
}
default:
return ResultFs.UnsupportedOperateRangeForFileStorage.Log();
} }
return ResultFs.UnsupportedOperateRangeForFileStorage.Log();
} }
private Result UpdateSize() private Result UpdateSize()
@ -153,7 +155,7 @@ public class FileStorage : IStorage
/// <see cref="IStorage"/> interface. The opened file will automatically be closed when the /// <see cref="IStorage"/> interface. The opened file will automatically be closed when the
/// <see cref="FileStorageBasedFileSystem"/> is disposed. /// <see cref="FileStorageBasedFileSystem"/> is disposed.
/// </summary> /// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class FileStorageBasedFileSystem : FileStorage public class FileStorageBasedFileSystem : FileStorage
{ {
private SharedRef<IFileSystem> _baseFileSystem; private SharedRef<IFileSystem> _baseFileSystem;
@ -196,7 +198,7 @@ public class FileStorageBasedFileSystem : FileStorage
/// Provides an <see cref="IStorage"/> interface for interacting with an opened file from a mounted file system. /// Provides an <see cref="IStorage"/> interface for interacting with an opened file from a mounted file system.
/// The caller may choose whether or not the file will be closed when the <see cref="FileHandleStorage"/> is disposed. /// The caller may choose whether or not the file will be closed when the <see cref="FileHandleStorage"/> is disposed.
/// </summary> /// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class FileHandleStorage : IStorage public class FileHandleStorage : IStorage
{ {
private const long InvalidSize = -1; private const long InvalidSize = -1;
@ -230,7 +232,7 @@ public class FileHandleStorage : IStorage
_handle = handle; _handle = handle;
_closeFile = closeFile; _closeFile = closeFile;
_size = InvalidSize; _size = InvalidSize;
_mutex.Initialize(); _mutex = new SdkMutexType();
} }
public override void Dispose() public override void Dispose()
@ -255,8 +257,8 @@ public class FileHandleStorage : IStorage
Result rc = UpdateSize(); Result rc = UpdateSize();
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, _size)) rc = CheckAccessRange(offset, destination.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _fsClient.ReadFile(_handle, offset, destination, ReadOption.None); return _fsClient.ReadFile(_handle, offset, destination, ReadOption.None);
} }
@ -271,8 +273,8 @@ public class FileHandleStorage : IStorage
Result rc = UpdateSize(); Result rc = UpdateSize();
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, _size)) rc = CheckAccessRange(offset, source.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _fsClient.WriteFile(_handle, offset, source, WriteOption.None); return _fsClient.WriteFile(_handle, offset, source, WriteOption.None);
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LibHac.Util;
namespace LibHac.Fs; namespace LibHac.Fs;
@ -7,12 +8,7 @@ namespace LibHac.Fs;
/// <summary> /// <summary>
/// Provides an interface for reading and writing a sequence of bytes. /// Provides an interface for reading and writing a sequence of bytes.
/// </summary> /// </summary>
/// <remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
/// The official IStorage makes the <c>Read</c> etc. methods abstract and doesn't
/// have <c>DoRead</c> etc. methods. We're using them here so we can make sure
/// the object isn't disposed before calling the method implementation.
/// </remarks>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks>
public abstract class IStorage : IDisposable public abstract class IStorage : IDisposable
{ {
public virtual void Dispose() { } public virtual void Dispose() { }
@ -78,20 +74,70 @@ public abstract class IStorage : IDisposable
return OperateRange(Span<byte>.Empty, operationId, offset, size, ReadOnlySpan<byte>.Empty); return OperateRange(Span<byte>.Empty, operationId, offset, size, ReadOnlySpan<byte>.Empty);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static Result CheckAccessRange(long offset, long size, long totalSize)
public static bool CheckAccessRange(long offset, long size, long totalSize)
{ {
return offset >= 0 && if (offset < 0)
size >= 0 && return ResultFs.InvalidOffset.Log();
size <= totalSize &&
offset <= totalSize - size; if (size < 0)
return ResultFs.InvalidSize.Log();
if (!IntUtil.CanAddWithoutOverflow(offset, size))
return ResultFs.OutOfRange.Log();
if (size + offset > totalSize)
return ResultFs.OutOfRange.Log();
return Result.Success;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static Result CheckAccessRange(long offset, ulong size, long totalSize)
public static bool CheckOffsetAndSize(long offset, long size)
{ {
return offset >= 0 && Result rc = CheckAccessRange(offset, unchecked((long)size), totalSize);
size >= 0 && if (rc.IsFailure()) return rc.Miss();
offset <= offset + size;
return Result.Success;
}
public static Result CheckOffsetAndSize(long offset, long size)
{
if (offset < 0)
return ResultFs.InvalidOffset.Log();
if (size < 0)
return ResultFs.InvalidSize.Log();
if (!IntUtil.CanAddWithoutOverflow(offset, size))
return ResultFs.OutOfRange.Log();
return Result.Success;
}
public static Result CheckOffsetAndSize(long offset, ulong size)
{
Result rc = CheckOffsetAndSize(offset, unchecked((long)size));
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
}
public static Result CheckOffsetAndSizeWithResult(long offset, long size, Result resultOnFailure)
{
Result rc = CheckOffsetAndSize(offset, size);
if (rc.IsFailure())
return resultOnFailure.Log();
return Result.Success;
}
public static Result CheckOffsetAndSizeWithResult(long offset, ulong size, Result resultOnFailure)
{
Result rc = CheckOffsetAndSize(offset, size);
if (rc.IsFailure())
return resultOnFailure.Log();
return Result.Success;
} }
} }

View file

@ -7,7 +7,7 @@ namespace LibHac.Fs;
/// <summary> /// <summary>
/// Allows interacting with a <see cref="byte"/> array via the <see cref="IStorage"/> interface. /// Allows interacting with a <see cref="byte"/> array via the <see cref="IStorage"/> interface.
/// </summary> /// </summary>
/// <remarks>Based on FS 13.1.0 (nnSdk 13.4.0)</remarks> /// <remarks>Based on FS 14.1.0 (nnSdk 14.3.0)</remarks>
public class MemoryStorage : IStorage public class MemoryStorage : IStorage
{ {
private byte[] _storageBuffer; private byte[] _storageBuffer;
@ -22,8 +22,8 @@ public class MemoryStorage : IStorage
if (destination.Length == 0) if (destination.Length == 0)
return Result.Success; return Result.Success;
if (!CheckAccessRange(offset, destination.Length, _storageBuffer.Length)) Result rc = CheckAccessRange(offset, destination.Length, _storageBuffer.Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
_storageBuffer.AsSpan((int)offset, destination.Length).CopyTo(destination); _storageBuffer.AsSpan((int)offset, destination.Length).CopyTo(destination);
@ -35,8 +35,8 @@ public class MemoryStorage : IStorage
if (source.Length == 0) if (source.Length == 0)
return Result.Success; return Result.Success;
if (!CheckAccessRange(offset, source.Length, _storageBuffer.Length)) Result rc = CheckAccessRange(offset, source.Length, _storageBuffer.Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
source.CopyTo(_storageBuffer.AsSpan((int)offset)); source.CopyTo(_storageBuffer.AsSpan((int)offset));

View file

@ -8,16 +8,17 @@ namespace LibHac.Fs;
/// Presents a subsection of a base IStorage as a new IStorage. /// Presents a subsection of a base IStorage as a new IStorage.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// A SubStorage presents a sub-range of an IStorage as a separate IStorage. /// <para>A SubStorage presents a sub-range of an IStorage as a separate IStorage.</para>
/// ///
/// The SubStorage doesn't check if the offset and size provided are actually in the base storage. /// <para>The SubStorage doesn't check if the offset and size provided are actually in the base storage.
/// GetSize will return the size given to the SubStorage at initialization and will not query /// GetSize will return the size given to the SubStorage at initialization and will not query
/// the base storage's size. /// the base storage's size.</para>
/// ///
/// A SubStorage is non-resizable by default. <see cref="SetResizable"/> may be used to mark /// <para>A SubStorage is non-resizable by default. <see cref="SetResizable"/> may be used to mark
/// the SubStorage as resizable. The SubStorage may only be resized if the end of the SubStorage /// the SubStorage as resizable. The SubStorage may only be resized if the end of the SubStorage
/// is located at the end of the base storage. When resizing the SubStorage, the base storage /// is located at the end of the base storage. When resizing the SubStorage, the base storage
/// will be resized to the appropriate length. /// will be resized to the appropriate length.</para>
/// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para>
/// </remarks> /// </remarks>
public class SubStorage : IStorage public class SubStorage : IStorage
{ {
@ -158,10 +159,13 @@ public class SubStorage : IStorage
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (destination.Length == 0) return Result.Success; if (destination.Length == 0) return Result.Success;
if (!CheckAccessRange(offset, destination.Length, _size)) Result rc = CheckAccessRange(offset, destination.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return BaseStorage.Read(_offset + offset, destination); rc = BaseStorage.Read(_offset + offset, destination);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public override Result Write(long offset, ReadOnlySpan<byte> source) public override Result Write(long offset, ReadOnlySpan<byte> source)
@ -169,26 +173,34 @@ public class SubStorage : IStorage
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (source.Length == 0) return Result.Success; if (source.Length == 0) return Result.Success;
if (!CheckAccessRange(offset, source.Length, _size)) Result rc = CheckAccessRange(offset, source.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return BaseStorage.Write(_offset + offset, source); rc = BaseStorage.Write(_offset + offset, source);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public override Result Flush() public override Result Flush()
{ {
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
return BaseStorage.Flush(); Result rc = BaseStorage.Flush();
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public override Result SetSize(long size) public override Result SetSize(long size)
{ {
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log(); if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log();
if (!CheckOffsetAndSize(_offset, size)) return ResultFs.InvalidSize.Log();
Result rc = BaseStorage.GetSize(out long currentSize); Result rc = CheckOffsetAndSize(_offset, size);
if (rc.IsFailure()) return rc.Miss();
rc = BaseStorage.GetSize(out long currentSize);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
if (currentSize != _offset + _size) if (currentSize != _offset + _size)
@ -222,7 +234,9 @@ public class SubStorage : IStorage
if (operationId != OperationId.InvalidateCache) if (operationId != OperationId.InvalidateCache)
{ {
if (size == 0) return Result.Success; if (size == 0) return Result.Success;
if (!CheckOffsetAndSize(_offset, size)) return ResultFs.OutOfRange.Log();
Result rc = CheckOffsetAndSize(_offset, size);
if (rc.IsFailure()) return rc.Miss();
} }
return BaseStorage.OperateRange(outBuffer, operationId, _offset + offset, size, inBuffer); return BaseStorage.OperateRange(outBuffer, operationId, _offset + offset, size, inBuffer);

View file

@ -112,10 +112,13 @@ public struct ValueSubStorage : IDisposable
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (destination.Length == 0) return Result.Success; if (destination.Length == 0) return Result.Success;
if (!IStorage.CheckAccessRange(offset, destination.Length, _size)) Result rc = IStorage.CheckAccessRange(offset, destination.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _baseStorage.Read(_offset + offset, destination); rc = _baseStorage.Read(_offset + offset, destination);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public readonly Result Write(long offset, ReadOnlySpan<byte> source) public readonly Result Write(long offset, ReadOnlySpan<byte> source)
@ -123,26 +126,34 @@ public struct ValueSubStorage : IDisposable
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (source.Length == 0) return Result.Success; if (source.Length == 0) return Result.Success;
if (!IStorage.CheckAccessRange(offset, source.Length, _size)) Result rc = IStorage.CheckAccessRange(offset, source.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return _baseStorage.Write(_offset + offset, source); rc = _baseStorage.Write(_offset + offset, source);
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public readonly Result Flush() public readonly Result Flush()
{ {
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
return _baseStorage.Flush(); Result rc = _baseStorage.Flush();
if (rc.IsFailure()) return rc.Miss();
return Result.Success;
} }
public Result SetSize(long size) public Result SetSize(long size)
{ {
if (!IsValid()) return ResultFs.NotInitialized.Log(); if (!IsValid()) return ResultFs.NotInitialized.Log();
if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log(); if (!_isResizable) return ResultFs.UnsupportedSetSizeForNotResizableSubStorage.Log();
if (!IStorage.CheckOffsetAndSize(_offset, size)) return ResultFs.InvalidSize.Log();
Result rc = _baseStorage.GetSize(out long currentSize); Result rc = IStorage.CheckOffsetAndSize(_offset, size);
if (rc.IsFailure()) return rc;
rc = _baseStorage.GetSize(out long currentSize);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
if (currentSize != _offset + _size) if (currentSize != _offset + _size)
@ -181,7 +192,9 @@ public struct ValueSubStorage : IDisposable
if (operationId != OperationId.InvalidateCache) if (operationId != OperationId.InvalidateCache)
{ {
if (size == 0) return Result.Success; if (size == 0) return Result.Success;
if (!IStorage.CheckOffsetAndSize(_offset, size)) return ResultFs.OutOfRange.Log();
Result rc = IStorage.CheckOffsetAndSize(_offset, size);
if (rc.IsFailure()) return rc.Miss();
} }
return _baseStorage.OperateRange(outBuffer, operationId, _offset + offset, size, inBuffer); return _baseStorage.OperateRange(outBuffer, operationId, _offset + offset, size, inBuffer);

View file

@ -23,7 +23,7 @@ public interface IAlignmentMatchingStorageSize { }
/// <remarks><para>This class uses a work buffer on the stack to avoid allocations. Because of this the data alignment /// <remarks><para>This class uses a work buffer on the stack to avoid allocations. Because of this the data alignment
/// must be kept small; no larger than 0x200. The <see cref="AlignmentMatchingStoragePooledBuffer{TBufferAlignment}"/> class /// must be kept small; no larger than 0x200. The <see cref="AlignmentMatchingStoragePooledBuffer{TBufferAlignment}"/> class
/// should be used for data alignment sizes larger than this.</para> /// should be used for data alignment sizes larger than this.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks> /// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para></remarks>
[SkipLocalsInit] [SkipLocalsInit]
public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStorage public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStorage
where TDataAlignment : struct, IAlignmentMatchingStorageSize where TDataAlignment : struct, IAlignmentMatchingStorageSize
@ -80,8 +80,8 @@ public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStora
Result rc = GetSize(out long totalSize); Result rc = GetSize(out long totalSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, totalSize)) rc = CheckAccessRange(offset, destination.Length, totalSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return AlignmentMatchingStorageImpl.Read(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, destination); return AlignmentMatchingStorageImpl.Read(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, destination);
} }
@ -96,8 +96,8 @@ public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStora
Result rc = GetSize(out long totalSize); Result rc = GetSize(out long totalSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, totalSize)) rc = CheckAccessRange(offset, source.Length, totalSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
return AlignmentMatchingStorageImpl.Write(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, source); return AlignmentMatchingStorageImpl.Write(_baseStorage, workBuffer, DataAlign, BufferAlign, offset, source);
} }
@ -146,8 +146,8 @@ public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStora
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size)) rc = CheckOffsetAndSize(offset, size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
long validSize = Math.Min(size, baseStorageSize - offset); long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, DataAlign); long alignedOffset = Alignment.AlignDownPow2(offset, DataAlign);
@ -166,7 +166,7 @@ public class AlignmentMatchingStorage<TDataAlignment, TBufferAlignment> : IStora
/// the beginning or end of the requested range. For data alignment sizes of 0x200 or smaller /// the beginning or end of the requested range. For data alignment sizes of 0x200 or smaller
/// <see cref="AlignmentMatchingStorage{TDataAlignment,TBufferAlignment}"/> should be used instead /// <see cref="AlignmentMatchingStorage{TDataAlignment,TBufferAlignment}"/> should be used instead
/// to avoid these allocations.</para> /// to avoid these allocations.</para>
/// <para>Based on FS 13.1.0 (nnSdk 13.4.0)</para></remarks> /// <para>Based on FS 14.1.0 (nnSdk 14.3.0)</para></remarks>
public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage
where TBufferAlignment : struct, IAlignmentMatchingStorageSize where TBufferAlignment : struct, IAlignmentMatchingStorageSize
{ {
@ -220,8 +220,8 @@ public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, baseStorageSize)) rc = CheckAccessRange(offset, destination.Length, baseStorageSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
using var pooledBuffer = new PooledBuffer(); using var pooledBuffer = new PooledBuffer();
pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment); pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment);
@ -238,8 +238,8 @@ public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, baseStorageSize)) rc = CheckAccessRange(offset, source.Length, baseStorageSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
using var pooledBuffer = new PooledBuffer(); using var pooledBuffer = new PooledBuffer();
pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment); pooledBuffer.AllocateParticularlyLarge((int)_dataAlignment, (int)_dataAlignment);
@ -292,8 +292,8 @@ public class AlignmentMatchingStoragePooledBuffer<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size)) rc = CheckOffsetAndSize(offset, size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
long validSize = Math.Min(size, baseStorageSize - offset); long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment); long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment);
@ -362,8 +362,8 @@ public class AlignmentMatchingStorageInBulkRead<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, destination.Length, baseStorageSize)) rc = CheckAccessRange(offset, destination.Length, baseStorageSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
// Calculate the aligned offsets of the requested region. // Calculate the aligned offsets of the requested region.
long offsetEnd = offset + destination.Length; long offsetEnd = offset + destination.Length;
@ -453,8 +453,8 @@ public class AlignmentMatchingStorageInBulkRead<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckAccessRange(offset, source.Length, baseStorageSize)) rc = CheckAccessRange(offset, source.Length, baseStorageSize);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
using var pooledBuffer = new PooledBuffer((int)_dataAlignment, (int)_dataAlignment); using var pooledBuffer = new PooledBuffer((int)_dataAlignment, (int)_dataAlignment);
@ -505,8 +505,8 @@ public class AlignmentMatchingStorageInBulkRead<TBufferAlignment> : IStorage
Result rc = GetSize(out long baseStorageSize); Result rc = GetSize(out long baseStorageSize);
if (rc.IsFailure()) return rc.Miss(); if (rc.IsFailure()) return rc.Miss();
if (!CheckOffsetAndSize(offset, size)) rc = CheckOffsetAndSize(offset, size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
long validSize = Math.Min(size, baseStorageSize - offset); long validSize = Math.Min(size, baseStorageSize - offset);
long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment); long alignedOffset = Alignment.AlignDownPow2(offset, _dataAlignment);

View file

@ -28,10 +28,10 @@ public class AesCbcStorage : SectorStorage
public override Result Read(long offset, Span<byte> destination) public override Result Read(long offset, Span<byte> destination)
{ {
if (!CheckAccessRange(offset, destination.Length, _size)) Result rc = CheckAccessRange(offset, destination.Length, _size);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
Result rc = base.Read(offset, destination); rc = base.Read(offset, destination);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
rc = GetDecryptor(out ICipher cipher, offset); rc = GetDecryptor(out ICipher cipher, offset);

View file

@ -39,8 +39,8 @@ public class CachedStorage : IStorage
long inOffset = offset; long inOffset = offset;
int outOffset = 0; int outOffset = 0;
if (!CheckAccessRange(offset, destination.Length, Length)) Result rc = CheckAccessRange(offset, destination.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
lock (Blocks) lock (Blocks)
{ {
@ -69,8 +69,8 @@ public class CachedStorage : IStorage
long inOffset = offset; long inOffset = offset;
int outOffset = 0; int outOffset = 0;
if (!CheckAccessRange(offset, source.Length, Length)) Result rc = CheckAccessRange(offset, source.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
lock (Blocks) lock (Blocks)
{ {

View file

@ -34,8 +34,8 @@ public class ConcatenationStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = destination.Length; int remaining = destination.Length;
if (!CheckAccessRange(offset, destination.Length, Length)) Result rc = CheckAccessRange(offset, destination.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
int sourceIndex = FindSource(inPos); int sourceIndex = FindSource(inPos);
@ -47,7 +47,7 @@ public class ConcatenationStorage : IStorage
int bytesToRead = (int)Math.Min(entryRemain, remaining); int bytesToRead = (int)Math.Min(entryRemain, remaining);
Result rc = entry.Storage.Read(entryPos, destination.Slice(outPos, bytesToRead)); rc = entry.Storage.Read(entryPos, destination.Slice(outPos, bytesToRead));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToRead; outPos += bytesToRead;
@ -65,8 +65,8 @@ public class ConcatenationStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = source.Length; int remaining = source.Length;
if (!CheckAccessRange(offset, source.Length, Length)) Result rc = CheckAccessRange(offset, source.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
int sourceIndex = FindSource(inPos); int sourceIndex = FindSource(inPos);
@ -78,7 +78,7 @@ public class ConcatenationStorage : IStorage
int bytesToWrite = (int)Math.Min(entryRemain, remaining); int bytesToWrite = (int)Math.Min(entryRemain, remaining);
Result rc = entry.Storage.Write(entryPos, source.Slice(outPos, bytesToWrite)); rc = entry.Storage.Write(entryPos, source.Slice(outPos, bytesToWrite));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToWrite; outPos += bytesToWrite;

View file

@ -33,8 +33,8 @@ public class DuplexStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = destination.Length; int remaining = destination.Length;
if (!CheckAccessRange(offset, destination.Length, Length)) Result rc = CheckAccessRange(offset, destination.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
while (remaining > 0) while (remaining > 0)
{ {
@ -45,7 +45,7 @@ public class DuplexStorage : IStorage
IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA;
Result rc = data.Read(inPos, destination.Slice(outPos, bytesToRead)); rc = data.Read(inPos, destination.Slice(outPos, bytesToRead));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToRead; outPos += bytesToRead;
@ -62,8 +62,8 @@ public class DuplexStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = source.Length; int remaining = source.Length;
if (!CheckAccessRange(offset, source.Length, Length)) Result rc = CheckAccessRange(offset, source.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
while (remaining > 0) while (remaining > 0)
{ {
@ -74,7 +74,7 @@ public class DuplexStorage : IStorage
IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA; IStorage data = Bitmap.Bitmap[blockNum] ? DataB : DataA;
Result rc = data.Write(inPos, source.Slice(outPos, bytesToWrite)); rc = data.Write(inPos, source.Slice(outPos, bytesToWrite));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToWrite; outPos += bytesToWrite;

View file

@ -40,8 +40,8 @@ public class JournalStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = destination.Length; int remaining = destination.Length;
if (!CheckAccessRange(offset, destination.Length, Length)) Result rc = CheckAccessRange(offset, destination.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
while (remaining > 0) while (remaining > 0)
{ {
@ -52,7 +52,7 @@ public class JournalStorage : IStorage
int bytesToRead = Math.Min(remaining, BlockSize - blockPos); int bytesToRead = Math.Min(remaining, BlockSize - blockPos);
Result rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead)); rc = BaseStorage.Read(physicalOffset, destination.Slice(outPos, bytesToRead));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToRead; outPos += bytesToRead;
@ -69,8 +69,8 @@ public class JournalStorage : IStorage
int outPos = 0; int outPos = 0;
int remaining = source.Length; int remaining = source.Length;
if (!CheckAccessRange(offset, source.Length, Length)) Result rc = CheckAccessRange(offset, source.Length, Length);
return ResultFs.OutOfRange.Log(); if (rc.IsFailure()) return rc.Miss();
while (remaining > 0) while (remaining > 0)
{ {
@ -81,7 +81,7 @@ public class JournalStorage : IStorage
int bytesToWrite = Math.Min(remaining, BlockSize - blockPos); int bytesToWrite = Math.Min(remaining, BlockSize - blockPos);
Result rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite)); rc = BaseStorage.Write(physicalOffset, source.Slice(outPos, bytesToWrite));
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
outPos += bytesToWrite; outPos += bytesToWrite;

View file

@ -0,0 +1,37 @@
namespace LibHac.Util;
public static class IntUtil
{
// Todo: Use generic math once C# 11 is out
public static bool IsIntValueRepresentableAsLong(ulong value)
{
return value <= long.MaxValue;
}
public static bool IsIntValueRepresentableAsULong(long value)
{
return value >= 0;
}
public static bool IsIntValueRepresentableAsUInt(long value)
{
return value >= uint.MinValue && value <= uint.MaxValue;
}
public static bool CanAddWithoutOverflow(long x, long y)
{
if (y >= 0)
{
return x <= long.MaxValue - y;
}
else
{
return x >= unchecked(long.MinValue - y);
}
}
public static bool CanAddWithoutOverflow(ulong x, ulong y)
{
return x <= ulong.MaxValue - y;
}
}