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;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
file struct StorageNode
|
||||
{
|
||||
private const uint ExpectedMagic = 0x52544B42; // BKTR
|
||||
private const int MaxVersion = 1;
|
||||
private static int NodeHeaderSize => Unsafe.SizeOf<BucketTree.NodeHeader>();
|
||||
|
||||
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 int _count;
|
||||
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
|
||||
{
|
||||
public OffsetCache()
|
||||
|
@ -320,26 +322,24 @@ public partial class BucketTree : IDisposable
|
|||
|
||||
public struct Header
|
||||
{
|
||||
public uint Magic;
|
||||
public uint HeaderSignature;
|
||||
public uint Version;
|
||||
public int EntryCount;
|
||||
#pragma warning disable 414
|
||||
private int _reserved;
|
||||
#pragma warning restore 414
|
||||
public int Reserved;
|
||||
|
||||
public void Format(int entryCount)
|
||||
{
|
||||
Assert.SdkRequiresLessEqual(0, entryCount);
|
||||
|
||||
Magic = ExpectedMagic;
|
||||
HeaderSignature = Signature;
|
||||
Version = MaxVersion;
|
||||
EntryCount = entryCount;
|
||||
_reserved = 0;
|
||||
Reserved = 0;
|
||||
}
|
||||
|
||||
public readonly Result Verify()
|
||||
{
|
||||
if (Magic != ExpectedMagic)
|
||||
if (HeaderSignature != Signature)
|
||||
return ResultFs.InvalidBucketTreeSignature.Log();
|
||||
|
||||
if (EntryCount < 0)
|
||||
|
@ -503,21 +503,19 @@ public partial class BucketTree : IDisposable
|
|||
if (entryCount <= 0)
|
||||
return ResultFs.InvalidArgument.Log();
|
||||
|
||||
Result result = ResultFs.BufferAllocationFailed.Value;
|
||||
|
||||
// Allocate node.
|
||||
if (!_nodeL1.Allocate(allocator, nodeSize))
|
||||
return ResultFs.BufferAllocationFailed.Log();
|
||||
|
||||
bool needFree = true;
|
||||
try
|
||||
if (_nodeL1.Allocate(allocator, nodeSize))
|
||||
{
|
||||
// Read node.
|
||||
Result res = nodeStorage.Read(0, _nodeL1.GetBuffer());
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
result = nodeStorage.Read(0, _nodeL1.GetBuffer());
|
||||
|
||||
// Verify node.
|
||||
res = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long));
|
||||
if (res.IsFailure()) return res.Miss();
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
result = _nodeL1.GetHeader().Verify(0, nodeSize, sizeof(long));
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
// Validate offsets.
|
||||
int offsetCount = GetOffsetCount(nodeSize);
|
||||
int entrySetCount = GetEntrySetCount(nodeSize, entrySize, entryCount);
|
||||
|
@ -533,11 +531,12 @@ public partial class BucketTree : IDisposable
|
|||
startOffset = node.GetBeginOffset();
|
||||
}
|
||||
|
||||
if (startOffset == 0 && startOffset <= node.GetBeginOffset())
|
||||
{
|
||||
long endOffset = node.GetEndOffset();
|
||||
|
||||
if (startOffset < 0 || startOffset > node.GetBeginOffset() || startOffset >= endOffset)
|
||||
return ResultFs.InvalidBucketTreeEntryOffset.Log();
|
||||
|
||||
if (startOffset < endOffset)
|
||||
{
|
||||
_nodeStorage.Set(in nodeStorage);
|
||||
_entryStorage.Set(in entryStorage);
|
||||
_nodeSize = nodeSize;
|
||||
|
@ -549,15 +548,21 @@ public partial class BucketTree : IDisposable
|
|||
_offsetCache.Offsets.StartOffset = startOffset;
|
||||
_offsetCache.Offsets.EndOffset = endOffset;
|
||||
|
||||
needFree = false;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (needFree)
|
||||
}
|
||||
|
||||
result = ResultFs.InvalidBucketTreeEntryOffset.Value;
|
||||
}
|
||||
}
|
||||
|
||||
_nodeL1.Free();
|
||||
}
|
||||
|
||||
if (result.IsFailure())
|
||||
return result.Log();
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
public void Initialize(long nodeSize, long endOffset)
|
||||
|
@ -795,7 +800,7 @@ public partial class BucketTree : IDisposable
|
|||
if (entry.IsFragment())
|
||||
{
|
||||
// If we can't merge, stop looping.
|
||||
if (readSize >= entry.FragmentSizeMax || remainingSize <= dataSize)
|
||||
if (readSize >= TEntry.FragmentSizeMax || remainingSize <= dataSize)
|
||||
break;
|
||||
|
||||
// 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 Offsets GetTreeOffsets() => _treeOffsets;
|
||||
[UnscopedRef]
|
||||
public readonly ref readonly Offsets GetTreeOffsets() => ref _treeOffsets;
|
||||
|
||||
public readonly bool CanMoveNext()
|
||||
{
|
||||
|
@ -1205,7 +1211,7 @@ public partial class BucketTree : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
_entryIndex = 1;
|
||||
_entryIndex = -1;
|
||||
}
|
||||
|
||||
// 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)
|
||||
where TEntry : unmanaged, IContinuousReadingEntry
|
||||
{
|
||||
Assert.SdkRequires(IsValid());
|
||||
|
||||
var param = new ContinuousReadingParam<TEntry>
|
||||
{
|
||||
Offset = offset,
|
||||
|
|
|
@ -52,7 +52,7 @@ public class IndirectStorage : IStorage
|
|||
|
||||
private struct ContinuousReadingEntry : BucketTree.IContinuousReadingEntry
|
||||
{
|
||||
public int FragmentSizeMax => 1024 * 4;
|
||||
public static int FragmentSizeMax => 1024 * 4;
|
||||
|
||||
#pragma warning disable CS0649
|
||||
// This field will be read in by BucketTree.Visitor.ScanContinuousReading
|
||||
|
|
Loading…
Reference in a new issue