mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Remove old BucketTree
This commit is contained in:
parent
d3abdeacc4
commit
33b414a15c
6 changed files with 719 additions and 828 deletions
|
@ -9,7 +9,7 @@ namespace LibHac.FsSystem
|
|||
{
|
||||
public static readonly int NodeSize = 1024 * 16;
|
||||
|
||||
private BucketTree2 Table { get; } = new BucketTree2();
|
||||
private BucketTree Table { get; } = new BucketTree();
|
||||
|
||||
private readonly object _locker = new object();
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace LibHac.FsSystem
|
|||
if (destination.Length == 0)
|
||||
return Result.Success;
|
||||
|
||||
var visitor = new BucketTree2.Visitor();
|
||||
var visitor = new BucketTree.Visitor();
|
||||
|
||||
Result rc = Table.Find(ref visitor, offset);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
|
|
@ -1,109 +1,738 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
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 BucketTree<T> where T : BucketTreeEntry<T>, new()
|
||||
public partial class BucketTree
|
||||
{
|
||||
private const int BucketAlignment = 0x4000;
|
||||
public BucketTreeBucket<OffsetEntry> BucketOffsets { get; }
|
||||
public BucketTreeBucket<T>[] Buckets { get; }
|
||||
private const uint ExpectedMagic = 0x52544B42; // BKTR
|
||||
private const int MaxVersion = 1;
|
||||
|
||||
public BucketTree(IStorage data)
|
||||
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 = new NodeBuffer();
|
||||
|
||||
private long NodeSize { get; set; }
|
||||
private long EntrySize { 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)
|
||||
{
|
||||
var reader = new BinaryReader(data.AsStream());
|
||||
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());
|
||||
|
||||
BucketOffsets = new BucketTreeBucket<OffsetEntry>(reader);
|
||||
// Ensure valid entry count.
|
||||
if (entryCount <= 0)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
Buckets = new BucketTreeBucket<T>[BucketOffsets.EntryCount];
|
||||
// Allocate node.
|
||||
if (!_nodeL1.Allocate(nodeSize))
|
||||
return ResultFs.BufferAllocationFailed.Log();
|
||||
|
||||
for (int i = 0; i < BucketOffsets.EntryCount; i++)
|
||||
bool needFree = true;
|
||||
try
|
||||
{
|
||||
reader.BaseStream.Position = (i + 1) * BucketAlignment;
|
||||
Buckets[i] = new BucketTreeBucket<T>(reader);
|
||||
// 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;
|
||||
OffsetCount = offsetCount;
|
||||
EntrySetCount = entrySetCount;
|
||||
StartOffset = startOffset;
|
||||
EndOffset = endOffset;
|
||||
|
||||
needFree = false;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (needFree)
|
||||
_nodeL1.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> GetEntryList()
|
||||
{
|
||||
List<T> list = Buckets.SelectMany(x => x.Entries).ToList();
|
||||
public bool IsInitialized() => NodeSize > 0;
|
||||
public bool IsEmpty() => EntrySize == 0;
|
||||
|
||||
for (int i = 0; i < list.Count - 1; i++)
|
||||
public long GetStart() => StartOffset;
|
||||
public long GetEnd() => EndOffset;
|
||||
public long GetSize() => EndOffset - StartOffset;
|
||||
|
||||
public bool Includes(long offset)
|
||||
{
|
||||
return StartOffset <= offset && offset < EndOffset;
|
||||
}
|
||||
|
||||
public bool Includes(long offset, long size)
|
||||
{
|
||||
return size > 0 && StartOffset <= offset && size <= EndOffset - offset;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public 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;
|
||||
#pragma warning disable 414
|
||||
private int _reserved;
|
||||
#pragma warning restore 414
|
||||
|
||||
public void Format(int entryCount)
|
||||
{
|
||||
list[i].Next = list[i + 1];
|
||||
list[i].OffsetEnd = list[i + 1].Offset;
|
||||
Magic = ExpectedMagic;
|
||||
Version = MaxVersion;
|
||||
EntryCount = entryCount;
|
||||
_reserved = 0;
|
||||
}
|
||||
|
||||
list[list.Count - 1].OffsetEnd = BucketOffsets.OffsetEnd;
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
public class BucketTreeBucket<T> where T : BucketTreeEntry<T>, new()
|
||||
{
|
||||
public int Index;
|
||||
public int EntryCount;
|
||||
public long OffsetEnd;
|
||||
public T[] Entries;
|
||||
|
||||
public BucketTreeBucket(BinaryReader reader)
|
||||
{
|
||||
Index = reader.ReadInt32();
|
||||
EntryCount = reader.ReadInt32();
|
||||
OffsetEnd = reader.ReadInt64();
|
||||
Entries = new T[EntryCount];
|
||||
|
||||
for (int i = 0; i < EntryCount; i++)
|
||||
public Result Verify()
|
||||
{
|
||||
Entries[i] = new T().Read(reader);
|
||||
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 abstract class BucketTreeEntry<T> where T : BucketTreeEntry<T>
|
||||
{
|
||||
public long Offset { get; set; }
|
||||
public long OffsetEnd { get; set; }
|
||||
public T Next { get; set; }
|
||||
|
||||
protected abstract void ReadSpecific(BinaryReader reader);
|
||||
internal T Read(BinaryReader reader)
|
||||
public struct NodeHeader
|
||||
{
|
||||
Offset = reader.ReadInt64();
|
||||
ReadSpecific(reader);
|
||||
return (T)this;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class OffsetEntry : BucketTreeEntry<OffsetEntry>
|
||||
{
|
||||
protected override void ReadSpecific(BinaryReader reader) { }
|
||||
}
|
||||
|
||||
public class AesSubsectionEntry : BucketTreeEntry<AesSubsectionEntry>
|
||||
{
|
||||
public uint Field8 { get; set; }
|
||||
public uint Counter { get; set; }
|
||||
|
||||
protected override void ReadSpecific(BinaryReader reader)
|
||||
private struct NodeBuffer
|
||||
{
|
||||
Field8 = reader.ReadUInt32();
|
||||
Counter = reader.ReadUInt32();
|
||||
// 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 class RelocationEntry : BucketTreeEntry<RelocationEntry>
|
||||
{
|
||||
public long SourceOffset { get; set; }
|
||||
public int SourceIndex { get; set; }
|
||||
|
||||
protected override void ReadSpecific(BinaryReader reader)
|
||||
public readonly ref struct BucketTreeNode<TEntry> where TEntry : unmanaged
|
||||
{
|
||||
SourceOffset = reader.ReadInt64();
|
||||
SourceIndex = reader.ReadInt32();
|
||||
private readonly Span<byte> _buffer;
|
||||
|
||||
public BucketTreeNode(Span<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() => GetWritableArray();
|
||||
internal Span<TEntry> GetWritableArray() => GetWritableArray<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 GetWritableArray<TElement>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<TElement> GetWritableArray<TElement>() where TElement : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, TElement>(_buffer.Slice(Unsafe.SizeOf<NodeHeader>()));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ref NodeHeader GetHeader()
|
||||
{
|
||||
return ref Unsafe.As<byte, NodeHeader>(ref MemoryMarshal.GetReference(_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
public ref struct Visitor
|
||||
{
|
||||
private BucketTree 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
|
||||
{
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
[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(BucketTree 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 void Dispose()
|
||||
{
|
||||
// todo: try using shared arrays
|
||||
}
|
||||
|
||||
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.InvalidBucketTreeVirtualOffset.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.InvalidBucketTreeVirtualOffset.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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,738 +0,0 @@
|
|||
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 partial 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 = new NodeBuffer();
|
||||
|
||||
private long NodeSize { get; set; }
|
||||
private long EntrySize { 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;
|
||||
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 long GetStart() => StartOffset;
|
||||
public long GetEnd() => EndOffset;
|
||||
public long GetSize() => EndOffset - StartOffset;
|
||||
|
||||
public bool Includes(long offset)
|
||||
{
|
||||
return StartOffset <= offset && offset < EndOffset;
|
||||
}
|
||||
|
||||
public bool Includes(long offset, long size)
|
||||
{
|
||||
return size > 0 && StartOffset <= offset && size <= EndOffset - offset;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public 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;
|
||||
#pragma warning disable 414
|
||||
private int _reserved;
|
||||
#pragma warning restore 414
|
||||
|
||||
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 struct 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 readonly ref struct BucketTreeNode<TEntry> where TEntry : unmanaged
|
||||
{
|
||||
private readonly Span<byte> _buffer;
|
||||
|
||||
public BucketTreeNode(Span<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() => GetWritableArray();
|
||||
internal Span<TEntry> GetWritableArray() => GetWritableArray<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 GetWritableArray<TElement>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Span<TElement> GetWritableArray<TElement>() where TElement : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, TElement>(_buffer.Slice(Unsafe.SizeOf<NodeHeader>()));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal 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
|
||||
{
|
||||
// ReSharper disable once MemberHidesStaticFromOuterClass
|
||||
[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 void Dispose()
|
||||
{
|
||||
// todo: try using shared arrays
|
||||
}
|
||||
|
||||
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.InvalidBucketTreeVirtualOffset.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.InvalidBucketTreeVirtualOffset.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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ using LibHac.Fs;
|
|||
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public partial class BucketTree2
|
||||
public partial class BucketTree
|
||||
{
|
||||
public class Builder
|
||||
{
|
||||
|
|
|
@ -12,10 +12,10 @@ namespace LibHac.FsSystem
|
|||
public static readonly int StorageCount = 2;
|
||||
public static readonly int NodeSize = 1024 * 16;
|
||||
|
||||
private BucketTree2 Table { get; } = new BucketTree2();
|
||||
private BucketTree Table { get; } = new BucketTree();
|
||||
private SubStorage2[] DataStorage { get; } = new SubStorage2[StorageCount];
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x14)]
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x14, Pack = 4)]
|
||||
public struct Entry
|
||||
{
|
||||
private long VirtualOffset;
|
||||
|
@ -29,13 +29,13 @@ namespace LibHac.FsSystem
|
|||
public long GetPhysicalOffset() => PhysicalOffset;
|
||||
}
|
||||
|
||||
public static long QueryHeaderStorageSize() => BucketTree2.QueryHeaderStorageSize();
|
||||
public static long QueryHeaderStorageSize() => BucketTree.QueryHeaderStorageSize();
|
||||
|
||||
public static long QueryNodeStorageSize(int entryCount) =>
|
||||
BucketTree2.QueryNodeStorageSize(NodeSize, Unsafe.SizeOf<Entry>(), entryCount);
|
||||
BucketTree.QueryNodeStorageSize(NodeSize, Unsafe.SizeOf<Entry>(), entryCount);
|
||||
|
||||
public static long QueryEntryStorageSize(int entryCount) =>
|
||||
BucketTree2.QueryEntryStorageSize(NodeSize, Unsafe.SizeOf<Entry>(), entryCount);
|
||||
BucketTree.QueryEntryStorageSize(NodeSize, Unsafe.SizeOf<Entry>(), entryCount);
|
||||
|
||||
public bool IsInitialized() => Table.IsInitialized();
|
||||
|
||||
|
@ -43,7 +43,7 @@ namespace LibHac.FsSystem
|
|||
{
|
||||
// Read and verify the bucket tree header.
|
||||
// note: skip init
|
||||
var header = new BucketTree2.Header();
|
||||
var header = new BucketTree.Header();
|
||||
|
||||
Result rc = tableStorage.Read(0, SpanHelpers.AsByteSpan(ref header));
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
@ -100,7 +100,7 @@ namespace LibHac.FsSystem
|
|||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
// Find the offset in our tree
|
||||
var visitor = new BucketTree2.Visitor();
|
||||
var visitor = new BucketTree.Visitor();
|
||||
Result rc = Table.Find(ref visitor, offset);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
|
@ -214,7 +214,7 @@ namespace LibHac.FsSystem
|
|||
return ResultFs.OutOfRange.Log();
|
||||
|
||||
// Find the offset in our tree
|
||||
var visitor = new BucketTree2.Visitor();
|
||||
var visitor = new BucketTree.Visitor();
|
||||
|
||||
Result rc = Table.Find(ref visitor, offset);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
|
|
@ -199,7 +199,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
|
||||
var cachedBucketTreeData = new CachedStorage(encryptionBucketTreeData, IndirectStorage.NodeSize, 6, true);
|
||||
|
||||
var treeHeader = new BucketTree2.Header();
|
||||
var treeHeader = new BucketTree.Header();
|
||||
info.EncryptionTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader));
|
||||
long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount);
|
||||
long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount);
|
||||
|
@ -237,7 +237,7 @@ namespace LibHac.FsSystem.NcaUtils
|
|||
return patchStorage;
|
||||
}
|
||||
|
||||
var treeHeader = new BucketTree2.Header();
|
||||
var treeHeader = new BucketTree.Header();
|
||||
patchInfo.RelocationTreeHeader.CopyTo(SpanHelpers.AsByteSpan(ref treeHeader));
|
||||
long nodeStorageSize = IndirectStorage.QueryNodeStorageSize(treeHeader.EntryCount);
|
||||
long entryStorageSize = IndirectStorage.QueryEntryStorageSize(treeHeader.EntryCount);
|
||||
|
|
Loading…
Reference in a new issue