Implement most of Nintendo's bucket tree code

This commit is contained in:
Alex Barney 2020-05-18 22:15:18 -07:00
parent 9ec00eed3b
commit 0c06d9e0b3
4 changed files with 784 additions and 0 deletions

View File

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

Can't render this file because it has a wrong number of fields in line 146.

View File

@ -117,6 +117,25 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-4023; Inner value: 0x1f6e02</summary>
public static Result.Base InvalidIndirectStorageSource => new Result.Base(ModuleFs, 4023);
/// <summary>Error code: 2002-4031; Range: 4031-4039; Inner value: 0x1f7e02</summary>
public static Result.Base BucketTreeCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4031, 4039); }
/// <summary>Error code: 2002-4032; Inner value: 0x1f8002</summary>
public static Result.Base InvalidBucketTreeSignature => new Result.Base(ModuleFs, 4032);
/// <summary>Error code: 2002-4033; Inner value: 0x1f8202</summary>
public static Result.Base InvalidBucketTreeEntryCount => new Result.Base(ModuleFs, 4033);
/// <summary>Error code: 2002-4034; Inner value: 0x1f8402</summary>
public static Result.Base InvalidBucketTreeNodeEntryCount => new Result.Base(ModuleFs, 4034);
/// <summary>Error code: 2002-4035; Inner value: 0x1f8602</summary>
public static Result.Base InvalidBucketTreeNodeOffset => new Result.Base(ModuleFs, 4035);
/// <summary>Error code: 2002-4036; Inner value: 0x1f8802</summary>
public static Result.Base InvalidBucketTreeEntryOffset => new Result.Base(ModuleFs, 4036);
/// <summary>Error code: 2002-4037; Inner value: 0x1f8a02</summary>
public static Result.Base InvalidBucketTreeEntrySetOffset => new Result.Base(ModuleFs, 4037);
/// <summary>Error code: 2002-4038; Inner value: 0x1f8c02</summary>
public static Result.Base InvalidBucketTreeNodeIndex => new Result.Base(ModuleFs, 4038);
/// <summary>Error code: 2002-4039; Inner value: 0x1f8e02</summary>
public static Result.Base BucketTreeEntryNotFound => new Result.Base(ModuleFs, 4039);
/// <summary>Error code: 2002-4241; Range: 4241-4259; Inner value: 0x212202</summary>
public static Result.Base RomHostFileSystemCorrupted { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 4241, 4259); }
/// <summary>Error code: 2002-4242; Inner value: 0x212402</summary>
@ -426,6 +445,8 @@ namespace LibHac.Fs
/// <summary>Error code: 2002-6700; Range: 6700-6799; Inner value: 0x345802</summary>
public static Result.Base OutOfResource { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6700, 6799); }
/// <summary>Error code: 2002-6705; Inner value: 0x346202</summary>
public static Result.Base BufferAllocationFailed => new Result.Base(ModuleFs, 6705);
/// <summary>Error code: 2002-6706; Inner value: 0x346402</summary>
public static Result.Base MappingTableFull => new Result.Base(ModuleFs, 6706);
/// <summary>Error code: 2002-6707; Inner value: 0x346602</summary>

View File

@ -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<NodeHeader>();
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<NodeHeader>());
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<long> node = NodeL1.GetNode<long>();
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<Header>();
public static long QueryNodeStorageSize(long nodeSize, long entrySize, int entryCount)
{
Assert.AssertTrue(entrySize >= sizeof(long));
Assert.AssertTrue(nodeSize >= entrySize + Unsafe.SizeOf<NodeHeader>());
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<NodeHeader>());
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<NodeHeader>()) / entrySize);
}
private static int GetOffsetCount(long nodeSize)
{
return (int)((nodeSize - Unsafe.SizeOf<NodeHeader>()) / 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<NodeHeader>() + 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<NodeHeader>());
return ref Unsafe.As<long, NodeHeader>(ref _header[0]);
}
public Span<byte> GetBuffer()
{
return MemoryMarshal.AsBytes(_header.AsSpan());
}
public BucketTreeNode<TEntry> GetNode<TEntry>() where TEntry : unmanaged
{
return new BucketTreeNode<TEntry>(GetBuffer());
}
public ref T Get<T>() where T : unmanaged
{
if (Unsafe.SizeOf<T>() != Unsafe.SizeOf<NodeHeader>())
{
throw new InvalidOperationException();
}
Assert.AssertTrue(_header.Length / sizeof(long) >= Unsafe.SizeOf<T>());
return ref Unsafe.As<long, T>(ref _header[0]);
}
}
public readonly ref struct BucketTreeNode<TEntry> where TEntry : unmanaged
{
private readonly ReadOnlySpan<byte> _buffer;
public BucketTreeNode(ReadOnlySpan<byte> buffer)
{
_buffer = buffer;
Assert.AssertTrue(_buffer.Length >= Unsafe.SizeOf<NodeHeader>());
Assert.AssertTrue(_buffer.Length >= Unsafe.SizeOf<NodeHeader>() + GetHeader().Count * Unsafe.SizeOf<TEntry>());
}
public int GetCount() => GetHeader().Count;
public ReadOnlySpan<TEntry> GetArray() => GetArray<TEntry>();
public long GetBeginOffset() => GetArray<long>()[0];
public long GetEndOffset() => GetHeader().Offset;
public long GetL2BeginOffset() => GetArray<long>()[GetCount()];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<TElement> GetArray<TElement>() where TElement : unmanaged
{
return MemoryMarshal.Cast<byte, TElement>(_buffer.Slice(Unsafe.SizeOf<NodeHeader>()));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private ref NodeHeader GetHeader()
{
return ref Unsafe.As<byte, NodeHeader>(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<T>() where T : unmanaged
{
return ref MemoryMarshal.Cast<byte, T>(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<long> node = Tree.NodeL1.GetNode<long>();
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<long> offsets = node.GetArray<long>().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<long> offsets = node.GetArray<long>().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<byte>((int)nodeSize))
{
return FindEntrySetWithBuffer(out entrySetIndex, virtualAddress, nodeIndex, rented.Span);
}
}
private Result FindEntrySetWithBuffer(out int outIndex, long virtualAddress, int nodeIndex,
Span<byte> 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<byte, NodeHeader>(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<byte>((int)entrySetSize))
{
return FindEntryWithBuffer(virtualAddress, entrySetIndex, rented.Span);
}
}
private Result FindEntryWithBuffer(long virtualAddress, int entrySetIndex, Span<byte> 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<byte, EntrySetHeader>(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<byte> 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();
}
}
}
}
}

View File

@ -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);
}
}
}