/// <summary> /// Reads BND4 data from a BinaryReaderEx. /// </summary> internal override void Read(BinaryReaderEx br) { br.BigEndian = false; br.AssertASCII("BND4"); Flag1 = br.ReadBoolean(); Flag2 = br.ReadBoolean(); br.AssertByte(0); br.AssertByte(0); BigEndian = br.AssertInt32(0x00010000, 0x00000100) == 0x00000100; br.BigEndian = BigEndian; int fileCount = br.ReadInt32(); // Header size br.AssertInt64(0x40); Timestamp = br.ReadFixStr(8); // File header size long fileHeaderSize = br.ReadInt64(); long dataStart = br.ReadInt64(); Unicode = br.ReadBoolean(); Format = br.ReadEnum8 <Binder.Format>(); Extended = br.AssertByte(0, 1, 4, 0x80); br.AssertByte(0); if (fileHeaderSize != Binder.FileHeaderSize(Format)) { throw new FormatException($"File header size 0x{fileHeaderSize} unexpected for format {Format}"); } br.AssertInt32(0); long hashGroupsOffset = 0; if (Extended == 4) { hashGroupsOffset = br.ReadInt64(); } else { br.AssertInt64(0); } Files = new List <BinderFile>(fileCount); for (int i = 0; i < fileCount; i++) { Files.Add(ReadFile(br, Unicode, Format)); } }
/// <summary> /// Writes BND4 data to a BinaryWriterEx. /// </summary> internal override void Write(BinaryWriterEx bw) { bw.BigEndian = BigEndian; bw.WriteASCII("BND4"); bw.WriteBoolean(Flag1); bw.WriteBoolean(Flag2); bw.WriteByte(0); bw.WriteByte(0); bw.WriteInt32(0x10000); bw.WriteInt32(Files.Count); bw.WriteInt64(0x40); bw.WriteFixStr(Timestamp, 8); bw.WriteInt64(Binder.FileHeaderSize(Format)); bw.ReserveInt64("DataStart"); bw.WriteBoolean(Unicode); bw.WriteByte((byte)Format); bw.WriteByte(Extended); bw.WriteByte(0); bw.WriteInt32(0); if (Extended == 4) { bw.ReserveInt64("HashGroups"); } else { bw.WriteInt64(0); } for (int i = 0; i < Files.Count; i++) { WriteFile(Files[i], bw, i, Format); } if (Binder.HasName(Format)) { for (int i = 0; i < Files.Count; i++) { BinderFile file = Files[i]; bw.FillUInt32($"FileName{i}", (uint)bw.Position); if (Unicode) { bw.WriteUTF16(file.Name, true); } else { bw.WriteShiftJIS(file.Name, true); } } } if (Extended == 4) { uint groupCount = 0; for (uint p = (uint)Files.Count / 7; p <= 100000; p++) { if (SFUtil.IsPrime(p)) { groupCount = p; break; } } if (groupCount == 0) { throw new InvalidOperationException("Hash group count not determined in BND4."); } var hashLists = new List <PathHash> [groupCount]; for (int i = 0; i < groupCount; i++) { hashLists[i] = new List <PathHash>(); } for (int i = 0; i < Files.Count; i++) { var pathHash = new PathHash(i, Files[i].Name); uint group = pathHash.Hash % groupCount; hashLists[group].Add(pathHash); } for (int i = 0; i < groupCount; i++) { hashLists[i].Sort((ph1, ph2) => ph1.Hash.CompareTo(ph2.Hash)); } var hashGroups = new List <HashGroup>(); var pathHashes = new List <PathHash>(); int count = 0; foreach (List <PathHash> hashList in hashLists) { int index = count; foreach (PathHash pathHash in hashList) { pathHashes.Add(pathHash); count++; } hashGroups.Add(new HashGroup(index, count - index)); } bw.Pad(0x8); bw.FillInt64("HashGroups", bw.Position); bw.ReserveInt64("PathHashes"); bw.WriteUInt32(groupCount); bw.WriteInt32(0x00080810); foreach (HashGroup hashGroup in hashGroups) { hashGroup.Write(bw); } // No padding after section 1 bw.FillInt64("PathHashes", bw.Position); foreach (PathHash pathHash in pathHashes) { pathHash.Write(bw); } } bw.FillInt64("DataStart", bw.Position); for (int i = 0; i < Files.Count; i++) { BinderFile file = Files[i]; if (file.Bytes.LongLength > 0) { bw.Pad(0x10); } bw.FillUInt32($"FileData{i}", (uint)bw.Position); int compressedSize = file.Bytes.Length; if (Binder.IsCompressed(file.Flags)) { if (Format == Binder.Format.x2E) { byte[] bytes = DCX.Compress(file.Bytes, DCX.Type.DemonsSoulsEDGE); bw.WriteBytes(bytes); compressedSize = bytes.Length; } else { compressedSize = SFUtil.WriteZlib(bw, 0x9C, file.Bytes); } } else { bw.WriteBytes(file.Bytes); } bw.FillInt64($"CompressedSize{i}", compressedSize); } }