diff --git a/build/CodeGen/results.csv b/build/CodeGen/results.csv index dd634f7f..1b9bd8e7 100644 --- a/build/CodeGen/results.csv +++ b/build/CodeGen/results.csv @@ -52,6 +52,16 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,4001,4299,RomCorrupted, 2,4023,,InvalidIndirectStorageSource, +2,4031,4039,BucketTreeCorrupted, +2,4032,,InvalidBucketTreeSignature, +2,4033,,InvalidBucketTreeEntryCount, +2,4034,,InvalidBucketTreeNodeEntryCount, +2,4035,,InvalidBucketTreeNodeOffset, +2,4036,,InvalidBucketTreeEntryOffset, +2,4037,,InvalidBucketTreeEntrySetOffset, +2,4038,,InvalidBucketTreeNodeIndex, +2,4039,,BucketTreeEntryNotFound, + 2,4241,4259,RomHostFileSystemCorrupted, 2,4242,,RomHostEntryCorrupted, 2,4243,,RomHostFileDataCorrupted, @@ -220,6 +230,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary 2,6606,,TargetProgramIndexNotFound,Specified program index is not found 2,6700,6799,OutOfResource, +2,6705,,BufferAllocationFailed, 2,6706,,MappingTableFull, 2,6707,,AllocationTableInsufficientFreeBlocks, 2,6709,,OpenCountLimit, diff --git a/src/LibHac/Fs/ResultFs.cs b/src/LibHac/Fs/ResultFs.cs index f2af9707..179f181a 100644 --- a/src/LibHac/Fs/ResultFs.cs +++ b/src/LibHac/Fs/ResultFs.cs @@ -117,6 +117,25 @@ namespace LibHac.Fs /// Error code: 2002-4023; Inner value: 0x1f6e02 public static Result.Base InvalidIndirectStorageSource => new Result.Base(ModuleFs, 4023); + /// Error code: 2002-4031; Range: 4031-4039; Inner value: 0x1f7e02 + public static Result.Base BucketTreeCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4031, 4039); } + /// Error code: 2002-4032; Inner value: 0x1f8002 + public static Result.Base InvalidBucketTreeSignature => new Result.Base(ModuleFs, 4032); + /// Error code: 2002-4033; Inner value: 0x1f8202 + public static Result.Base InvalidBucketTreeEntryCount => new Result.Base(ModuleFs, 4033); + /// Error code: 2002-4034; Inner value: 0x1f8402 + public static Result.Base InvalidBucketTreeNodeEntryCount => new Result.Base(ModuleFs, 4034); + /// Error code: 2002-4035; Inner value: 0x1f8602 + public static Result.Base InvalidBucketTreeNodeOffset => new Result.Base(ModuleFs, 4035); + /// Error code: 2002-4036; Inner value: 0x1f8802 + public static Result.Base InvalidBucketTreeEntryOffset => new Result.Base(ModuleFs, 4036); + /// Error code: 2002-4037; Inner value: 0x1f8a02 + public static Result.Base InvalidBucketTreeEntrySetOffset => new Result.Base(ModuleFs, 4037); + /// Error code: 2002-4038; Inner value: 0x1f8c02 + public static Result.Base InvalidBucketTreeNodeIndex => new Result.Base(ModuleFs, 4038); + /// Error code: 2002-4039; Inner value: 0x1f8e02 + public static Result.Base BucketTreeEntryNotFound => new Result.Base(ModuleFs, 4039); + /// Error code: 2002-4241; Range: 4241-4259; Inner value: 0x212202 public static Result.Base RomHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4241, 4259); } /// Error code: 2002-4242; Inner value: 0x212402 @@ -426,6 +445,8 @@ namespace LibHac.Fs /// Error code: 2002-6700; Range: 6700-6799; Inner value: 0x345802 public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6700, 6799); } + /// Error code: 2002-6705; Inner value: 0x346202 + public static Result.Base BufferAllocationFailed => new Result.Base(ModuleFs, 6705); /// Error code: 2002-6706; Inner value: 0x346402 public static Result.Base MappingTableFull => new Result.Base(ModuleFs, 6706); /// Error code: 2002-6707; Inner value: 0x346602 diff --git a/src/LibHac/FsSystem/BucketTree2.cs b/src/LibHac/FsSystem/BucketTree2.cs new file mode 100644 index 00000000..ace5ef7a --- /dev/null +++ b/src/LibHac/FsSystem/BucketTree2.cs @@ -0,0 +1,730 @@ +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibHac.Common; +using LibHac.Diag; +using LibHac.Fs; + +namespace LibHac.FsSystem +{ + public class BucketTree2 + { + private const uint ExpectedMagic = 0x52544B42; // BKTR + private const int MaxVersion = 1; + + private const int NodeSizeMin = 1024; + private const int NodeSizeMax = 1024 * 512; + + private static int NodeHeaderSize => Unsafe.SizeOf(); + + private SubStorage2 NodeStorage { get; set; } + private SubStorage2 EntryStorage { get; set; } + + private NodeBuffer NodeL1 { get; } = new NodeBuffer(); + + private long NodeSize { get; set; } + private long EntrySize { get; set; } + private int EntryCount { get; set; } + private int OffsetCount { get; set; } + private int EntrySetCount { get; set; } + private long StartOffset { get; set; } + private long EndOffset { get; set; } + + public Result Initialize(SubStorage2 nodeStorage, SubStorage2 entryStorage, int nodeSize, int entrySize, + int entryCount) + { + Assert.AssertTrue(entrySize >= sizeof(long)); + Assert.AssertTrue(nodeSize >= entrySize + Unsafe.SizeOf()); + Assert.AssertTrue(NodeSizeMin <= nodeSize && nodeSize <= NodeSizeMax); + Assert.AssertTrue(Util.IsPowerOfTwo(nodeSize)); + Assert.AssertTrue(!IsInitialized()); + + // Ensure valid entry count. + if (entryCount <= 0) + return ResultFs.InvalidArgument.Log(); + + // Allocate node. + if (!NodeL1.Allocate(nodeSize)) + return ResultFs.BufferAllocationFailed.Log(); + + bool needFree = true; + try + { + // Read node. + Result rc = nodeStorage.Read(0, NodeL1.GetBuffer()); + if (rc.IsFailure()) return rc; + + // Verify node. + rc = NodeL1.GetHeader().Verify(0, nodeSize, sizeof(long)); + if (rc.IsFailure()) return rc; + + // Validate offsets. + int offsetCount = GetOffsetCount(nodeSize); + int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); + BucketTreeNode node = NodeL1.GetNode(); + + long startOffset; + if (offsetCount < entrySetCount && node.GetCount() < offsetCount) + { + startOffset = node.GetL2BeginOffset(); + } + else + { + startOffset = node.GetBeginOffset(); + } + + long endOffset = node.GetEndOffset(); + + if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset) + return ResultFs.InvalidBucketTreeEntryOffset.Log(); + + NodeStorage = nodeStorage; + EntryStorage = entryStorage; + NodeSize = nodeSize; + EntrySize = entrySize; + EntryCount = entryCount; + OffsetCount = offsetCount; + EntrySetCount = entrySetCount; + StartOffset = startOffset; + EndOffset = endOffset; + + needFree = false; + + return Result.Success; + } + finally + { + if (needFree) + NodeL1.Free(); + } + } + + public bool IsInitialized() => NodeSize > 0; + public bool IsEmpty() => EntrySize == 0; + + public Result Find(ref Visitor visitor, long virtualAddress) + { + Assert.AssertTrue(IsInitialized()); + + if (virtualAddress < 0) + return ResultFs.InvalidOffset.Log(); + + if (IsEmpty()) + return ResultFs.OutOfRange.Log(); + + Result rc = visitor.Initialize(this); + if (rc.IsFailure()) return rc; + + return visitor.Find(virtualAddress); + } + + public static int QueryHeaderStorageSize() => Unsafe.SizeOf
(); + + public static long QueryNodeStorageSize(long nodeSize, long entrySize, int entryCount) + { + Assert.AssertTrue(entrySize >= sizeof(long)); + Assert.AssertTrue(nodeSize >= entrySize + Unsafe.SizeOf()); + Assert.AssertTrue(NodeSizeMin <= nodeSize && nodeSize <= NodeSizeMax); + Assert.AssertTrue(Util.IsPowerOfTwo(nodeSize)); + Assert.AssertTrue(entryCount >= 0); + + if (entryCount <= 0) + return 0; + + return (1 + GetNodeL2Count(nodeSize, entrySize, entryCount)) * nodeSize; + } + + public static long QueryEntryStorageSize(long nodeSize, long entrySize, int entryCount) + { + Assert.AssertTrue(entrySize >= sizeof(long)); + Assert.AssertTrue(nodeSize >= entrySize + Unsafe.SizeOf()); + Assert.AssertTrue(NodeSizeMin <= nodeSize && nodeSize <= NodeSizeMax); + Assert.AssertTrue(Util.IsPowerOfTwo(nodeSize)); + Assert.AssertTrue(entryCount >= 0); + + if (entryCount <= 0) + return 0; + + return GetEntrySetCount(nodeSize, entrySize, entryCount) * nodeSize; + } + + private static int GetEntryCount(long nodeSize, long entrySize) + { + return (int)((nodeSize - Unsafe.SizeOf()) / entrySize); + } + + private static int GetOffsetCount(long nodeSize) + { + return (int)((nodeSize - Unsafe.SizeOf()) / sizeof(long)); + } + + private static int GetEntrySetCount(long nodeSize, long entrySize, int entryCount) + { + int entryCountPerNode = GetEntryCount(nodeSize, entrySize); + return Util.DivideByRoundUp(entryCount, entryCountPerNode); + } + + private static int GetNodeL2Count(long nodeSize, long entrySize, int entryCount) + { + int offsetCountPerNode = GetOffsetCount(nodeSize); + int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount); + + if (entrySetCount <= offsetCountPerNode) + return 0; + + int nodeL2Count = Util.DivideByRoundUp(entrySetCount, offsetCountPerNode); + Abort.DoAbortUnless(nodeL2Count <= offsetCountPerNode); + + return Util.DivideByRoundUp(entrySetCount - (offsetCountPerNode - (nodeL2Count - 1)), offsetCountPerNode); + } + + private static long GetBucketTreeEntryOffset(long entrySetOffset, long entrySize, int entryIndex) + { + return entrySetOffset + Unsafe.SizeOf() + entryIndex * entrySize; + } + + private static long GetBucketTreeEntryOffset(int entrySetIndex, long nodeSize, long entrySize, int entryIndex) + { + return GetBucketTreeEntryOffset(entrySetIndex * nodeSize, entrySize, entryIndex); + } + + private bool IsExistL2() => OffsetCount < EntrySetCount; + private bool IsExistOffsetL2OnL1() => IsExistL2() && NodeL1.GetHeader().Count < OffsetCount; + + private long GetEntrySetIndex(int nodeIndex, int offsetIndex) + { + return (OffsetCount - NodeL1.GetHeader().Count) + (OffsetCount * nodeIndex) + offsetIndex; + } + + public struct Header + { + public uint Magic; + public uint Version; + public int EntryCount; + private int _reserved; + + public void Format(int entryCount) + { + Magic = ExpectedMagic; + Version = MaxVersion; + EntryCount = entryCount; + _reserved = 0; + } + + public Result Verify() + { + if (Magic != ExpectedMagic) + return ResultFs.InvalidBucketTreeSignature.Log(); + + if (EntryCount < 0) + return ResultFs.InvalidBucketTreeEntryCount.Log(); + + if (Version > MaxVersion) + return ResultFs.UnsupportedVersion.Log(); + + return Result.Success; + } + } + + public struct NodeHeader + { + public int Index; + public int Count; + public long Offset; + + public Result Verify(int nodeIndex, long nodeSize, long entrySize) + { + if (Index != nodeIndex) + return ResultFs.InvalidBucketTreeNodeIndex.Log(); + + if (entrySize == 0 || nodeSize < entrySize + NodeHeaderSize) + return ResultFs.InvalidSize.Log(); + + long maxEntryCount = (nodeSize - NodeHeaderSize) / entrySize; + + if (Count <= 0 || maxEntryCount < Count) + return ResultFs.InvalidBucketTreeNodeEntryCount.Log(); + + if (Offset < 0) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); + + return Result.Success; + } + } + + private class NodeBuffer + { + // Use long to ensure alignment + private long[] _header; + + public bool Allocate(int nodeSize) + { + Assert.AssertTrue(_header == null); + + _header = new long[nodeSize / sizeof(long)]; + + return _header != null; + } + + public void Free() + { + _header = null; + } + + public void FillZero() + { + if (_header != null) + { + Array.Fill(_header, 0); + } + } + + public ref NodeHeader GetHeader() + { + Assert.AssertTrue(_header.Length / sizeof(long) >= Unsafe.SizeOf()); + + return ref Unsafe.As(ref _header[0]); + } + + public Span GetBuffer() + { + return MemoryMarshal.AsBytes(_header.AsSpan()); + } + + public BucketTreeNode GetNode() where TEntry : unmanaged + { + return new BucketTreeNode(GetBuffer()); + } + + public ref T Get() where T : unmanaged + { + if (Unsafe.SizeOf() != Unsafe.SizeOf()) + { + throw new InvalidOperationException(); + } + + Assert.AssertTrue(_header.Length / sizeof(long) >= Unsafe.SizeOf()); + + return ref Unsafe.As(ref _header[0]); + } + } + + public readonly ref struct BucketTreeNode where TEntry : unmanaged + { + private readonly ReadOnlySpan _buffer; + + public BucketTreeNode(ReadOnlySpan buffer) + { + _buffer = buffer; + + Assert.AssertTrue(_buffer.Length >= Unsafe.SizeOf()); + Assert.AssertTrue(_buffer.Length >= Unsafe.SizeOf() + GetHeader().Count * Unsafe.SizeOf()); + } + + public int GetCount() => GetHeader().Count; + + public ReadOnlySpan GetArray() => GetArray(); + + public long GetBeginOffset() => GetArray()[0]; + public long GetEndOffset() => GetHeader().Offset; + public long GetL2BeginOffset() => GetArray()[GetCount()]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan GetArray() where TElement : unmanaged + { + return MemoryMarshal.Cast(_buffer.Slice(Unsafe.SizeOf())); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private ref NodeHeader GetHeader() + { + return ref Unsafe.As(ref MemoryMarshal.GetReference(_buffer)); + } + } + + public ref struct Visitor + { + private BucketTree2 Tree { get; set; } + private byte[] Entry { get; set; } + private int EntryIndex { get; set; } + private int EntrySetCount { get; set; } + private EntrySetHeader _entrySet; + + [StructLayout(LayoutKind.Explicit)] + private struct EntrySetHeader + { + [FieldOffset(0)] public NodeHeader Header; + [FieldOffset(0)] public EntrySetInfo Info; + + [StructLayout(LayoutKind.Sequential)] + public struct EntrySetInfo + { + public int Index; + public int Count; + public long End; + public long Start; + } + } + + public Result Initialize(BucketTree2 tree) + { + Assert.AssertTrue(tree != null); + Assert.AssertTrue(Tree == null || tree == Tree); + + if (Entry == null) + { + Entry = new byte[tree.EntrySize]; + Tree = tree; + EntryIndex = -1; + } + + return Result.Success; + } + + public bool IsValid() => EntryIndex >= 0; + + public bool CanMoveNext() + { + return IsValid() && (EntryIndex + 1 < _entrySet.Info.Count || _entrySet.Info.Index + 1 < EntrySetCount); + } + + public bool CanMovePrevious() + { + return IsValid() && (EntryIndex > 0 || _entrySet.Info.Index > 0); + } + + public ref T Get() where T : unmanaged + { + return ref MemoryMarshal.Cast(Entry)[0]; + } + + public Result MoveNext() + { + Result rc; + + if (!IsValid()) + return ResultFs.OutOfRange.Log(); + + int entryIndex = EntryIndex + 1; + + // Invalidate our index, and read the header for the next index. + if (entryIndex == _entrySet.Info.Count) + { + int entrySetIndex = _entrySet.Info.Index + 1; + if (entrySetIndex >= EntrySetCount) + return ResultFs.OutOfRange.Log(); + + EntryIndex = -1; + + long end = _entrySet.Info.End; + + long entrySetSize = Tree.NodeSize; + long entrySetOffset = entrySetIndex * entrySetSize; + + rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); + if (rc.IsFailure()) return rc; + + rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); + if (rc.IsFailure()) return rc; + + if (_entrySet.Info.Start != end || _entrySet.Info.Start >= _entrySet.Info.End) + return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); + + entryIndex = 0; + } + else + { + EntryIndex = 1; + } + + // Read the new entry + long entrySize = Tree.EntrySize; + long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); + + rc = Tree.EntryStorage.Read(entryOffset, Entry); + if (rc.IsFailure()) return rc; + + // Note that we changed index. + EntryIndex = entryIndex; + return Result.Success; + } + + public Result MovePrevious() + { + Result rc; + + if (!IsValid()) + return ResultFs.OutOfRange.Log(); + + int entryIndex = EntryIndex; + + if (entryIndex == 0) + { + if (_entrySet.Info.Index <= 0) + return ResultFs.OutOfRange.Log(); + + EntryIndex = -1; + + long start = _entrySet.Info.Start; + + long entrySetSize = Tree.NodeSize; + int entrySetIndex = _entrySet.Info.Index - 1; + long entrySetOffset = entrySetIndex * entrySetSize; + + rc = Tree.EntryStorage.Read(entrySetOffset, SpanHelpers.AsByteSpan(ref _entrySet)); + if (rc.IsFailure()) return rc; + + rc = _entrySet.Header.Verify(entrySetIndex, entrySetSize, Tree.EntrySize); + if (rc.IsFailure()) return rc; + + if (_entrySet.Info.End != start || _entrySet.Info.Start >= _entrySet.Info.End) + return ResultFs.InvalidBucketTreeEntrySetOffset.Log(); + + entryIndex = _entrySet.Info.Count; + } + else + { + EntryIndex = -1; + } + + // Read the new entry + long entrySize = Tree.EntrySize; + long entryOffset = GetBucketTreeEntryOffset(_entrySet.Info.Index, Tree.NodeSize, entrySize, entryIndex); + + rc = Tree.EntryStorage.Read(entryOffset, Entry); + if (rc.IsFailure()) return rc; + + // Note that we changed index. + EntryIndex = entryIndex; + return Result.Success; + } + + public Result Find(long virtualAddress) + { + Result rc; + + // Get the node. + BucketTreeNode node = Tree.NodeL1.GetNode(); + + if (virtualAddress >= node.GetEndOffset()) + return ResultFs.OutOfRange.Log(); + + int entrySetIndex; + + if (Tree.IsExistOffsetL2OnL1() && virtualAddress < node.GetBeginOffset()) + { + // The portion of the L2 offsets containing our target offset is stored in the L1 node + ReadOnlySpan offsets = node.GetArray().Slice(node.GetCount()); + int index = offsets.BinarySearch(virtualAddress); + if (index < 0) index = (~index) - 1; + + if (index < 0) + return ResultFs.OutOfRange.Log(); + + entrySetIndex = index; + } + else + { + ReadOnlySpan offsets = node.GetArray().Slice(0, node.GetCount()); + int index = offsets.BinarySearch(virtualAddress); + if (index < 0) index = (~index) - 1; + + if (index < 0) + return ResultFs.OutOfRange.Log(); + + if (Tree.IsExistL2()) + { + if (index >= Tree.OffsetCount) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); + + rc = FindEntrySet(out entrySetIndex, virtualAddress, index); + if (rc.IsFailure()) return rc; + } + else + { + entrySetIndex = index; + } + } + + // Validate the entry set index. + if (entrySetIndex < 0 || entrySetIndex >= Tree.EntrySetCount) + return ResultFs.InvalidBucketTreeNodeOffset.Log(); + + // Find the entry. + rc = FindEntry(virtualAddress, entrySetIndex); + if (rc.IsFailure()) return rc; + + // Set count. + EntrySetCount = Tree.EntrySetCount; + return Result.Success; + } + + private Result FindEntrySet(out int entrySetIndex, long virtualAddress, int nodeIndex) + { + long nodeSize = Tree.NodeSize; + + using (var rented = new RentedArray((int)nodeSize)) + { + return FindEntrySetWithBuffer(out entrySetIndex, virtualAddress, nodeIndex, rented.Span); + } + } + + private Result FindEntrySetWithBuffer(out int outIndex, long virtualAddress, int nodeIndex, + Span buffer) + { + outIndex = default; + + // Calculate node extents. + long nodeSize = Tree.NodeSize; + long nodeOffset = (nodeIndex + 1) * nodeSize; + SubStorage2 storage = Tree.NodeStorage; + + // Read the node. + Result rc = storage.Read(nodeOffset, buffer.Slice(0, (int)nodeSize)); + if (rc.IsFailure()) return rc; + + // Validate the header. + NodeHeader header = MemoryMarshal.Cast(buffer)[0]; + rc = header.Verify(nodeIndex, nodeSize, sizeof(long)); + if (rc.IsFailure()) return rc; + + // Create the node and find. + var node = new StorageNode(sizeof(long), header.Count); + node.Find(buffer, virtualAddress); + + if (node.GetIndex() < 0) + return ResultFs.BucketTreeEntryNotFound.Log(); + + // Return the index. + outIndex = (int)Tree.GetEntrySetIndex(header.Index, node.GetIndex()); + return Result.Success; + } + + private Result FindEntry(long virtualAddress, int entrySetIndex) + { + long entrySetSize = Tree.NodeSize; + + using (var rented = new RentedArray((int)entrySetSize)) + { + return FindEntryWithBuffer(virtualAddress, entrySetIndex, rented.Span); + } + } + + private Result FindEntryWithBuffer(long virtualAddress, int entrySetIndex, Span buffer) + { + // Calculate entry set extents. + long entrySize = Tree.EntrySize; + long entrySetSize = Tree.NodeSize; + long entrySetOffset = entrySetIndex * entrySetSize; + SubStorage2 storage = Tree.EntryStorage; + + // Read the entry set. + Result rc = storage.Read(entrySetOffset, buffer.Slice(0, (int)entrySetSize)); + if (rc.IsFailure()) return rc; + + // Validate the entry set. + EntrySetHeader entrySet = MemoryMarshal.Cast(buffer)[0]; + rc = entrySet.Header.Verify(entrySetIndex, entrySetSize, entrySize); + if (rc.IsFailure()) return rc; + + // Create the node, and find. + var node = new StorageNode(entrySize, entrySet.Info.Count); + node.Find(buffer, virtualAddress); + + if (node.GetIndex() < 0) + return ResultFs.BucketTreeEntryNotFound.Log(); + + // Copy the data into entry. + int entryIndex = node.GetIndex(); + long entryOffset = GetBucketTreeEntryOffset(0, entrySize, entryIndex); + buffer.Slice((int)entryOffset, (int)entrySize).CopyTo(Entry); + + // Set our entry set/index. + _entrySet = entrySet; + EntryIndex = entryIndex; + + return Result.Success; + } + + private struct StorageNode + { + private Offset _start; + private int _count; + private int _index; + + public StorageNode(long size, int count) + { + _start = new Offset(NodeHeaderSize, (int)size); + _count = count; + _index = -1; + } + + public StorageNode(long offset, long size, int count) + { + _start = new Offset(offset + NodeHeaderSize, (int)size); + _count = count; + _index = -1; + } + + public int GetIndex() => _index; + + public void Find(ReadOnlySpan buffer, long virtualAddress) + { + int end = _count; + Offset pos = _start; + + while (end > 0) + { + int half = end / 2; + Offset mid = pos + half; + + long offset = BinaryPrimitives.ReadInt64LittleEndian(buffer.Slice((int)mid.Get())); + + if (offset <= virtualAddress) + { + pos = mid + 1; + end -= half + 1; + } + else + { + end = half; + } + } + + _index = (int)(pos - _start) - 1; + } + + private readonly struct Offset + { + private readonly long _offset; + private readonly int _stride; + + public Offset(long offset, int stride) + { + _offset = offset; + _stride = stride; + } + + public long Get() => _offset; + + public static Offset operator ++(Offset left) => left + 1; + public static Offset operator --(Offset left) => left - 1; + + public static Offset operator +(Offset left, long right) => new Offset(left._offset + right * left._stride, left._stride); + public static Offset operator -(Offset left, long right) => new Offset(left._offset - right * left._stride, left._stride); + + public static long operator -(Offset left, Offset right) => + (left._offset - right._offset) / left._stride; + + public static bool operator ==(Offset left, Offset right) => left._offset == right._offset; + public static bool operator !=(Offset left, Offset right) => left._offset != right._offset; + + public bool Equals(Offset other) => _offset == other._offset; + public override bool Equals(object obj) => obj is Offset other && Equals(other); + public override int GetHashCode() => _offset.GetHashCode(); + } + } + } + } +} diff --git a/src/LibHac/Util.cs b/src/LibHac/Util.cs index 7c226b5e..31c7ce7b 100644 --- a/src/LibHac/Util.cs +++ b/src/LibHac/Util.cs @@ -491,5 +491,27 @@ namespace LibHac return keyGeneration - 1; } + + public static bool IsPowerOfTwo(int value) + { + return value > 0 && ResetLeastSignificantOneBit(value) == 0; + } + + public static bool IsPowerOfTwo(long value) + { + return value > 0 && ResetLeastSignificantOneBit(value) == 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int ResetLeastSignificantOneBit(int value) + { + return value & (value - 1); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static long ResetLeastSignificantOneBit(long value) + { + return value & (value - 1); + } } }