using LibBundle.Records; using System.Collections.Generic; using System.IO; using System.Text; namespace LibBundle { public class IndexContainer { public readonly BundleContainer BundleContainer; public readonly BundleRecord[] Bundles; public readonly FileRecord[] Files; public readonly DirectoryRecord[] Directorys; public readonly Dictionary FindFiles = new(); public readonly HashSet Paths = new(); public readonly byte[] directoryBundleData; protected static BinaryReader tmp; public IndexContainer(string path) : this(tmp = new BinaryReader(File.OpenRead(path))) { tmp.Close(); tmp = null; } public IndexContainer(BinaryReader br) { BundleContainer = new BundleContainer(br); var data = BundleContainer.Read(br); data.Seek(0, SeekOrigin.Begin); var databr = new BinaryReader(data); var bundleCount = databr.ReadInt32(); Bundles = new BundleRecord[bundleCount]; for (var i = 0; i < bundleCount; i++) Bundles[i] = new BundleRecord(databr) { bundleIndex = i }; var fileCount = databr.ReadInt32(); Files = new FileRecord[fileCount]; for (var i = 0; i < fileCount; i++) { var f = new FileRecord(databr); Files[i] = f; FindFiles[f.NameHash] = f; var b = Bundles[f.BundleIndex]; f.bundleRecord = b; b.Files.Add(f); } var directoryCount = databr.ReadInt32(); Directorys = new DirectoryRecord[directoryCount]; for (var i = 0; i < directoryCount; i++) Directorys[i] = new DirectoryRecord(databr); var tmp = databr.BaseStream.Position; directoryBundleData = databr.ReadBytes((int)(databr.BaseStream.Length - tmp)); databr.BaseStream.Seek(tmp, SeekOrigin.Begin); var directoryBundle = new BundleContainer(databr); var br2 = new BinaryReader(directoryBundle.Read(databr), Encoding.UTF8); // Array.Sort(Directorys, new Comparison((dr1, dr2) => { return dr1.Offset > dr2.Offset ? 1 : -1; })); foreach (var d in Directorys) { var temp = new List(); var Base = false; br2.BaseStream.Seek(d.Offset, SeekOrigin.Begin); while (br2.BaseStream.Position - d.Offset <= d.Size - 4) { var index = br2.ReadInt32(); if (index == 0) { Base = !Base; if (Base) temp.Clear(); } else { index -= 1; var sb = new StringBuilder(); char c; while ((c = br2.ReadChar()) != 0) sb.Append(c); var str = sb.ToString(); if (index < temp.Count) str = temp[index] + str; if (Base) temp.Add(str); else { Paths.Add(str); var f = FindFiles[FNV1a64Hash(str)]; f.path = str; d.children.Add(f); f.parent = d; } } } } br2.Close(); } public virtual void Save(string path) { var bw = new BinaryWriter(new MemoryStream()); bw.Write(Bundles.Length); foreach (var b in Bundles) { bw.Write(b.NameLength); bw.Write(Encoding.UTF8.GetBytes(b.Name), 0, b.NameLength); bw.Write(b.UncompressedSize); } bw.Write(Files.Length); foreach (var f in Files) { bw.Write(f.NameHash); bw.Write(f.BundleIndex); bw.Write(f.Offset); bw.Write(f.Size); } bw.Write(Directorys.Length); foreach (var d in Directorys) { bw.Write(d.NameHash); bw.Write(d.Offset); bw.Write(d.Size); bw.Write(d.RecursiveSize); } bw.Write(directoryBundleData); bw.Flush(); BundleContainer.Save(bw.BaseStream, path); bw.Close(); } public virtual byte[] Save() { using var bw = new BinaryWriter(new MemoryStream()); bw.Write(Bundles.Length); foreach (var b in Bundles) { bw.Write(b.NameLength); bw.Write(Encoding.UTF8.GetBytes(b.Name), 0, b.NameLength); bw.Write(b.UncompressedSize); } bw.Write(Files.Length); foreach (var f in Files) { bw.Write(f.NameHash); bw.Write(f.BundleIndex); bw.Write(f.Offset); bw.Write(f.Size); } bw.Write(Directorys.Length); foreach (var d in Directorys) { bw.Write(d.NameHash); bw.Write(d.Offset); bw.Write(d.Size); bw.Write(d.RecursiveSize); } bw.Write(directoryBundleData); bw.Flush(); return BundleContainer.Save(bw.BaseStream); } public virtual BundleRecord GetSmallestBundle(IList Bundles = null) { Bundles ??= this.Bundles; if (Bundles.Count == 0) return null; var result = Bundles[0]; var l = Bundles[0].UncompressedSize; foreach (var b in Bundles) if (b.UncompressedSize < l) { l = b.UncompressedSize; result = b; } return result; } public static unsafe ulong FNV1a64Hash(string str) { byte[] b; if (str.EndsWith('/')) b = Encoding.UTF8.GetBytes(str, 0, str.Length - 1); else b = Encoding.UTF8.GetBytes(str.ToLower()); //var hash = 0xCBF29CE484222325UL; // Equals to: b.Aggregate(0xCBF29CE484222325UL, (current, by) => (current ^ by) * 0x100000001B3UL); //foreach (var by in b) //hash = (hash ^ by) * 0x100000001B3UL; //return (((hash ^ 43) * 0x100000001B3UL) ^ 43) * 0x100000001B3UL; // "++" --> '+'==43 return MMHash(b, 0x1337B33FUL); } private const ulong m = 0xc6a4a7935bd1e995; private const int r = 47; public static ulong MMHash(byte[] key, ulong seed) { int len = key.Length; ulong h = seed ^ ((ulong)len * m); unsafe { fixed (byte* data = &key[0]) { int blocks = len / 8; int remainder = len & 7; ulong* b = (ulong*)data; while (blocks-- > 0) { ulong k = *b++; k *= m; k ^= k >> r; k *= m; h ^= k; h *= m; } byte* remainderBlock = (byte*)b; switch (remainder) { case 7: h ^= ((ulong)*(remainderBlock + 6)) << 48; goto case 6; case 6: h ^= ((ulong)*(remainderBlock + 5)) << 40; goto case 5; case 5: h ^= ((ulong)*(remainderBlock + 4)) << 32; goto case 4; case 4: h ^= ((ulong)*(remainderBlock + 3)) << 24; goto case 3; case 3: h ^= ((ulong)*(remainderBlock + 2)) << 16; goto case 2; case 2: h ^= ((ulong)*(remainderBlock + 1)) << 8; goto case 1; case 1: h ^= (ulong)*remainderBlock; h *= m; break; } } } h ^= h >> r; h *= m; h ^= h >> r; return h; } } }