public void Save(string path) { using (var fs = new FileStream(path, FileMode.Create)) using (var bs = new BinaryStream(fs, ByteConverter.Big)) { bs.WriteString(MAGIC, StringCoding.Raw); bs.Position += 4; bs.Position += 4; // Skip prefixes offset for now bs.WriteInt32(NameTrees.Count); bs.Position += NameTrees.Count * sizeof(uint); bs.AlignWithValue(0x10, 0x5E); long lastPos = bs.Position; for (int i = 0; i < NameTrees.Count; i++) { var currentTree = NameTrees[i]; bs.Position = lastPos; int baseTreePos = (int)bs.Position; // For now skip everything and go to the string tables bs.Position += 0x10; bs.Position += 0x10 * currentTree.Entries.Count; OptimizedStringTable opt = NameTrees[i].GetStringTable(); opt.SaveStream(bs); bs.AlignWithValue(0x10, 0x5E); lastPos = bs.Position; // Write where the tree is located bs.Position = 0x10 + (i * sizeof(uint)); bs.WriteInt32(baseTreePos); // Write its data bs.Position = baseTreePos; bs.WriteInt32(100); bs.WriteInt32(currentTree.Entries.Count); bs.AlignWithValue(0x10, 0x5E); for (int j = 0; j < currentTree.Entries.Count; j++) { var entry = currentTree.Entries[j]; bs.WriteUInt32(entry.SpecDBID); bs.WriteUInt32(entry.AlphabeticalID); bs.WriteInt32(opt.GetStringOffset(entry.FullName)); bs.WriteBytes(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }); } } // Write the prefix tree bs.Position = lastPos; bs.WriteInt32(Prefixes.Count); // 4 byte header // Skip entries bs.Position += Prefixes.Count * sizeof(uint); OptimizedStringTable prefixST = GetPrefixStringTable(); prefixST.SaveStream(bs); bs.Align(0x10, true); // String table bs.Position = lastPos + 4; foreach (var prefix in Prefixes) { bs.WriteInt32(prefixST.GetStringOffset(prefix.Name)); } // Write prefix tree offset bs.Position = 0x08; bs.WriteInt32((int)lastPos); } }
public void Pack(string outputFileName, bool bigEndian = true) { Console.WriteLine($"[:] GPB: Packing {Files.Count} to -> {outputFileName}.."); using var fs = new FileStream(outputFileName, FileMode.Create); using var bs = new BinaryStream(fs, bigEndian ? ByteConverter.Big : ByteConverter.Little); if (bigEndian) { bs.WriteString("3bpg", StringCoding.Raw); } else { bs.WriteString("gpb3", StringCoding.Raw); } bs.WriteInt32(0); bs.WriteInt32(HeaderSize); // Header Size bs.WriteInt32(Files.Count); // Write all file names first int baseFileNameOffset = HeaderSize + (EntrySize * Files.Count); int currentFileNameOffset = baseFileNameOffset; // Game uses bsearch, important Files = Files.OrderBy(e => e.FileName).ToList(); for (int i = 0; i < Files.Count; i++) { bs.Position = HeaderSize + (i * EntrySize); bs.WriteInt32(currentFileNameOffset); bs.Position = currentFileNameOffset; bs.WriteString(Files[i].FileName, StringCoding.ZeroTerminated); currentFileNameOffset = (int)bs.Position; } bs.AlignWithValue(0x80, 0x5E, true); int baseDataOffset = (int)bs.Position; // Align with 0x5E todo int currentFileDataOffset = baseDataOffset; // Write the buffers for (int i = 0; i < Files.Count; i++) { bs.Position = HeaderSize + (i * EntrySize) + 4; bs.WriteInt32(currentFileDataOffset); bs.WriteInt32(Files[i].FileData.Length); bs.Position = currentFileDataOffset; bs.WriteBytes(Files[i].FileData); bs.AlignWithValue(0x80, 0x5E, true); currentFileDataOffset = (int)bs.Position; } bs.Position = 0x10; bs.WriteInt32(HeaderSize); // Offset of entries bs.WriteInt32(baseFileNameOffset); bs.WriteInt32(baseDataOffset); Console.WriteLine($"[:] GPB: Done packing {Files.Count} files to {outputFileName}."); }