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