using System.Text; using KeepersCompound.LGS.Database.Chunks; using Serilog; namespace KeepersCompound.LGS.Database; public class DbFile { public struct FHeader { public uint TocOffset { get; set; } public Version Version { get; } public string Deadbeef { get; } public FHeader(BinaryReader reader) { TocOffset = reader.ReadUInt32(); Version = new Version(reader); reader.ReadBytes(256); Deadbeef = BitConverter.ToString(reader.ReadBytes(4)); } public readonly void Write(BinaryWriter writer) { writer.Write(TocOffset); Version.Write(writer); writer.Write(new byte[256]); writer.Write(Array.ConvertAll(Deadbeef.Split('-'), s => byte.Parse(s, System.Globalization.NumberStyles.HexNumber))); } } public struct TableOfContents { public struct Entry { public string Name; public uint Offset; public uint Size; public Entry(BinaryReader reader) { Name = reader.ReadNullString(12); Offset = reader.ReadUInt32(); Size = reader.ReadUInt32(); } public override readonly string ToString() { return $"Name: {Name}, Offset: {Offset}, Size: {Size}"; } public readonly void Write(BinaryWriter writer) { writer.WriteNullString(Name, 12); writer.Write(Offset); writer.Write(Size); } } public uint ItemCount { get; } public List Items { get; } public TableOfContents(BinaryReader reader) { ItemCount = reader.ReadUInt32(); Items = new List(); for (var i = 0; i < ItemCount; i++) Items.Add(new Entry(reader)); Items.Sort((a, b) => a.Offset.CompareTo(b.Offset)); } public readonly void Write(BinaryWriter writer) { writer.Write(ItemCount); foreach (var entry in Items) { entry.Write(writer); } } } public FHeader Header { get; private set; } public TableOfContents Toc { get; } public Dictionary Chunks { get; set; } public DbFile(string filename) { Log.Information("Loading DbFile: {Path}", filename); if (!File.Exists(filename)) { Log.Error("Failed to load DbFile. File does not exist."); throw new FileNotFoundException(); } using MemoryStream stream = new(File.ReadAllBytes(filename)); using BinaryReader reader = new(stream, Encoding.UTF8, false); Header = new(reader); stream.Seek(Header.TocOffset, SeekOrigin.Begin); Toc = new(reader); Chunks = new Dictionary(); foreach (var entry in Toc.Items) { var chunk = NewChunk(entry.Name); chunk.Read(reader, entry); Chunks.Add(entry.Name, chunk); } } public void Save(string filename) { Log.Information("Saving DbFile: {Path}", filename); using var stream = File.Open(filename, FileMode.Create); using var writer = new BinaryWriter(stream, Encoding.UTF8, false); Header.Write(writer); for (var i = 0; i < Toc.ItemCount; i++) { var item = Toc.Items[i]; var pos = stream.Position; var chunk = Chunks[item.Name]; chunk.Write(writer); var size = stream.Position - pos - 24; item.Offset = (uint)pos; item.Size = (uint)size; Toc.Items[i] = item; } var tocOffset = (uint)stream.Position; Toc.Write(writer); stream.Seek(0, SeekOrigin.Begin); writer.Write(tocOffset); } public bool TryGetChunk(string name, out T chunk) { if (Chunks.TryGetValue(name, out var rawChunk)) { chunk = (T)rawChunk; return true; } chunk = default; return false; } private static IChunk NewChunk(string entryName) { return entryName switch { // "AI_ROOM_DB" => new AiRoomDb(), // "AICONVERSE" => new AiConverseChunk(), "GAM_FILE" => new GamFile(), "TXLIST" => new TxList(), "WREXT" => new WorldRep(), "BRLIST" => new BrList(), "LM_PARAM" => new LmParams(), "RENDPARAMS" => new RendParams(), "P$ModelName" => new PropertyChunk(), "P$Scale" => new PropertyChunk(), "P$RenderTyp" => new PropertyChunk(), "P$JointPos" => new PropertyChunk(), "P$Immobile" => new PropertyChunk(), "P$StatShad" => new PropertyChunk(), "P$OTxtRepr0" => new PropertyChunk(), "P$OTxtRepr1" => new PropertyChunk(), "P$OTxtRepr2" => new PropertyChunk(), "P$OTxtRepr3" => new PropertyChunk(), "P$Light" => new PropertyChunk(), "P$AnimLight" => new PropertyChunk(), "P$LightColo" => new PropertyChunk(), "P$Spotlight" => new PropertyChunk(), "P$SpotAmb" => new PropertyChunk(), "P$RenderAlp" => new PropertyChunk(), "LD$MetaProp" => new LinkDataMetaProp(), _ when entryName.StartsWith("L$") => new LinkChunk(), _ when entryName.StartsWith("P$") => new PropertyChunk(), _ => new GenericChunk(), }; } }