using System; using System.Collections.Generic; using System.IO; using System.Text; namespace LibDgf.Dat { public class DatBuilder { public class ReplacementEntry { public int Index { get; set; } public DatReader SourceDat { get; set; } public int SourceIndex { get; set; } public string SourceFile { get; set; } } class NewEntry { public DatEntry ArchEntry { get; set; } = new DatEntry(); public DatEntry OrigEntry { get; set; } public int OrigIndex { get; set; } public ReplacementEntry ReplacementEntry { get; set; } } DatReader sourceDat; public List ReplacementEntries { get; } = new List(); public DatBuilder(DatReader sourceDat = null) { this.sourceDat = sourceDat; } public void Build(Stream destStream) { // Check there are no duplicated indexes HashSet indexSet = new HashSet(); foreach (var entry in ReplacementEntries) { if (entry.Index < 0) throw new InvalidOperationException("Entry with negative index present."); indexSet.Add(entry.Index); } if (indexSet.Count != ReplacementEntries.Count) { throw new InvalidOperationException("Replacement entries with non-unique IDs present."); } ReplacementEntries.Sort((x, y) => x.Index.CompareTo(y.Index)); List newEntries = new List(); // Copy over original entries if (sourceDat != null) { for (int i = 0; i < sourceDat.EntriesCount; ++i) { var e = sourceDat.GetEntry(i); newEntries.Add(new NewEntry { OrigEntry = e, OrigIndex = i }); } } // Set replacement entries foreach (var rep in ReplacementEntries) { if (rep.Index > newEntries.Count) throw new InvalidOperationException("Replacement entries results in incontinuity."); var newEntry = new NewEntry { ReplacementEntry = rep }; if (rep.Index == newEntries.Count) newEntries.Add(newEntry); else newEntries[rep.Index] = newEntry; } // Update size and position uint dataOffset = 0; for (int i = 0; i < newEntries.Count; ++i) { var newEntry = newEntries[i]; newEntry.ArchEntry.Offset = dataOffset; var repEntry = newEntry.ReplacementEntry; if (repEntry == null) { newEntry.ArchEntry.Length = newEntry.OrigEntry.Length; } else { if (repEntry.SourceDat != null) { if (repEntry.SourceFile != null) throw new InvalidOperationException("Replacement entries with both DAT and file source specified exist."); newEntry.ArchEntry.Length = repEntry.SourceDat.GetEntry(repEntry.SourceIndex).Length; } else if (repEntry.SourceFile != null) { newEntry.ArchEntry.Length = (uint)new FileInfo(repEntry.SourceFile).Length; } else { newEntry.ArchEntry.Length = 0; newEntries.RemoveAt(i); --i; } } dataOffset += newEntry.ArchEntry.Length; } // Write file BinaryWriter bw = new BinaryWriter(destStream); bw.Write("DAT\0".ToCharArray()); bw.Write(newEntries.Count); dataOffset = (uint)(((newEntries.Count + 1) * 8 + 15) & ~15); foreach (var newEntry in newEntries) { newEntry.ArchEntry.Offset += dataOffset; newEntry.ArchEntry.Write(bw); } // Do we need to 16-byte align anything after the header? if (destStream.Position < dataOffset) bw.Write(new byte[dataOffset - destStream.Position]); foreach (var newEntry in newEntries) { var repEntry = newEntry.ReplacementEntry; if (repEntry == null) { bw.Write(sourceDat.GetData(newEntry.OrigIndex)); } else { if (repEntry.SourceDat != null) { bw.Write(repEntry.SourceDat.GetData(repEntry.SourceIndex)); } else { using (FileStream fs = File.OpenRead(repEntry.SourceFile)) { fs.CopyTo(destStream); } } } } } } }