mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Fix bug in BucketTree, diff with v16, use new C# features
This commit is contained in:
parent
04a5d03b5f
commit
809cecd1aa
2 changed files with 178 additions and 170 deletions
|
@ -12,175 +12,10 @@ using Buffer = LibHac.Mem.Buffer;
|
||||||
|
|
||||||
namespace LibHac.FsSystem;
|
namespace LibHac.FsSystem;
|
||||||
|
|
||||||
/// <summary>
|
file struct StorageNode
|
||||||
/// Allows searching and iterating the entries in a bucket tree data structure.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>Based on nnSdk 14.3.0 (FS 14.1.0)</remarks>
|
|
||||||
public partial class BucketTree : IDisposable
|
|
||||||
{
|
{
|
||||||
private const uint ExpectedMagic = 0x52544B42; // BKTR
|
private static int NodeHeaderSize => Unsafe.SizeOf<BucketTree.NodeHeader>();
|
||||||
private const int MaxVersion = 1;
|
|
||||||
|
|
||||||
private const int NodeSizeMin = 1024;
|
|
||||||
private const int NodeSizeMax = 1024 * 512;
|
|
||||||
|
|
||||||
private static readonly int BufferAlignment = sizeof(long);
|
|
||||||
|
|
||||||
private static int NodeHeaderSize => Unsafe.SizeOf<NodeHeader>();
|
|
||||||
|
|
||||||
private ValueSubStorage _nodeStorage;
|
|
||||||
private ValueSubStorage _entryStorage;
|
|
||||||
|
|
||||||
private NodeBuffer _nodeL1;
|
|
||||||
|
|
||||||
private long _nodeSize;
|
|
||||||
private long _entrySize;
|
|
||||||
private int _entryCount;
|
|
||||||
private int _offsetCount;
|
|
||||||
private int _entrySetCount;
|
|
||||||
private OffsetCache _offsetCache;
|
|
||||||
|
|
||||||
public struct ContinuousReadingInfo
|
|
||||||
{
|
|
||||||
private long _readSize;
|
|
||||||
private int _skipCount;
|
|
||||||
private bool _isDone;
|
|
||||||
|
|
||||||
public readonly bool CanDo() => _readSize != 0;
|
|
||||||
public bool CheckNeedScan() => --_skipCount <= 0;
|
|
||||||
public readonly bool IsDone() => _isDone;
|
|
||||||
|
|
||||||
public void Done()
|
|
||||||
{
|
|
||||||
_readSize = 0;
|
|
||||||
_isDone = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly long GetReadSize() => _readSize;
|
|
||||||
public void SetReadSize(long readSize) => _readSize = readSize;
|
|
||||||
|
|
||||||
public void Reset()
|
|
||||||
{
|
|
||||||
_readSize = 0;
|
|
||||||
_skipCount = 0;
|
|
||||||
_isDone = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSkipCount(int count)
|
|
||||||
{
|
|
||||||
Assert.SdkRequiresGreaterEqual(count, 0);
|
|
||||||
|
|
||||||
_skipCount = count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IContinuousReadingEntry
|
|
||||||
{
|
|
||||||
int FragmentSizeMax { get; }
|
|
||||||
|
|
||||||
long GetVirtualOffset();
|
|
||||||
long GetPhysicalOffset();
|
|
||||||
bool IsFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct ContinuousReadingParam<TEntry> where TEntry : unmanaged, IContinuousReadingEntry
|
|
||||||
{
|
|
||||||
public long Offset;
|
|
||||||
public long Size;
|
|
||||||
public NodeHeader EntrySet;
|
|
||||||
public int EntryIndex;
|
|
||||||
public Offsets TreeOffsets;
|
|
||||||
public TEntry Entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct NodeHeader
|
|
||||||
{
|
|
||||||
public int Index;
|
|
||||||
public int EntryCount;
|
|
||||||
public long OffsetEnd;
|
|
||||||
|
|
||||||
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 (EntryCount <= 0 || maxEntryCount < EntryCount)
|
|
||||||
return ResultFs.InvalidBucketTreeNodeEntryCount.Log();
|
|
||||||
|
|
||||||
if (OffsetEnd < 0)
|
|
||||||
return ResultFs.InvalidBucketTreeNodeOffset.Log();
|
|
||||||
|
|
||||||
return Result.Success;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[NonCopyable]
|
|
||||||
private struct NodeBuffer : IDisposable
|
|
||||||
{
|
|
||||||
private MemoryResource _allocator;
|
|
||||||
private Buffer _header;
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
Assert.SdkAssert(_header.IsNull);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly MemoryResource GetAllocator() => _allocator;
|
|
||||||
|
|
||||||
public bool Allocate(MemoryResource allocator, int nodeSize)
|
|
||||||
{
|
|
||||||
Assert.SdkRequires(_header.IsNull);
|
|
||||||
|
|
||||||
_allocator = allocator;
|
|
||||||
_header = allocator.Allocate(nodeSize, BufferAlignment);
|
|
||||||
|
|
||||||
return !_header.IsNull;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Free()
|
|
||||||
{
|
|
||||||
if (!_header.IsNull)
|
|
||||||
{
|
|
||||||
_allocator.Deallocate(ref _header, BufferAlignment);
|
|
||||||
_header = Buffer.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
_allocator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FillZero()
|
|
||||||
{
|
|
||||||
if (!_header.IsNull)
|
|
||||||
{
|
|
||||||
_header.Span.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly ref NodeHeader GetHeader()
|
|
||||||
{
|
|
||||||
Assert.SdkRequiresGreaterEqual(_header.Length * sizeof(long), Unsafe.SizeOf<NodeHeader>());
|
|
||||||
|
|
||||||
return ref Unsafe.As<byte, NodeHeader>(ref _header.Span[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly Span<byte> GetBuffer()
|
|
||||||
{
|
|
||||||
return _header.Span;
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly BucketTreeNode<TEntry> GetNode<TEntry>() where TEntry : unmanaged
|
|
||||||
{
|
|
||||||
return new BucketTreeNode<TEntry>(GetBuffer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct StorageNode
|
|
||||||
{
|
|
||||||
private Offset _start;
|
private Offset _start;
|
||||||
private int _count;
|
private int _count;
|
||||||
private int _index;
|
private int _index;
|
||||||
|
@ -287,6 +122,173 @@ public partial class BucketTree : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Allows searching and iterating the entries in a bucket tree data structure.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>Based on nnSdk 16.2.0 (FS 16.0.0)</remarks>
|
||||||
|
public partial class BucketTree : IDisposable
|
||||||
|
{
|
||||||
|
private const uint Signature = 0x52544B42; // BKTR
|
||||||
|
private const int MaxVersion = 1;
|
||||||
|
|
||||||
|
private const int NodeSizeMin = 1024;
|
||||||
|
private const int NodeSizeMax = 1024 * 512;
|
||||||
|
|
||||||
|
private static readonly int BufferAlignment = sizeof(long);
|
||||||
|
|
||||||
|
private static int NodeHeaderSize => Unsafe.SizeOf<NodeHeader>();
|
||||||
|
|
||||||
|
private ValueSubStorage _nodeStorage;
|
||||||
|
private ValueSubStorage _entryStorage;
|
||||||
|
|
||||||
|
private NodeBuffer _nodeL1;
|
||||||
|
|
||||||
|
private long _nodeSize;
|
||||||
|
private long _entrySize;
|
||||||
|
private int _entryCount;
|
||||||
|
private int _offsetCount;
|
||||||
|
private int _entrySetCount;
|
||||||
|
private OffsetCache _offsetCache;
|
||||||
|
|
||||||
|
public struct ContinuousReadingInfo
|
||||||
|
{
|
||||||
|
private long _readSize;
|
||||||
|
private int _skipCount;
|
||||||
|
private bool _isDone;
|
||||||
|
|
||||||
|
public readonly bool CanDo() => _readSize != 0;
|
||||||
|
public bool CheckNeedScan() => --_skipCount <= 0;
|
||||||
|
public readonly bool IsDone() => _isDone;
|
||||||
|
|
||||||
|
public void Done()
|
||||||
|
{
|
||||||
|
_readSize = 0;
|
||||||
|
_isDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly long GetReadSize() => _readSize;
|
||||||
|
public void SetReadSize(long readSize) => _readSize = readSize;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
_readSize = 0;
|
||||||
|
_skipCount = 0;
|
||||||
|
_isDone = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetSkipCount(int count)
|
||||||
|
{
|
||||||
|
Assert.SdkRequiresGreaterEqual(count, 0);
|
||||||
|
|
||||||
|
_skipCount = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IContinuousReadingEntry
|
||||||
|
{
|
||||||
|
public static abstract int FragmentSizeMax { get; }
|
||||||
|
|
||||||
|
long GetVirtualOffset();
|
||||||
|
long GetPhysicalOffset();
|
||||||
|
bool IsFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ContinuousReadingParam<TEntry> where TEntry : unmanaged, IContinuousReadingEntry
|
||||||
|
{
|
||||||
|
public long Offset;
|
||||||
|
public long Size;
|
||||||
|
public NodeHeader EntrySet;
|
||||||
|
public int EntryIndex;
|
||||||
|
public Offsets TreeOffsets;
|
||||||
|
public TEntry Entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NodeHeader
|
||||||
|
{
|
||||||
|
public int Index;
|
||||||
|
public int EntryCount;
|
||||||
|
public long OffsetEnd;
|
||||||
|
|
||||||
|
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 (EntryCount <= 0 || maxEntryCount < EntryCount)
|
||||||
|
return ResultFs.InvalidBucketTreeNodeEntryCount.Log();
|
||||||
|
|
||||||
|
if (OffsetEnd < 0)
|
||||||
|
return ResultFs.InvalidBucketTreeNodeOffset.Log();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[NonCopyable]
|
||||||
|
private struct NodeBuffer : IDisposable
|
||||||
|
{
|
||||||
|
private MemoryResource _allocator;
|
||||||
|
private Buffer _header;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Assert.SdkAssert(_header.IsNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly MemoryResource GetAllocator() => _allocator;
|
||||||
|
|
||||||
|
public bool Allocate(MemoryResource allocator, int nodeSize)
|
||||||
|
{
|
||||||
|
Assert.SdkRequires(_header.IsNull);
|
||||||
|
|
||||||
|
_allocator = allocator;
|
||||||
|
_header = allocator.Allocate(nodeSize, BufferAlignment);
|
||||||
|
|
||||||
|
return !_header.IsNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free()
|
||||||
|
{
|
||||||
|
if (!_header.IsNull)
|
||||||
|
{
|
||||||
|
_allocator.Deallocate(ref _header, BufferAlignment);
|
||||||
|
_header = Buffer.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
_allocator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FillZero()
|
||||||
|
{
|
||||||
|
if (!_header.IsNull)
|
||||||
|
{
|
||||||
|
_header.Span.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly ref NodeHeader GetHeader()
|
||||||
|
{
|
||||||
|
Assert.SdkRequiresGreaterEqual(_header.Length * sizeof(long), Unsafe.SizeOf<NodeHeader>());
|
||||||
|
|
||||||
|
return ref Unsafe.As<byte, NodeHeader>(ref _header.Span[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Span<byte> GetBuffer()
|
||||||
|
{
|
||||||
|
return _header.Span;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly BucketTreeNode<TEntry> GetNode<TEntry>() where TEntry : unmanaged
|
||||||
|
{
|
||||||
|
return new BucketTreeNode<TEntry>(GetBuffer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private struct OffsetCache
|
private struct OffsetCache
|
||||||
{
|
{
|
||||||
public OffsetCache()
|
public OffsetCache()
|
||||||
|
@ -320,26 +322,24 @@ public partial class BucketTree : IDisposable
|
||||||
|
|
||||||
public struct Header
|
public struct Header
|
||||||
{
|
{
|
||||||
public uint Magic;
|
public uint HeaderSignature;
|
||||||
public uint Version;
|
public uint Version;
|
||||||
public int EntryCount;
|
public int EntryCount;
|
||||||
#pragma warning disable 414
|
public int Reserved;
|
||||||
private int _reserved;
|
|
||||||
#pragma warning restore 414
|
|
||||||
|
|
||||||
public void Format(int entryCount)
|
public void Format(int entryCount)
|
||||||
{
|
{
|
||||||
Assert.SdkRequiresLessEqual(0, entryCount);
|
Assert.SdkRequiresLessEqual(0, entryCount);
|
||||||
|
|
||||||
Magic = ExpectedMagic;
|
HeaderSignature = Signature;
|
||||||
Version = MaxVersion;
|
Version = MaxVersion;
|
||||||
EntryCount = entryCount;
|
EntryCount = entryCount;
|
||||||
_reserved = 0;
|
Reserved = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Result Verify()
|
public readonly Result Verify()
|
||||||
{
|
{
|
||||||
if (Magic != ExpectedMagic)
|
if (HeaderSignature != Signature)
|
||||||
return ResultFs.InvalidBucketTreeSignature.Log();
|
return ResultFs.InvalidBucketTreeSignature.Log();
|
||||||
|
|
||||||
if (EntryCount < 0)
|
if (EntryCount < 0)
|
||||||
|
@ -503,21 +503,19 @@ public partial class BucketTree : IDisposable
|
||||||
if (entryCount <= 0)
|
if (entryCount <= 0)
|
||||||
return ResultFs.InvalidArgument.Log();
|
return ResultFs.InvalidArgument.Log();
|
||||||
|
|
||||||
|
Result result = ResultFs.BufferAllocationFailed.Value;
|
||||||
|
|
||||||
// Allocate node.
|
// Allocate node.
|
||||||
if (!_nodeL1.Allocate(allocator, nodeSize))
|
if (_nodeL1.Allocate(allocator, nodeSize))
|
||||||
return ResultFs.BufferAllocationFailed.Log();
|
|
||||||
|
|
||||||
bool needFree = true;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Read node.
|
result = nodeStorage.Read(0, _nodeL1.GetBuffer());
|
||||||
Result res = nodeStorage.Read(0, _nodeL1.GetBuffer());
|
|
||||||
if (res.IsFailure()) return res.Miss();
|
|
||||||
|
|
||||||
// Verify node.
|
if (result.IsSuccess())
|
||||||
res = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long));
|
{
|
||||||
if (res.IsFailure()) return res.Miss();
|
result = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long));
|
||||||
|
|
||||||
|
if (result.IsSuccess())
|
||||||
|
{
|
||||||
// Validate offsets.
|
// Validate offsets.
|
||||||
int offsetCount = GetOffsetCount(nodeSize);
|
int offsetCount = GetOffsetCount(nodeSize);
|
||||||
int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount);
|
int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount);
|
||||||
|
@ -533,11 +531,12 @@ public partial class BucketTree : IDisposable
|
||||||
startOffset = node.GetBeginOffset();
|
startOffset = node.GetBeginOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (startOffset == 0 && startOffset <= node.GetBeginOffset())
|
||||||
|
{
|
||||||
long endOffset = node.GetEndOffset();
|
long endOffset = node.GetEndOffset();
|
||||||
|
|
||||||
if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset)
|
if (startOffset < endOffset)
|
||||||
return ResultFs.InvalidBucketTreeEntryOffset.Log();
|
{
|
||||||
|
|
||||||
_nodeStorage.Set(in nodeStorage);
|
_nodeStorage.Set(in nodeStorage);
|
||||||
_entryStorage.Set(in entryStorage);
|
_entryStorage.Set(in entryStorage);
|
||||||
_nodeSize = nodeSize;
|
_nodeSize = nodeSize;
|
||||||
|
@ -549,15 +548,21 @@ public partial class BucketTree : IDisposable
|
||||||
_offsetCache.Offsets.StartOffset = startOffset;
|
_offsetCache.Offsets.StartOffset = startOffset;
|
||||||
_offsetCache.Offsets.EndOffset = endOffset;
|
_offsetCache.Offsets.EndOffset = endOffset;
|
||||||
|
|
||||||
needFree = false;
|
|
||||||
|
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
if (needFree)
|
result = ResultFs.InvalidBucketTreeEntryOffset.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_nodeL1.Free();
|
_nodeL1.Free();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.IsFailure())
|
||||||
|
return result.Log();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize(long nodeSize, long endOffset)
|
public void Initialize(long nodeSize, long endOffset)
|
||||||
|
@ -795,7 +800,7 @@ public partial class BucketTree : IDisposable
|
||||||
if (entry.IsFragment())
|
if (entry.IsFragment())
|
||||||
{
|
{
|
||||||
// If we can't merge, stop looping.
|
// If we can't merge, stop looping.
|
||||||
if (readSize >= entry.FragmentSizeMax || remainingSize <= dataSize)
|
if (readSize >= TEntry.FragmentSizeMax || remainingSize <= dataSize)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Otherwise, add the current size to the merge size.
|
// Otherwise, add the current size to the merge size.
|
||||||
|
@ -911,7 +916,8 @@ public partial class BucketTree : IDisposable
|
||||||
|
|
||||||
public readonly bool IsValid() => _entryIndex >= 0;
|
public readonly bool IsValid() => _entryIndex >= 0;
|
||||||
|
|
||||||
public readonly Offsets GetTreeOffsets() => _treeOffsets;
|
[UnscopedRef]
|
||||||
|
public readonly ref readonly Offsets GetTreeOffsets() => ref _treeOffsets;
|
||||||
|
|
||||||
public readonly bool CanMoveNext()
|
public readonly bool CanMoveNext()
|
||||||
{
|
{
|
||||||
|
@ -1205,7 +1211,7 @@ public partial class BucketTree : IDisposable
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_entryIndex = 1;
|
_entryIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the new entry
|
// Read the new entry
|
||||||
|
@ -1275,6 +1281,8 @@ public partial class BucketTree : IDisposable
|
||||||
public readonly Result ScanContinuousReading<TEntry>(out ContinuousReadingInfo info, long offset, long size)
|
public readonly Result ScanContinuousReading<TEntry>(out ContinuousReadingInfo info, long offset, long size)
|
||||||
where TEntry : unmanaged, IContinuousReadingEntry
|
where TEntry : unmanaged, IContinuousReadingEntry
|
||||||
{
|
{
|
||||||
|
Assert.SdkRequires(IsValid());
|
||||||
|
|
||||||
var param = new ContinuousReadingParam<TEntry>
|
var param = new ContinuousReadingParam<TEntry>
|
||||||
{
|
{
|
||||||
Offset = offset,
|
Offset = offset,
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class IndirectStorage : IStorage
|
||||||
|
|
||||||
private struct ContinuousReadingEntry : BucketTree.IContinuousReadingEntry
|
private struct ContinuousReadingEntry : BucketTree.IContinuousReadingEntry
|
||||||
{
|
{
|
||||||
public int FragmentSizeMax => 1024 * 4;
|
public static int FragmentSizeMax => 1024 * 4;
|
||||||
|
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
// This field will be read in by BucketTree.Visitor.ScanContinuousReading
|
// This field will be read in by BucketTree.Visitor.ScanContinuousReading
|
||||||
|
|
Loading…
Reference in a new issue