using Serilog; namespace KeepersCompound.LGS.Database.Chunks; public record LinkId { private readonly uint _data; public LinkId(uint data) { _data = data; } public uint GetId() { return _data & 0xFFFF; } public bool IsConcrete() { return (_data & 0xF0000) != 0; } public uint GetRelation() { return (_data >> 20) & 0xFFF; } public uint GetRaw() { return _data; } public void Write(BinaryWriter writer) { writer.Write(_data); } } public class LinkChunk : IChunk, IMergable { public record Link { public LinkId linkId; public int source; public int destination; public ushort relation; public Link(BinaryReader reader) { linkId = new LinkId(reader.ReadUInt32()); source = reader.ReadInt32(); destination = reader.ReadInt32(); relation = reader.ReadUInt16(); } public void Write(BinaryWriter writer) { linkId.Write(writer); writer.Write(source); writer.Write(destination); writer.Write(relation); } } public ChunkHeader Header { get; set; } public List links; public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) { links = new List(); while (reader.BaseStream.Position < entry.Offset + entry.Size + 24) { links.Add(new Link(reader)); } } public void WriteData(BinaryWriter writer) { foreach (var link in links) { link.Write(writer); } } public void Merge(IMergable other) { // !HACK: We always merge into gamesys so we can pre-trim garbage here var count = links.Count; for (var i = count - 1; i >= 0; i--) { var link = links[i]; if (link.linkId.IsConcrete()) { links.RemoveAt(i); } } if (links.Count != count) { Log.Information("Trimming excess Links in GAM: {StartCount} -> {EndCount}", count, links.Count); } links.AddRange(((LinkChunk)other).links); } } // TODO: This should be generic like Property public class LinkDataMetaProp : IChunk, IMergable { public record LinkData { public LinkId linkId; public int priority; public LinkData(BinaryReader reader) { linkId = new LinkId(reader.ReadUInt32()); priority = reader.ReadInt32(); } public void Write(BinaryWriter writer) { linkId.Write(writer); writer.Write(priority); } } public ChunkHeader Header { get; set; } public int DataSize; public List linkData; public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry) { DataSize = reader.ReadInt32(); linkData = new List(); while (reader.BaseStream.Position < entry.Offset + entry.Size + 24) { linkData.Add(new LinkData(reader)); } } public void WriteData(BinaryWriter writer) { writer.Write(DataSize); foreach (var data in linkData) { data.Write(writer); } } public void Merge(IMergable other) { // !HACK: We always merge into gamesys so we can pre-trim garbage here var count = linkData.Count; for (var i = count - 1; i >= 0; i--) { var link = linkData[i]; if (link.linkId.IsConcrete()) { linkData.RemoveAt(i); } } if (linkData.Count != count) { Log.Information("Trimming excess LinkData in GAM: {StartCount} -> {EndCount}", count, linkData.Count); } linkData.AddRange(((LinkDataMetaProp)other).linkData); } }