diff --git a/src/LibHac/HashHelpers.cs b/src/LibHac/HashHelpers.cs index b1412c38..b30778a6 100644 --- a/src/LibHac/HashHelpers.cs +++ b/src/LibHac/HashHelpers.cs @@ -52,6 +52,23 @@ namespace LibHac return (candidate == 2); } + public static int GetHashTableEntryCount(int entries) + { + uint count = (uint) entries; + if (entries < 3) + count = 3; + else if (count < 19) + count |= 1; + else + { + while (count % 2 == 0 || count % 3 == 0 || count % 5 == 0 || count % 7 == 0 || count % 11 == 0 || count % 13 == 0 || count % 17 == 0) + { + count++; + } + } + return (int) count; + } + public static int GetPrime(int min) { if (min < 0) @@ -68,6 +85,7 @@ namespace LibHac //outside of our predefined table. //compute the hard way. + for (int i = (min | 1); i < int.MaxValue; i += 2) { if (IsPrime(i) && ((i - 1) % HashPrime != 0)) diff --git a/src/LibHac/IO/PathTools.cs b/src/LibHac/IO/PathTools.cs index 68499cd8..42001f66 100644 --- a/src/LibHac/IO/PathTools.cs +++ b/src/LibHac/IO/PathTools.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; namespace LibHac.IO @@ -99,14 +100,16 @@ namespace LibHac.IO public static ReadOnlySpan GetParentDirectory(ReadOnlySpan path) { + Debug.Assert(IsNormalized(path)); + int i = path.Length - 1; // A trailing separator should be ignored - if (path.Length > 0 && path[i] == '/') i--; + if (path[i] == '/') i--; - while (i >= 0 && path[i] != '/') i--; + while (i >= 1 && path[i] != '/') i--; - if (i < 1) return new ReadOnlySpan(new[] { (byte)'/' }); + i = Math.Max(i, 1); return path.Slice(0, i); } diff --git a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs index fc160f3e..05d91937 100644 --- a/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs +++ b/src/LibHac/IO/RomFs/HierarchicalRomFileTable.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -56,7 +57,7 @@ namespace LibHac.IO.RomFs public bool OpenFile(string path, out RomFileInfo fileInfo) { - FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key, out _); + FindFileRecursive(GetUtf8Bytes(path), out RomEntryKey key); if (FileTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -82,7 +83,7 @@ namespace LibHac.IO.RomFs public bool OpenDirectory(string path, out FindPosition position) { - FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key, out _); + FindDirectoryRecursive(GetUtf8Bytes(path), out RomEntryKey key); if (DirectoryTable.TryGetValue(ref key, out RomKeyValuePair keyValuePair)) { @@ -113,33 +114,48 @@ namespace LibHac.IO.RomFs public bool FindNextFile(ref FindPosition position, out RomFileInfo info, out string name) { - if (FileTable.TryGetValue(position.NextFile, out RomKeyValuePair keyValuePair)) + if (position.NextFile == -1) { - position.NextFile = keyValuePair.Value.NextSibling; - info = keyValuePair.Value.Info; - name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray()); - return true; + info = default; + name = default; + return false; } - info = default; - name = default; - return false; + ref FileRomEntry entry = ref FileTable.GetValueReference(position.NextFile, out Span nameBytes); + position.NextFile = entry.NextSibling; + info = entry.Info; + + name = GetUtf8String(nameBytes); + + return true; } public bool FindNextDirectory(ref FindPosition position, out string name) { - if (DirectoryTable.TryGetValue(position.NextDirectory, out RomKeyValuePair keyValuePair)) + if (position.NextDirectory == -1) { - position.NextDirectory = keyValuePair.Value.NextSibling; - name = Encoding.UTF8.GetString(keyValuePair.Key.Name.ToArray()); - return true; + name = default; + return false; } - name = default; - return false; + ref DirectoryRomEntry entry = ref DirectoryTable.GetValueReference(position.NextDirectory, out Span nameBytes); + position.NextDirectory = entry.NextSibling; + name = GetUtf8String(nameBytes); + + return true; } - public void CreateRootDirectory() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetUtf8String(ReadOnlySpan value) + { +#if NETFRAMEWORK + return Encoding.UTF8.GetString(value.ToArray()); +#else + return Encoding.UTF8.GetString(value); +#endif + } + + private void CreateRootDirectory() { var key = new RomEntryKey(ReadOnlySpan.Empty, 0); var entry = new DirectoryRomEntry(); @@ -155,158 +171,144 @@ namespace LibHac.IO.RomFs path = PathTools.Normalize(path); ReadOnlySpan pathBytes = GetUtf8Bytes(path); - ReadOnlySpan parentPath = PathTools.GetParentDirectory(pathBytes); - CreateDirectoryRecursiveInternal(parentPath); - - FindFileRecursive(pathBytes, out RomEntryKey key, out RomKeyValuePair parentEntry); - - if (EntryExists(ref key)) - { - throw new ArgumentException("Path already exists."); - } - - var entry = new FileRomEntry(); - entry.NextSibling = -1; - entry.Info = fileInfo; - - int offset = FileTable.Insert(ref key, ref entry); - - if (parentEntry.Value.Pos.NextFile == -1) - { - parentEntry.Value.Pos.NextFile = offset; - - DirectoryTable.TrySetValue(ref parentEntry.Key, ref parentEntry.Value); - return; - } - - int nextOffset = parentEntry.Value.Pos.NextFile; - - while (FileTable.TryGetValue(nextOffset, out RomKeyValuePair chainEntry)) - { - if (chainEntry.Value.NextSibling == -1) - { - chainEntry.Value.NextSibling = offset; - FileTable.TrySetValue(ref chainEntry.Key, ref chainEntry.Value); - - return; - } - - nextOffset = chainEntry.Value.NextSibling; - } - } - - public void CreateDirectoryRecursive(string path) - { - path = PathTools.Normalize(path); - - CreateDirectoryRecursiveInternal(GetUtf8Bytes(path)); - } - - private void CreateDirectoryRecursiveInternal(ReadOnlySpan path) - { - for (int i = 1; i < path.Length; i++) - { - if (path[i] == '/') - { - ReadOnlySpan subPath = path.Slice(0, i); - CreateDirectoryInternal(subPath); - } - } - - CreateDirectoryInternal(path); + CreateFileRecursiveInternal(pathBytes, ref fileInfo); } public void CreateDirectory(string path) { path = PathTools.Normalize(path); - CreateDirectoryInternal(GetUtf8Bytes(path)); + CreateDirectoryRecursive(GetUtf8Bytes(path)); } - private void CreateDirectoryInternal(ReadOnlySpan path) + private void CreateDirectoryRecursive(ReadOnlySpan path) { - FindDirectoryRecursive(path, out RomEntryKey key, out RomKeyValuePair parentEntry); + var parser = new PathParser(path); + var key = new RomEntryKey(); - if (EntryExists(ref key)) + int prevOffset = 0; + + while (parser.TryGetNext(out key.Name)) { - return; - // throw new ArgumentException("Path already exists."); - } - - var entry = new DirectoryRomEntry(); - entry.NextSibling = -1; - entry.Pos.NextDirectory = -1; - entry.Pos.NextFile = -1; - - int offset = DirectoryTable.Insert(ref key, ref entry); - - if (parentEntry.Value.Pos.NextDirectory == -1) - { - parentEntry.Value.Pos.NextDirectory = offset; - - DirectoryTable.TrySetValue(ref parentEntry.Key, ref parentEntry.Value); - return; - } - - int nextOffset = parentEntry.Value.Pos.NextDirectory; - - while (nextOffset != -1) - { - DirectoryTable.TryGetValue(nextOffset, out RomKeyValuePair chainEntry); - if (chainEntry.Value.NextSibling == -1) + int offset = DirectoryTable.GetOffsetFromKey(ref key); + if (offset < 0) { - chainEntry.Value.NextSibling = offset; - DirectoryTable.TrySetValue(ref chainEntry.Key, ref chainEntry.Value); + ref DirectoryRomEntry entry = ref DirectoryTable.Insert(ref key, out offset, out _); + entry.NextSibling = -1; + entry.Pos.NextDirectory = -1; + entry.Pos.NextFile = -1; - return; + ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); + + if (parent.Pos.NextDirectory == -1) + { + parent.Pos.NextDirectory = offset; + } + else + { + ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); + + while (chain.NextSibling != -1) + { + chain = ref DirectoryTable.GetValueReference(chain.NextSibling); + } + + chain.NextSibling = offset; + } } - nextOffset = chainEntry.Value.NextSibling; + prevOffset = offset; + key.Parent = offset; } } - private void FindFileRecursive(ReadOnlySpan path, out RomEntryKey key, out RomKeyValuePair parentEntry) + private void CreateFileRecursiveInternal(ReadOnlySpan path, ref RomFileInfo fileInfo) { var parser = new PathParser(path); - FindParentDirectoryRecursive(ref parser, out parentEntry); + var key = new RomEntryKey(); - key = new RomEntryKey(parser.GetCurrent(), parentEntry.Offset); + parser.TryGetNext(out key.Name); + int prevOffset = 0; + + while (!parser.IsFinished()) + { + int offset = DirectoryTable.GetOffsetFromKey(ref key); + if (offset < 0) + { + ref DirectoryRomEntry entry = ref DirectoryTable.Insert(ref key, out offset, out _); + entry.NextSibling = -1; + entry.Pos.NextDirectory = -1; + entry.Pos.NextFile = -1; + + ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); + + if (parent.Pos.NextDirectory == -1) + { + parent.Pos.NextDirectory = offset; + } + else + { + ref DirectoryRomEntry chain = ref DirectoryTable.GetValueReference(parent.Pos.NextDirectory); + + while (chain.NextSibling != -1) + { + chain = ref DirectoryTable.GetValueReference(chain.NextSibling); + } + + chain.NextSibling = offset; + } + } + + prevOffset = offset; + key.Parent = offset; + parser.TryGetNext(out key.Name); + } + + { + ref FileRomEntry entry = ref FileTable.Insert(ref key, out int offset, out _); + entry.NextSibling = -1; + entry.Info = fileInfo; + + ref DirectoryRomEntry parent = ref DirectoryTable.GetValueReference(prevOffset); + + if (parent.Pos.NextFile == -1) + { + parent.Pos.NextFile = offset; + } + else + { + ref FileRomEntry chain = ref FileTable.GetValueReference(parent.Pos.NextFile); + + while (chain.NextSibling != -1) + { + chain = ref FileTable.GetValueReference(chain.NextSibling); + } + + chain.NextSibling = offset; + } + } } - private void FindDirectoryRecursive(ReadOnlySpan path, out RomEntryKey key, out RomKeyValuePair parentEntry) + private void FindFileRecursive(ReadOnlySpan path, out RomEntryKey key) { var parser = new PathParser(path); - FindParentDirectoryRecursive(ref parser, out parentEntry); - - ReadOnlySpan name = parser.GetCurrent(); - int parentOffset = name.Length == 0 ? 0 : parentEntry.Offset; - - key = new RomEntryKey(name, parentOffset); - } - - private void FindParentDirectoryRecursive(ref PathParser parser, out RomKeyValuePair keyValuePair) - { - keyValuePair = default; - RomEntryKey key = default; + key = default; while (parser.TryGetNext(out key.Name) && !parser.IsFinished()) { - DirectoryTable.TryGetValue(ref key, out keyValuePair); - key.Parent = keyValuePair.Offset; - } - - // The above loop won't run for top-level directories, so - // manually return the root directory for them - if (key.Parent == 0) - { - DirectoryTable.TryGetValue(0, out keyValuePair); + key.Parent = DirectoryTable.GetOffsetFromKey(ref key); } } - private bool EntryExists(ref RomEntryKey key) + private void FindDirectoryRecursive(ReadOnlySpan path, out RomEntryKey key) { - return DirectoryTable.ContainsKey(ref key) || - FileTable.ContainsKey(ref key); + var parser = new PathParser(path); + key = default; + + while (parser.TryGetNext(out key.Name) && !parser.IsFinished()) + { + key.Parent = DirectoryTable.GetOffsetFromKey(ref key); + } } [StructLayout(LayoutKind.Sequential, Pack = 4)] diff --git a/src/LibHac/IO/RomFs/RomFsDictionary.cs b/src/LibHac/IO/RomFs/RomFsDictionary.cs index cc6e75d8..2a2b49c1 100644 --- a/src/LibHac/IO/RomFs/RomFsDictionary.cs +++ b/src/LibHac/IO/RomFs/RomFsDictionary.cs @@ -29,7 +29,7 @@ namespace LibHac.IO.RomFs public RomFsDictionary(int capacity) { - int size = HashHelpers.GetPrime(capacity); + int size = HashHelpers.GetHashTableEntryCount(capacity); Buckets = new int[size]; Buckets.AsSpan().Fill(-1); @@ -85,6 +85,13 @@ namespace LibHac.IO.RomFs return true; } + public ref T GetValue(int offset, out Span name) + { + ref RomFsEntry entry = ref GetEntryReference(offset, out name); + + return ref entry.Value; + } + public bool ContainsKey(ref RomEntryKey key) => FindEntry(ref key) >= 0; public int Insert(ref RomEntryKey key, ref T value) @@ -97,7 +104,7 @@ namespace LibHac.IO.RomFs uint hashCode = key.GetRomHashCode(); int bucket = (int)(hashCode % Buckets.Length); - int newOffset = FindOffsetForInsert(ref key); + int newOffset = FindOffsetForInsert(key.Name.Length); ref RomFsEntry entry = ref GetEntryReference(newOffset, out Span name, key.Name.Length); @@ -113,9 +120,31 @@ namespace LibHac.IO.RomFs return newOffset; } - private int FindOffsetForInsert(ref RomEntryKey key) + public ref T Insert(ref RomEntryKey key, out int offset, out Span name) { - int bytesNeeded = Util.AlignUp(_sizeOfEntry + key.Name.Length, 4); + uint hashCode = key.GetRomHashCode(); + + int bucket = (int)(hashCode % Buckets.Length); + int newOffset = FindOffsetForInsert(key.Name.Length); + + ref RomFsEntry entry = ref GetEntryReference(newOffset, out name, key.Name.Length); + + entry.KeyLength = key.Name.Length; + + entry.Next = Buckets[bucket]; + entry.Parent = key.Parent; + key.Name.CopyTo(name); + + Buckets[bucket] = newOffset; + _count++; + + offset = newOffset; + return ref entry.Value; + } + + private int FindOffsetForInsert(int nameLength) + { + int bytesNeeded = Util.AlignUp(_sizeOfEntry + nameLength, 4); if (_length + bytesNeeded > _capacity) { @@ -149,6 +178,27 @@ namespace LibHac.IO.RomFs return i; } + public int GetOffsetFromKey(ref RomEntryKey key) + { + uint hashCode = key.GetRomHashCode(); + int index = (int)(hashCode % Buckets.Length); + int i = Buckets[index]; + + while (i != -1) + { + ref RomFsEntry entry = ref GetEntryReference(i, out Span name); + + if (key.Parent == entry.Parent && key.Name.SequenceEqual(name)) + { + break; + } + + i = entry.Next; + } + + return i; + } + private void EnsureCapacityBytes(int value) { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); @@ -216,6 +266,20 @@ namespace LibHac.IO.RomFs Buckets = newBuckets; } + public ref T GetValueReference(int offset) + { + ref RomFsEntry entry = ref MemoryMarshal.Cast(Entries.AsSpan(offset))[0]; + return ref entry.Value; + } + + public ref T GetValueReference(int offset, out Span name) + { + ref RomFsEntry entry = ref MemoryMarshal.Cast(Entries.AsSpan(offset))[0]; + + name = Entries.AsSpan(offset + _sizeOfEntry, entry.KeyLength); + return ref entry.Value; + } + private ref RomFsEntry GetEntryReference(int offset) { return ref MemoryMarshal.Cast(Entries.AsSpan(offset))[0];