Add LGS project (from TMV)
This commit is contained in:
parent
50bcb41134
commit
27b52f32d8
|
@ -0,0 +1,71 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public struct ChunkHeader
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Version Version { get; set; }
|
||||||
|
|
||||||
|
public ChunkHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Name = reader.ReadNullString(12);
|
||||||
|
Version = new(reader);
|
||||||
|
reader.ReadBytes(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
var writeBytes = new byte[12];
|
||||||
|
var nameBytes = Encoding.UTF8.GetBytes(Name);
|
||||||
|
nameBytes[..Math.Min(12, nameBytes.Length)].CopyTo(writeBytes, 0);
|
||||||
|
writer.Write(writeBytes);
|
||||||
|
Version.Write(writer);
|
||||||
|
writer.Write(new byte[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
|
||||||
|
public void Read(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
reader.BaseStream.Seek(entry.Offset, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
Header = new(reader);
|
||||||
|
ReadData(reader, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
Header.Write(writer);
|
||||||
|
WriteData(writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry);
|
||||||
|
public abstract void WriteData(BinaryWriter writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GenericChunk : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public byte[] Data { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
Data = reader.ReadBytes((int)entry.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IMergable
|
||||||
|
{
|
||||||
|
public abstract void Merge(IMergable other);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
class AiConverseChunk : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public uint Count;
|
||||||
|
public uint[] Ids;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
Count = reader.ReadUInt32();
|
||||||
|
Ids = new uint[Count];
|
||||||
|
for (var i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
Ids[i] = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Count);
|
||||||
|
for (var i = 0; i < Count; i++)
|
||||||
|
{
|
||||||
|
writer.Write(Ids[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
class AiRoomDb : IChunk
|
||||||
|
{
|
||||||
|
public struct Cell
|
||||||
|
{
|
||||||
|
int Size { get; set; }
|
||||||
|
uint[] CellIds { get; set; }
|
||||||
|
|
||||||
|
public Cell(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Size = reader.ReadInt32();
|
||||||
|
CellIds = new uint[Size];
|
||||||
|
for (var i = 0; i < Size; i++)
|
||||||
|
{
|
||||||
|
CellIds[i] = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Size);
|
||||||
|
for (var i = 0; i < Size; i++)
|
||||||
|
{
|
||||||
|
writer.Write(CellIds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public bool IsEmpty { get; set; }
|
||||||
|
public int ValidCellCount { get; set; }
|
||||||
|
public int CellCount { get; set; }
|
||||||
|
public Cell[] Cells { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
IsEmpty = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
ValidCellCount = reader.ReadInt32();
|
||||||
|
CellCount = reader.ReadInt16();
|
||||||
|
Cells = new Cell[CellCount];
|
||||||
|
for (var i = 0; i < CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i] = new Cell(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(IsEmpty);
|
||||||
|
writer.Write(new byte[3]);
|
||||||
|
writer.Write(ValidCellCount);
|
||||||
|
writer.Write(CellCount);
|
||||||
|
for (var i = 0; i < CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i].Write(writer);
|
||||||
|
}
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class BrList : IChunk
|
||||||
|
{
|
||||||
|
// TODO: Add better handling of the different brush types
|
||||||
|
public record Brush
|
||||||
|
{
|
||||||
|
public enum Media
|
||||||
|
{
|
||||||
|
Room = 0xFB,
|
||||||
|
Flow = 0xFC,
|
||||||
|
Object = 0xFD,
|
||||||
|
Area = 0xFE,
|
||||||
|
Light = 0xFF,
|
||||||
|
FillSolid = 0x00,
|
||||||
|
FillAir = 0x01,
|
||||||
|
FillWater = 0x02,
|
||||||
|
Flood = 0x03,
|
||||||
|
Evaporate = 0x04,
|
||||||
|
SolidToWater = 0x05,
|
||||||
|
SolidToAir = 0x06,
|
||||||
|
AirToSolid = 0x07,
|
||||||
|
WaterToSolid = 0x08,
|
||||||
|
Blockable = 0x09,
|
||||||
|
};
|
||||||
|
|
||||||
|
public record TexInfo
|
||||||
|
{
|
||||||
|
public short id;
|
||||||
|
public ushort rot;
|
||||||
|
public short scale;
|
||||||
|
public ushort x;
|
||||||
|
public ushort y;
|
||||||
|
|
||||||
|
public TexInfo(BinaryReader reader)
|
||||||
|
{
|
||||||
|
id = reader.ReadInt16();
|
||||||
|
rot = reader.ReadUInt16();
|
||||||
|
scale = reader.ReadInt16();
|
||||||
|
x = reader.ReadUInt16();
|
||||||
|
y = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public short id;
|
||||||
|
public short time;
|
||||||
|
public uint brushInfo;
|
||||||
|
public short textureId;
|
||||||
|
public Media media;
|
||||||
|
public sbyte flags;
|
||||||
|
public Vector3 position;
|
||||||
|
public Vector3 size;
|
||||||
|
public Vector3 angle;
|
||||||
|
public short currentFaceIndex;
|
||||||
|
public float gridLineSpacing;
|
||||||
|
public Vector3 gridPhaseShift;
|
||||||
|
public Vector3 gridOrientation;
|
||||||
|
public bool gridEnabled;
|
||||||
|
public byte numFaces;
|
||||||
|
public sbyte edgeSelected;
|
||||||
|
public sbyte pointSelected;
|
||||||
|
public sbyte useFlag;
|
||||||
|
public sbyte groupId;
|
||||||
|
public TexInfo[] txs;
|
||||||
|
|
||||||
|
public Brush(BinaryReader reader)
|
||||||
|
{
|
||||||
|
id = reader.ReadInt16();
|
||||||
|
time = reader.ReadInt16();
|
||||||
|
brushInfo = reader.ReadUInt32();
|
||||||
|
textureId = reader.ReadInt16();
|
||||||
|
media = (Media)reader.ReadByte();
|
||||||
|
flags = reader.ReadSByte();
|
||||||
|
position = reader.ReadVec3();
|
||||||
|
size = reader.ReadVec3();
|
||||||
|
angle = reader.ReadRotation();
|
||||||
|
currentFaceIndex = reader.ReadInt16();
|
||||||
|
gridLineSpacing = reader.ReadSingle();
|
||||||
|
gridPhaseShift = reader.ReadVec3();
|
||||||
|
gridOrientation = reader.ReadRotation();
|
||||||
|
gridEnabled = reader.ReadBoolean();
|
||||||
|
numFaces = reader.ReadByte();
|
||||||
|
edgeSelected = reader.ReadSByte();
|
||||||
|
pointSelected = reader.ReadSByte();
|
||||||
|
useFlag = reader.ReadSByte();
|
||||||
|
groupId = reader.ReadSByte();
|
||||||
|
reader.ReadBytes(4);
|
||||||
|
if ((sbyte)media >= 0)
|
||||||
|
{
|
||||||
|
txs = new TexInfo[numFaces];
|
||||||
|
for (var i = 0; i < numFaces; i++)
|
||||||
|
{
|
||||||
|
txs[i] = new TexInfo(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
txs = Array.Empty<TexInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public List<Brush> Brushes { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
Brushes = new List<Brush>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
Brushes.Add(new Brush(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class GamFile : IChunk
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public string fileName;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
fileName = reader.ReadNullString(256);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
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 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 ChunkHeader Header { get; set; }
|
||||||
|
public List<Link> links;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
links = new List<Link>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
links.Add(new Link(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Merge(IMergable other)
|
||||||
|
{
|
||||||
|
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 ChunkHeader Header { get; set; }
|
||||||
|
public int DataSize;
|
||||||
|
public List<LinkData> linkData;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
DataSize = reader.ReadInt32();
|
||||||
|
linkData = new List<LinkData>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
linkData.Add(new LinkData(reader));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Merge(IMergable other)
|
||||||
|
{
|
||||||
|
linkData.AddRange(((LinkDataMetaProp)other).linkData);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,290 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class Property
|
||||||
|
{
|
||||||
|
public int objectId;
|
||||||
|
public int length;
|
||||||
|
|
||||||
|
public virtual void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
objectId = reader.ReadInt32();
|
||||||
|
length = (int)reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertyChunk<T> : IChunk, IMergable where T : Property, new()
|
||||||
|
{
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public List<T> properties;
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
properties = new List<T>();
|
||||||
|
while (reader.BaseStream.Position < entry.Offset + entry.Size + 24)
|
||||||
|
{
|
||||||
|
var prop = new T();
|
||||||
|
prop.Read(reader);
|
||||||
|
properties.Add(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Merge(IMergable other)
|
||||||
|
{
|
||||||
|
properties.AddRange(((PropertyChunk<T>)other).properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropGeneric : Property
|
||||||
|
{
|
||||||
|
public byte[] data;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
data = reader.ReadBytes(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropBool : Property
|
||||||
|
{
|
||||||
|
public bool value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
value = reader.ReadInt32() != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropInt : Property
|
||||||
|
{
|
||||||
|
public int value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
value = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropLabel : Property
|
||||||
|
{
|
||||||
|
public string value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
value = reader.ReadNullString(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropString : Property
|
||||||
|
{
|
||||||
|
public int stringLength;
|
||||||
|
public string value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
stringLength = reader.ReadInt32();
|
||||||
|
value = reader.ReadNullString(stringLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropFloat : Property
|
||||||
|
{
|
||||||
|
public float value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
value = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropVector : Property
|
||||||
|
{
|
||||||
|
public Vector3 value;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
value = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropRenderType : Property
|
||||||
|
{
|
||||||
|
public enum Mode
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
NotRendered,
|
||||||
|
Unlit,
|
||||||
|
EditorOnly,
|
||||||
|
CoronaOnly,
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mode mode;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
mode = (Mode)reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropSlayResult : Property
|
||||||
|
{
|
||||||
|
public enum Effect
|
||||||
|
{
|
||||||
|
Normal, NoEffect, Terminate, Destroy,
|
||||||
|
}
|
||||||
|
|
||||||
|
public Effect effect;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
effect = (Effect)reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropInventoryType : Property
|
||||||
|
{
|
||||||
|
public enum Slot
|
||||||
|
{
|
||||||
|
Junk, Item, Weapon,
|
||||||
|
}
|
||||||
|
|
||||||
|
public Slot type;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
type = (Slot)reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropCollisionType : Property
|
||||||
|
{
|
||||||
|
public bool Bounce;
|
||||||
|
public bool DestroyOnImpact;
|
||||||
|
public bool SlayOnImpact;
|
||||||
|
public bool NoCollisionSound;
|
||||||
|
public bool NoResult;
|
||||||
|
public bool FullCollisionSound;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
var flags = reader.ReadUInt32();
|
||||||
|
Bounce = (flags & 0x1) != 0;
|
||||||
|
DestroyOnImpact = (flags & (0x1 << 1)) != 0;
|
||||||
|
SlayOnImpact = (flags & (0x1 << 2)) != 0;
|
||||||
|
NoCollisionSound = (flags & (0x1 << 3)) != 0;
|
||||||
|
NoResult = (flags & (0x1 << 4)) != 0;
|
||||||
|
FullCollisionSound = (flags & (0x1 << 5)) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropPosition : Property
|
||||||
|
{
|
||||||
|
public Vector3 Location;
|
||||||
|
public Vector3 Rotation;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
Location = reader.ReadVec3();
|
||||||
|
reader.ReadBytes(4); // Runtime Cell/Hint in editor
|
||||||
|
Rotation = reader.ReadRotation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropLight : Property
|
||||||
|
{
|
||||||
|
public float Brightness;
|
||||||
|
public Vector3 Offset;
|
||||||
|
public float Radius;
|
||||||
|
public float InnerRadius;
|
||||||
|
public bool QuadLit;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
Brightness = reader.ReadSingle();
|
||||||
|
Offset = reader.ReadVec3();
|
||||||
|
Radius = reader.ReadSingle();
|
||||||
|
QuadLit = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
InnerRadius = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropLightColor : Property
|
||||||
|
{
|
||||||
|
public float Hue;
|
||||||
|
public float Saturation;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
Hue = reader.ReadSingle();
|
||||||
|
Saturation = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropSpotlight : Property
|
||||||
|
{
|
||||||
|
public float InnerAngle;
|
||||||
|
public float OuterAngle;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
InnerAngle = reader.ReadSingle();
|
||||||
|
OuterAngle = reader.ReadSingle();
|
||||||
|
reader.ReadBytes(4); // Z is unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropSpotlightAndAmbient : Property
|
||||||
|
{
|
||||||
|
public float InnerAngle;
|
||||||
|
public float OuterAngle;
|
||||||
|
public float SpotBrightness;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
InnerAngle = reader.ReadSingle();
|
||||||
|
OuterAngle = reader.ReadSingle();
|
||||||
|
SpotBrightness = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Work out what this property actually does
|
||||||
|
public class PropLightBasedAlpha : Property
|
||||||
|
{
|
||||||
|
public bool Enabled;
|
||||||
|
public Vector2 AlphaRange;
|
||||||
|
public Vector2 LightRange;
|
||||||
|
|
||||||
|
public override void Read(BinaryReader reader)
|
||||||
|
{
|
||||||
|
base.Read(reader);
|
||||||
|
Enabled = reader.ReadBoolean();
|
||||||
|
reader.ReadBytes(3);
|
||||||
|
AlphaRange = reader.ReadVec2();
|
||||||
|
LightRange = new Vector2(reader.ReadInt32(), reader.ReadInt32());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class TxList : IChunk
|
||||||
|
{
|
||||||
|
public struct Item
|
||||||
|
{
|
||||||
|
public byte[] Tokens { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public Item(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Tokens = reader.ReadBytes(4);
|
||||||
|
Name = reader.ReadNullString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
|
||||||
|
public int BlockSize { get; set; }
|
||||||
|
public int ItemCount { get; set; }
|
||||||
|
public int TokenCount { get; set; }
|
||||||
|
public string[] Tokens { get; set; }
|
||||||
|
public Item[] Items { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
BlockSize = reader.ReadInt32();
|
||||||
|
ItemCount = reader.ReadInt32();
|
||||||
|
TokenCount = reader.ReadInt32();
|
||||||
|
Tokens = new string[TokenCount];
|
||||||
|
for (var i = 0; i < TokenCount; i++)
|
||||||
|
{
|
||||||
|
Tokens[i] = reader.ReadNullString(16);
|
||||||
|
}
|
||||||
|
Items = new Item[ItemCount];
|
||||||
|
for (var i = 0; i < ItemCount; i++)
|
||||||
|
{
|
||||||
|
Items[i] = new Item(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,304 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
public class WorldRep : IChunk
|
||||||
|
{
|
||||||
|
public struct WrHeader
|
||||||
|
{
|
||||||
|
// Extended header content
|
||||||
|
public int Size { get; set; }
|
||||||
|
public int Version { get; set; }
|
||||||
|
public int Flags { get; set; }
|
||||||
|
public uint LightmapFormat { get; set; }
|
||||||
|
public int LightmapScale { get; set; }
|
||||||
|
|
||||||
|
// Standard header
|
||||||
|
public uint DataSize { get; set; }
|
||||||
|
public uint CellCount { get; set; }
|
||||||
|
|
||||||
|
public WrHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Size = reader.ReadInt32();
|
||||||
|
Version = reader.ReadInt32();
|
||||||
|
Flags = reader.ReadInt32();
|
||||||
|
LightmapFormat = reader.ReadUInt32();
|
||||||
|
LightmapScale = reader.ReadInt32();
|
||||||
|
DataSize = reader.ReadUInt32();
|
||||||
|
CellCount = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly float LightmapScaleMultiplier()
|
||||||
|
{
|
||||||
|
return Math.Sign(LightmapScale) switch
|
||||||
|
{
|
||||||
|
1 => LightmapScale,
|
||||||
|
-1 => 1.0f / LightmapScale,
|
||||||
|
_ => 1.0f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Cell
|
||||||
|
{
|
||||||
|
public struct Poly
|
||||||
|
{
|
||||||
|
public byte Flags { get; set; }
|
||||||
|
public byte VertexCount { get; set; }
|
||||||
|
public byte PlaneId { get; set; }
|
||||||
|
public byte ClutId { get; set; }
|
||||||
|
public ushort Destination { get; set; }
|
||||||
|
public byte MotionIndex { get; set; }
|
||||||
|
|
||||||
|
public Poly(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Flags = reader.ReadByte();
|
||||||
|
VertexCount = reader.ReadByte();
|
||||||
|
PlaneId = reader.ReadByte();
|
||||||
|
ClutId = reader.ReadByte();
|
||||||
|
Destination = reader.ReadUInt16();
|
||||||
|
MotionIndex = reader.ReadByte();
|
||||||
|
reader.ReadByte();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct RenderPoly
|
||||||
|
{
|
||||||
|
public (Vector3, Vector3) TextureVectors { get; set; }
|
||||||
|
public (float, float) TextureBases { get; set; }
|
||||||
|
public ushort TextureId { get; set; }
|
||||||
|
public ushort CachedSurface { get; set; }
|
||||||
|
public float TextureMagnitude { get; set; }
|
||||||
|
public Vector3 Center { get; set; }
|
||||||
|
|
||||||
|
public RenderPoly(BinaryReader reader)
|
||||||
|
{
|
||||||
|
TextureVectors = (reader.ReadVec3(), reader.ReadVec3());
|
||||||
|
TextureBases = (reader.ReadSingle(), reader.ReadSingle());
|
||||||
|
TextureId = reader.ReadUInt16();
|
||||||
|
CachedSurface = reader.ReadUInt16();
|
||||||
|
TextureMagnitude = reader.ReadSingle();
|
||||||
|
Center = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct LightmapInfo
|
||||||
|
{
|
||||||
|
public (short, short) Bases { get; set; }
|
||||||
|
public short PaddedWidth { get; set; }
|
||||||
|
public byte Height { get; set; }
|
||||||
|
public byte Width { get; set; }
|
||||||
|
public uint DataPtr { get; set; }
|
||||||
|
public uint DynamicLightPtr { get; set; }
|
||||||
|
public uint AnimLightBitmask { get; set; }
|
||||||
|
|
||||||
|
public LightmapInfo(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Bases = (reader.ReadInt16(), reader.ReadInt16());
|
||||||
|
PaddedWidth = reader.ReadInt16();
|
||||||
|
Height = reader.ReadByte();
|
||||||
|
Width = reader.ReadByte();
|
||||||
|
DataPtr = reader.ReadUInt32();
|
||||||
|
DynamicLightPtr = reader.ReadUInt32();
|
||||||
|
AnimLightBitmask = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Lightmap
|
||||||
|
{
|
||||||
|
public byte[] Pixels { get; set; }
|
||||||
|
|
||||||
|
public int Layers;
|
||||||
|
public int Width;
|
||||||
|
public int Height;
|
||||||
|
public int Bpp;
|
||||||
|
|
||||||
|
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
|
||||||
|
{
|
||||||
|
var count = 1 + BitOperations.PopCount(bitmask);
|
||||||
|
var length = bytesPerPixel * width * height * count;
|
||||||
|
Pixels = reader.ReadBytes(length);
|
||||||
|
Layers = count;
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
Bpp = bytesPerPixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Vector4 GetPixel(uint layer, uint x, uint y)
|
||||||
|
{
|
||||||
|
if (layer >= Layers || x >= Width || y >= Height)
|
||||||
|
{
|
||||||
|
return Vector4.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = 0 + x * Bpp + y * Bpp * Width + layer * Bpp * Width * Height;
|
||||||
|
switch (Bpp)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
var raw1 = Pixels[idx];
|
||||||
|
return new Vector4(raw1, raw1, raw1, 255) / 255.0f;
|
||||||
|
case 2:
|
||||||
|
var raw2 = Pixels[idx] + (Pixels[idx + 1] << 8);
|
||||||
|
return new Vector4(raw2 & 31, (raw2 >> 5) & 31, (raw2 >> 10) & 31, 31) / 31.0f;
|
||||||
|
case 4:
|
||||||
|
return new Vector4(Pixels[idx + 2], Pixels[idx + 1], Pixels[idx], Pixels[idx + 3]) / 255.0f;
|
||||||
|
default:
|
||||||
|
return Vector4.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly byte[] AsBytesRgba(int layer)
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer));
|
||||||
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer));
|
||||||
|
|
||||||
|
var pIdx = layer * Bpp * Width * Height;
|
||||||
|
var length = 4 * Width * Height;
|
||||||
|
var bytes = new byte[length];
|
||||||
|
for (var i = 0; i < length; i += 4, pIdx += Bpp)
|
||||||
|
{
|
||||||
|
switch (Bpp)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
var raw1 = Pixels[pIdx];
|
||||||
|
bytes[i] = raw1;
|
||||||
|
bytes[i + 1] = raw1;
|
||||||
|
bytes[i + 2] = raw1;
|
||||||
|
bytes[i + 3] = 255;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
var raw2 = Pixels[pIdx] + (Pixels[pIdx + 1] << 8);
|
||||||
|
bytes[i] = (byte)(255 * (raw2 & 31) / 31.0f);
|
||||||
|
bytes[i + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f);
|
||||||
|
bytes[i + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f);
|
||||||
|
bytes[i + 3] = 255;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
bytes[i] = Pixels[pIdx + 2];
|
||||||
|
bytes[i + 1] = Pixels[pIdx + 1];
|
||||||
|
bytes[i + 2] = Pixels[pIdx];
|
||||||
|
bytes[i + 3] = Pixels[pIdx + 3];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte VertexCount { get; set; }
|
||||||
|
public byte PolyCount { get; set; }
|
||||||
|
public byte RenderPolyCount { get; set; }
|
||||||
|
public byte PortalPolyCount { get; set; }
|
||||||
|
public byte PlaneCount { get; set; }
|
||||||
|
public byte Medium { get; set; }
|
||||||
|
public byte Flags { get; set; }
|
||||||
|
public int PortalVertices { get; set; }
|
||||||
|
public ushort NumVList { get; set; }
|
||||||
|
public byte AnimLightCount { get; set; }
|
||||||
|
public byte MotionIndex { get; set; }
|
||||||
|
public Vector3 SphereCenter { get; set; }
|
||||||
|
public float SphereRadius { get; set; }
|
||||||
|
public Vector3[] Vertices { get; set; }
|
||||||
|
public Poly[] Polys { get; set; }
|
||||||
|
public RenderPoly[] RenderPolys { get; set; }
|
||||||
|
public uint IndexCount { get; set; }
|
||||||
|
public byte[] Indices { get; set; }
|
||||||
|
public Plane[] Planes { get; set; }
|
||||||
|
public ushort[] AnimLights { get; set; }
|
||||||
|
public LightmapInfo[] LightList { get; set; }
|
||||||
|
public Lightmap[] Lightmaps { get; set; }
|
||||||
|
public int LightIndexCount { get; set; }
|
||||||
|
public ushort[] LightIndices { get; set; }
|
||||||
|
|
||||||
|
public Cell(BinaryReader reader, int bpp)
|
||||||
|
{
|
||||||
|
VertexCount = reader.ReadByte();
|
||||||
|
PolyCount = reader.ReadByte();
|
||||||
|
RenderPolyCount = reader.ReadByte();
|
||||||
|
PortalPolyCount = reader.ReadByte();
|
||||||
|
PlaneCount = reader.ReadByte();
|
||||||
|
Medium = reader.ReadByte();
|
||||||
|
Flags = reader.ReadByte();
|
||||||
|
PortalVertices = reader.ReadInt32();
|
||||||
|
NumVList = reader.ReadUInt16();
|
||||||
|
AnimLightCount = reader.ReadByte();
|
||||||
|
MotionIndex = reader.ReadByte();
|
||||||
|
SphereCenter = reader.ReadVec3();
|
||||||
|
SphereRadius = reader.ReadSingle();
|
||||||
|
Vertices = new Vector3[VertexCount];
|
||||||
|
for (var i = 0; i < VertexCount; i++)
|
||||||
|
{
|
||||||
|
Vertices[i] = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
Polys = new Poly[PolyCount];
|
||||||
|
for (var i = 0; i < PolyCount; i++)
|
||||||
|
{
|
||||||
|
Polys[i] = new Poly(reader);
|
||||||
|
}
|
||||||
|
RenderPolys = new RenderPoly[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
RenderPolys[i] = new RenderPoly(reader);
|
||||||
|
}
|
||||||
|
IndexCount = reader.ReadUInt32();
|
||||||
|
Indices = new byte[IndexCount];
|
||||||
|
for (var i = 0; i < IndexCount; i++)
|
||||||
|
{
|
||||||
|
Indices[i] = reader.ReadByte();
|
||||||
|
}
|
||||||
|
Planes = new Plane[PlaneCount];
|
||||||
|
for (var i = 0; i < PlaneCount; i++)
|
||||||
|
{
|
||||||
|
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
AnimLights = new ushort[AnimLightCount];
|
||||||
|
for (var i = 0; i < AnimLightCount; i++)
|
||||||
|
{
|
||||||
|
AnimLights[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
LightList = new LightmapInfo[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
LightList[i] = new LightmapInfo(reader);
|
||||||
|
}
|
||||||
|
Lightmaps = new Lightmap[RenderPolyCount];
|
||||||
|
for (var i = 0; i < RenderPolyCount; i++)
|
||||||
|
{
|
||||||
|
var info = LightList[i];
|
||||||
|
Lightmaps[i] = new Lightmap(reader, info.Width, info.Height, info.AnimLightBitmask, bpp);
|
||||||
|
}
|
||||||
|
LightIndexCount = reader.ReadInt32();
|
||||||
|
LightIndices = new ushort[LightIndexCount];
|
||||||
|
for (var i = 0; i < LightIndexCount; i++)
|
||||||
|
{
|
||||||
|
LightIndices[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChunkHeader Header { get; set; }
|
||||||
|
public WrHeader DataHeader { get; set; }
|
||||||
|
public Cell[] Cells { get; set; }
|
||||||
|
|
||||||
|
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
|
||||||
|
{
|
||||||
|
DataHeader = new(reader);
|
||||||
|
var bpp = (DataHeader.LightmapFormat == 0) ? 2 : 4;
|
||||||
|
|
||||||
|
Cells = new Cell[DataHeader.CellCount];
|
||||||
|
for (var i = 0; i < DataHeader.CellCount; i++)
|
||||||
|
{
|
||||||
|
Cells[i] = new Cell(reader, bpp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: All the other info lol
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteData(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
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 readonly struct TableOfContents
|
||||||
|
{
|
||||||
|
public readonly struct Entry
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public uint Offset { get; }
|
||||||
|
public uint Size { get; }
|
||||||
|
|
||||||
|
public Entry(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Name = reader.ReadNullString(12);
|
||||||
|
Offset = reader.ReadUInt32();
|
||||||
|
Size = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
// return $"Name: {Name}, Offset: {O}"
|
||||||
|
return base.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint ItemCount { get; }
|
||||||
|
public List<Entry> Items { get; }
|
||||||
|
|
||||||
|
public TableOfContents(BinaryReader reader)
|
||||||
|
{
|
||||||
|
ItemCount = reader.ReadUInt32();
|
||||||
|
Items = new List<Entry>();
|
||||||
|
for (var i = 0; i < ItemCount; i++)
|
||||||
|
Items.Add(new Entry(reader));
|
||||||
|
Items.Sort((a, b) => a.Offset.CompareTo(b.Offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public FHeader Header { get; private set; }
|
||||||
|
public TableOfContents Toc { get; }
|
||||||
|
public Dictionary<string, IChunk> Chunks { get; set; }
|
||||||
|
|
||||||
|
public DbFile(string filename)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filename)) return;
|
||||||
|
|
||||||
|
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<string, IChunk>();
|
||||||
|
foreach (var entry in Toc.Items)
|
||||||
|
{
|
||||||
|
var chunk = NewChunk(entry.Name);
|
||||||
|
chunk.Read(reader, entry);
|
||||||
|
Chunks.Add(entry.Name, chunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
"P$ModelName" => new PropertyChunk<PropLabel>(),
|
||||||
|
"P$Scale" => new PropertyChunk<PropVector>(),
|
||||||
|
"P$RenderTyp" => new PropertyChunk<PropRenderType>(),
|
||||||
|
"P$OTxtRepr0" => new PropertyChunk<PropString>(),
|
||||||
|
"P$OTxtRepr1" => new PropertyChunk<PropString>(),
|
||||||
|
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
|
||||||
|
"P$OTxtRepr3" => new PropertyChunk<PropString>(),
|
||||||
|
"P$RenderAlp" => new PropertyChunk<PropFloat>(),
|
||||||
|
"LD$MetaProp" => new LinkDataMetaProp(),
|
||||||
|
_ when entryName.StartsWith("L$") => new LinkChunk(),
|
||||||
|
_ when entryName.StartsWith("P$") => new PropertyChunk<PropGeneric>(),
|
||||||
|
_ => new GenericChunk(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using KeepersCompound.LGS.Database.Chunks;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public class ObjectHierarchy
|
||||||
|
{
|
||||||
|
public class DarkObject
|
||||||
|
{
|
||||||
|
public int objectId;
|
||||||
|
public int parentId;
|
||||||
|
public Dictionary<string, Property> properties;
|
||||||
|
|
||||||
|
public DarkObject(int id)
|
||||||
|
{
|
||||||
|
objectId = id;
|
||||||
|
parentId = 0;
|
||||||
|
properties = new Dictionary<string, Property>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetProperty<T>(string propName) where T : Property
|
||||||
|
{
|
||||||
|
if (properties.TryGetValue(propName, out var prop))
|
||||||
|
{
|
||||||
|
return (T)prop;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<int, DarkObject> _objects;
|
||||||
|
|
||||||
|
public ObjectHierarchy(DbFile db, DbFile gam = null)
|
||||||
|
{
|
||||||
|
_objects = new Dictionary<int, DarkObject>();
|
||||||
|
|
||||||
|
T GetMergedChunk<T>(string name) where T : IMergable
|
||||||
|
{
|
||||||
|
if (db.Chunks.TryGetValue(name, out var rawChunk))
|
||||||
|
{
|
||||||
|
var chunk = (T)rawChunk;
|
||||||
|
if (gam != null && gam.Chunks.TryGetValue(name, out var rawGamChunk))
|
||||||
|
{
|
||||||
|
var gamChunk = (T)rawGamChunk;
|
||||||
|
chunk.Merge(gamChunk);
|
||||||
|
}
|
||||||
|
return chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add parentages
|
||||||
|
var metaPropLinks = GetMergedChunk<LinkChunk>("L$MetaProp");
|
||||||
|
var metaPropLinkData = GetMergedChunk<LinkDataMetaProp>("LD$MetaProp");
|
||||||
|
var length = metaPropLinks.links.Count;
|
||||||
|
for (var i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var link = metaPropLinks.links[i];
|
||||||
|
var linkData = metaPropLinkData.linkData[i];
|
||||||
|
var childId = link.source;
|
||||||
|
var parentId = link.destination;
|
||||||
|
if (!_objects.ContainsKey(childId))
|
||||||
|
{
|
||||||
|
_objects.Add(childId, new DarkObject(childId));
|
||||||
|
}
|
||||||
|
if (!_objects.ContainsKey(parentId))
|
||||||
|
{
|
||||||
|
_objects.Add(parentId, new DarkObject(parentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkData.priority == 0)
|
||||||
|
{
|
||||||
|
_objects[childId].parentId = parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddProp<T>(string name) where T : Property, new()
|
||||||
|
{
|
||||||
|
var chunk = GetMergedChunk<PropertyChunk<T>>(name);
|
||||||
|
foreach (var prop in chunk.properties)
|
||||||
|
{
|
||||||
|
var id = prop.objectId;
|
||||||
|
if (!_objects.ContainsKey(id))
|
||||||
|
{
|
||||||
|
_objects.Add(id, new DarkObject(id));
|
||||||
|
}
|
||||||
|
_objects[id].properties.TryAdd(name, prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddProp<PropLabel>("P$ModelName");
|
||||||
|
AddProp<PropVector>("P$Scale");
|
||||||
|
AddProp<PropRenderType>("P$RenderTyp");
|
||||||
|
AddProp<PropString>("P$OTxtRepr0");
|
||||||
|
AddProp<PropString>("P$OTxtRepr1");
|
||||||
|
AddProp<PropString>("P$OTxtRepr2");
|
||||||
|
AddProp<PropString>("P$OTxtRepr3");
|
||||||
|
AddProp<PropFloat>("P$RenderAlp");
|
||||||
|
}
|
||||||
|
|
||||||
|
public T GetProperty<T>(int objectId, string propName) where T : Property
|
||||||
|
{
|
||||||
|
if (!_objects.ContainsKey(objectId))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentId = objectId;
|
||||||
|
while (parentId != 0)
|
||||||
|
{
|
||||||
|
if (!_objects.TryGetValue(parentId, out var obj))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prop = obj.GetProperty<T>(propName);
|
||||||
|
if (prop != null)
|
||||||
|
{
|
||||||
|
return prop;
|
||||||
|
}
|
||||||
|
parentId = obj.parentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS.Database;
|
||||||
|
|
||||||
|
public struct Version
|
||||||
|
{
|
||||||
|
public uint Major { get; set; }
|
||||||
|
public uint Minor { get; set; }
|
||||||
|
|
||||||
|
public Version(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Major = reader.ReadUInt32();
|
||||||
|
Minor = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Write(BinaryWriter writer)
|
||||||
|
{
|
||||||
|
writer.Write(Major);
|
||||||
|
writer.Write(Minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return $"{Major}.{Minor}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static Vector3 ReadRotation(this BinaryReader reader)
|
||||||
|
{
|
||||||
|
var raw = new Vector3(reader.ReadUInt16(), reader.ReadUInt16(), reader.ReadUInt16());
|
||||||
|
return raw * 360 / (ushort.MaxValue + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3 ReadVec3(this BinaryReader reader)
|
||||||
|
{
|
||||||
|
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector2 ReadVec2(this BinaryReader reader)
|
||||||
|
{
|
||||||
|
return new Vector2(reader.ReadSingle(), reader.ReadSingle());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ReadNullString(this BinaryReader reader, int length)
|
||||||
|
{
|
||||||
|
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length));
|
||||||
|
var idx = tmpName.IndexOf('\0');
|
||||||
|
if (idx >= 0) tmpName = tmpName[..idx];
|
||||||
|
return tmpName;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
|
@ -0,0 +1,206 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
// TODO: Remove all the things that don't actually need to be stored
|
||||||
|
public class ModelFile
|
||||||
|
{
|
||||||
|
public readonly struct BHeader
|
||||||
|
{
|
||||||
|
public string Signature { get; }
|
||||||
|
public int Version { get; }
|
||||||
|
|
||||||
|
public BHeader(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Signature = reader.ReadNullString(4);
|
||||||
|
Version = reader.ReadInt32();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct MHeader
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public float Radius { get; }
|
||||||
|
public float MaxPolygonRadius { get; }
|
||||||
|
public Vector3 MaxBounds { get; }
|
||||||
|
public Vector3 MinBounds { get; }
|
||||||
|
public Vector3 Center { get; }
|
||||||
|
|
||||||
|
public ushort PolygonCount { get; }
|
||||||
|
public ushort VertexCount { get; }
|
||||||
|
public ushort ParameterCount { get; }
|
||||||
|
public byte MaterialCount { get; }
|
||||||
|
public byte VCallCount { get; }
|
||||||
|
public byte VHotCount { get; }
|
||||||
|
public byte ObjectCount { get; }
|
||||||
|
|
||||||
|
public uint ObjectOffset { get; }
|
||||||
|
public uint MaterialOffset { get; }
|
||||||
|
public uint UvOffset { get; }
|
||||||
|
public uint VHotOffset { get; }
|
||||||
|
public uint VertexOffset { get; }
|
||||||
|
public uint LightOffset { get; }
|
||||||
|
public uint NormalOffset { get; }
|
||||||
|
public uint PolygonOffset { get; }
|
||||||
|
public uint NodeOffset { get; }
|
||||||
|
|
||||||
|
public uint ModelSize { get; }
|
||||||
|
|
||||||
|
public uint AuxMaterialFlags { get; }
|
||||||
|
public uint AuxMaterialOffset { get; }
|
||||||
|
public uint AuxMaterialSize { get; }
|
||||||
|
|
||||||
|
public MHeader(BinaryReader reader, int version)
|
||||||
|
{
|
||||||
|
Name = reader.ReadNullString(8);
|
||||||
|
Radius = reader.ReadSingle();
|
||||||
|
MaxPolygonRadius = reader.ReadSingle();
|
||||||
|
MaxBounds = reader.ReadVec3();
|
||||||
|
MinBounds = reader.ReadVec3();
|
||||||
|
Center = reader.ReadVec3();
|
||||||
|
PolygonCount = reader.ReadUInt16();
|
||||||
|
VertexCount = reader.ReadUInt16();
|
||||||
|
ParameterCount = reader.ReadUInt16();
|
||||||
|
MaterialCount = reader.ReadByte();
|
||||||
|
VCallCount = reader.ReadByte();
|
||||||
|
VHotCount = reader.ReadByte();
|
||||||
|
ObjectCount = reader.ReadByte();
|
||||||
|
ObjectOffset = reader.ReadUInt32();
|
||||||
|
MaterialOffset = reader.ReadUInt32();
|
||||||
|
UvOffset = reader.ReadUInt32();
|
||||||
|
VHotOffset = reader.ReadUInt32();
|
||||||
|
VertexOffset = reader.ReadUInt32();
|
||||||
|
LightOffset = reader.ReadUInt32();
|
||||||
|
NormalOffset = reader.ReadUInt32();
|
||||||
|
PolygonOffset = reader.ReadUInt32();
|
||||||
|
NodeOffset = reader.ReadUInt32();
|
||||||
|
ModelSize = reader.ReadUInt32();
|
||||||
|
|
||||||
|
if (version == 4)
|
||||||
|
{
|
||||||
|
AuxMaterialFlags = reader.ReadUInt32();
|
||||||
|
AuxMaterialOffset = reader.ReadUInt32();
|
||||||
|
AuxMaterialSize = reader.ReadUInt32();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AuxMaterialFlags = 0;
|
||||||
|
AuxMaterialOffset = 0;
|
||||||
|
AuxMaterialSize = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Polygon
|
||||||
|
{
|
||||||
|
public ushort Index;
|
||||||
|
public ushort Data;
|
||||||
|
public byte Type;
|
||||||
|
public byte VertexCount;
|
||||||
|
public ushort Normal;
|
||||||
|
public float D;
|
||||||
|
public ushort[] VertexIndices;
|
||||||
|
public ushort[] LightIndices;
|
||||||
|
public ushort[] UvIndices;
|
||||||
|
public byte Material;
|
||||||
|
|
||||||
|
public Polygon(BinaryReader reader, int version)
|
||||||
|
{
|
||||||
|
Index = reader.ReadUInt16();
|
||||||
|
Data = reader.ReadUInt16();
|
||||||
|
Type = reader.ReadByte();
|
||||||
|
VertexCount = reader.ReadByte();
|
||||||
|
Normal = reader.ReadUInt16();
|
||||||
|
D = reader.ReadSingle();
|
||||||
|
VertexIndices = new ushort[VertexCount];
|
||||||
|
for (var i = 0; i < VertexCount; i++)
|
||||||
|
{
|
||||||
|
VertexIndices[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
LightIndices = new ushort[VertexCount];
|
||||||
|
for (var i = 0; i < VertexCount; i++)
|
||||||
|
{
|
||||||
|
LightIndices[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
UvIndices = new ushort[Type == 0x1B ? VertexCount : 0];
|
||||||
|
for (var i = 0; i < UvIndices.Length; i++)
|
||||||
|
{
|
||||||
|
UvIndices[i] = reader.ReadUInt16();
|
||||||
|
}
|
||||||
|
|
||||||
|
Material = version == 4 ? reader.ReadByte() : (byte)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Material
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public byte Type;
|
||||||
|
public byte Slot;
|
||||||
|
public uint Handle;
|
||||||
|
public float Uv;
|
||||||
|
|
||||||
|
public Material(BinaryReader reader)
|
||||||
|
{
|
||||||
|
Name = reader.ReadNullString(16);
|
||||||
|
Type = reader.ReadByte();
|
||||||
|
Slot = reader.ReadByte();
|
||||||
|
Handle = reader.ReadUInt32();
|
||||||
|
Uv = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BHeader BinHeader { get; set; }
|
||||||
|
public MHeader Header { get; set; }
|
||||||
|
public Vector3[] Vertices { get; }
|
||||||
|
public Vector2[] Uvs { get; }
|
||||||
|
public Vector3[] Normals { get; }
|
||||||
|
public Polygon[] Polygons { get; }
|
||||||
|
public Material[] Materials { get; }
|
||||||
|
|
||||||
|
public ModelFile(string filename)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filename)) return;
|
||||||
|
|
||||||
|
using MemoryStream stream = new(File.ReadAllBytes(filename));
|
||||||
|
using BinaryReader reader = new(stream, Encoding.UTF8, false);
|
||||||
|
|
||||||
|
BinHeader = new BHeader(reader);
|
||||||
|
if (BinHeader.Signature != "LGMD") return;
|
||||||
|
|
||||||
|
Header = new MHeader(reader, BinHeader.Version);
|
||||||
|
stream.Seek(Header.VertexOffset, SeekOrigin.Begin);
|
||||||
|
Vertices = new Vector3[Header.VertexCount];
|
||||||
|
for (var i = 0; i < Vertices.Length; i++)
|
||||||
|
{
|
||||||
|
Vertices[i] = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
stream.Seek(Header.UvOffset, SeekOrigin.Begin);
|
||||||
|
Uvs = new Vector2[(Header.VHotOffset - Header.UvOffset) / 8];
|
||||||
|
for (var i = 0; i < Uvs.Length; i++)
|
||||||
|
{
|
||||||
|
Uvs[i] = reader.ReadVec2();
|
||||||
|
}
|
||||||
|
stream.Seek(Header.NormalOffset, SeekOrigin.Begin);
|
||||||
|
Normals = new Vector3[(Header.PolygonOffset - Header.NormalOffset) / 12];
|
||||||
|
for (var i = 0; i < Normals.Length; i++)
|
||||||
|
{
|
||||||
|
Normals[i] = reader.ReadVec3();
|
||||||
|
}
|
||||||
|
stream.Seek(Header.PolygonOffset, SeekOrigin.Begin);
|
||||||
|
Polygons = new Polygon[Header.PolygonCount];
|
||||||
|
for (var i = 0; i < Polygons.Length; i++)
|
||||||
|
{
|
||||||
|
Polygons[i] = new Polygon(reader, BinHeader.Version);
|
||||||
|
}
|
||||||
|
stream.Seek(Header.MaterialOffset, SeekOrigin.Begin);
|
||||||
|
Materials = new Material[Header.MaterialCount];
|
||||||
|
for (var i = 0; i < Materials.Length; i++)
|
||||||
|
{
|
||||||
|
Materials[i] = new Material(reader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,449 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace KeepersCompound.LGS;
|
||||||
|
|
||||||
|
// TODO: Make this nicer to use!
|
||||||
|
// Rather than navigating through the path manager Context should hold the current campaign resources
|
||||||
|
// Campaign resources should be lazy loaded (only get the paths when we first set it as campaign)
|
||||||
|
// Campaign resources should extend off of the base game resources and just overwrite any resource paths it needs to
|
||||||
|
|
||||||
|
enum ConfigFile
|
||||||
|
{
|
||||||
|
Cam,
|
||||||
|
CamExt,
|
||||||
|
CamMod,
|
||||||
|
Game,
|
||||||
|
Install,
|
||||||
|
User,
|
||||||
|
ConfigFileCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ResourceType
|
||||||
|
{
|
||||||
|
Mission,
|
||||||
|
Object,
|
||||||
|
ObjectTexture,
|
||||||
|
Texture,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResourcePathManager
|
||||||
|
{
|
||||||
|
public record CampaignResources
|
||||||
|
{
|
||||||
|
public bool initialised = false;
|
||||||
|
public string name;
|
||||||
|
private readonly Dictionary<string, string> _missionPathMap = [];
|
||||||
|
private readonly Dictionary<string, string> _texturePathMap = [];
|
||||||
|
private readonly Dictionary<string, string> _objectPathMap = [];
|
||||||
|
private readonly Dictionary<string, string> _objectTexturePathMap = [];
|
||||||
|
|
||||||
|
public List<string> GetResourceNames(ResourceType type)
|
||||||
|
{
|
||||||
|
List<string> keys = type switch
|
||||||
|
{
|
||||||
|
ResourceType.Mission => [.. _missionPathMap.Keys],
|
||||||
|
ResourceType.Object => [.. _objectPathMap.Keys],
|
||||||
|
ResourceType.ObjectTexture => [.. _objectTexturePathMap.Keys],
|
||||||
|
ResourceType.Texture => [.. _texturePathMap.Keys],
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||||
|
};
|
||||||
|
keys.Sort();
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetResourcePath(ResourceType type, string name)
|
||||||
|
{
|
||||||
|
var map = type switch
|
||||||
|
{
|
||||||
|
ResourceType.Mission => _missionPathMap,
|
||||||
|
ResourceType.Object => _objectPathMap,
|
||||||
|
ResourceType.ObjectTexture => _objectTexturePathMap,
|
||||||
|
ResourceType.Texture => _texturePathMap,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(type)),
|
||||||
|
};
|
||||||
|
return map.TryGetValue(name.ToLower(), out var resourcePath) ? resourcePath : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialise(string misPath, string resPath)
|
||||||
|
{
|
||||||
|
foreach (var path in Directory.GetFiles(misPath))
|
||||||
|
{
|
||||||
|
var convertedPath = ConvertSeparator(path);
|
||||||
|
var ext = Path.GetExtension(convertedPath).ToLower();
|
||||||
|
if (ext == ".mis" || ext == ".cow")
|
||||||
|
{
|
||||||
|
var baseName = Path.GetFileName(convertedPath).ToLower();
|
||||||
|
_missionPathMap[baseName] = convertedPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (name, path) in GetTexturePaths(resPath))
|
||||||
|
{
|
||||||
|
_texturePathMap[name] = path;
|
||||||
|
}
|
||||||
|
foreach (var (name, path) in GetObjectPaths(resPath))
|
||||||
|
{
|
||||||
|
_objectPathMap[name] = path;
|
||||||
|
}
|
||||||
|
foreach (var (name, path) in GetObjectTexturePaths(resPath))
|
||||||
|
{
|
||||||
|
_objectTexturePathMap[name] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialise(string misPath, string resPath, CampaignResources parent)
|
||||||
|
{
|
||||||
|
foreach (var (name, path) in parent._texturePathMap)
|
||||||
|
{
|
||||||
|
_texturePathMap[name] = path;
|
||||||
|
}
|
||||||
|
foreach (var (name, path) in parent._objectPathMap)
|
||||||
|
{
|
||||||
|
_objectPathMap[name] = path;
|
||||||
|
}
|
||||||
|
foreach (var (name, path) in parent._objectTexturePathMap)
|
||||||
|
{
|
||||||
|
_objectTexturePathMap[name] = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialise(misPath, resPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _initialised = false;
|
||||||
|
private readonly string _extractionPath;
|
||||||
|
private string _fmsDir;
|
||||||
|
private CampaignResources _omResources;
|
||||||
|
private Dictionary<string, CampaignResources> _fmResources;
|
||||||
|
|
||||||
|
public ResourcePathManager(string extractionPath)
|
||||||
|
{
|
||||||
|
_extractionPath = extractionPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ConvertSeparator(string path)
|
||||||
|
{
|
||||||
|
return path.Replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init(string installPath)
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
// - Determine if folder is a thief install
|
||||||
|
// - Load all the (relevant) config files
|
||||||
|
// - Get base paths from configs
|
||||||
|
// - Build list of FM campaigns
|
||||||
|
// - Initialise OM campaign resource paths
|
||||||
|
// - Lazy load FM campaign resource paths (that inherit OM resources)
|
||||||
|
|
||||||
|
if (!DirContainsThiefExe(installPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"No Thief installation found at {installPath}", nameof(installPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should these paths be stored?
|
||||||
|
if (!TryGetConfigPaths(installPath, out var configPaths))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Failed to find all installation config paths.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the paths of the base Fam and Obj resources so we can extract them.
|
||||||
|
var installCfgLines = File.ReadAllLines(configPaths[(int)ConfigFile.Install]);
|
||||||
|
FindConfigVar(installCfgLines, "resname_base", out var resPaths);
|
||||||
|
var baseFamPath = "";
|
||||||
|
var baseObjPath = "";
|
||||||
|
foreach (var resPath in resPaths.Split('+'))
|
||||||
|
{
|
||||||
|
var dir = Path.Join(installPath, ConvertSeparator(resPath));
|
||||||
|
foreach (var path in Directory.GetFiles(dir))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path).ToLower();
|
||||||
|
if (name == "fam.crf" && baseFamPath == "")
|
||||||
|
{
|
||||||
|
baseFamPath = path;
|
||||||
|
}
|
||||||
|
else if (name == "obj.crf" && baseObjPath == "")
|
||||||
|
{
|
||||||
|
baseObjPath = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do the extraction bro
|
||||||
|
(string, string)[] resources = [("fam", baseFamPath), ("obj", baseObjPath)];
|
||||||
|
foreach (var (extractName, zipPath) in resources)
|
||||||
|
{
|
||||||
|
var extractPath = Path.Join(_extractionPath, extractName);
|
||||||
|
if (Directory.Exists(extractPath))
|
||||||
|
{
|
||||||
|
Directory.Delete(extractPath, true);
|
||||||
|
}
|
||||||
|
ZipFile.OpenRead(zipPath).ExtractToDirectory(extractPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
FindConfigVar(installCfgLines, "load_path", out var omsPath);
|
||||||
|
omsPath = Path.Join(installPath, ConvertSeparator(omsPath));
|
||||||
|
_omResources = new CampaignResources();
|
||||||
|
_omResources.name = "";
|
||||||
|
_omResources.Initialise(omsPath, _extractionPath);
|
||||||
|
|
||||||
|
var camModLines = File.ReadAllLines(configPaths[(int)ConfigFile.CamMod]);
|
||||||
|
FindConfigVar(camModLines, "fm_path", out var fmsPath, "FMs");
|
||||||
|
_fmsDir = Path.Join(installPath, fmsPath);
|
||||||
|
|
||||||
|
// Build up the map of FM campaigns. These are uninitialised, we just want
|
||||||
|
// to have their name
|
||||||
|
_fmResources = new Dictionary<string, CampaignResources>();
|
||||||
|
foreach (var dir in Directory.GetDirectories(_fmsDir))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(dir);
|
||||||
|
var fmResource = new CampaignResources();
|
||||||
|
fmResource.name = name;
|
||||||
|
_fmResources.Add(name, fmResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
_initialised = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetCampaignNames()
|
||||||
|
{
|
||||||
|
if (!_initialised) return null;
|
||||||
|
|
||||||
|
var names = new List<string>(_fmResources.Keys);
|
||||||
|
names.Sort();
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CampaignResources GetCampaign(string campaignName)
|
||||||
|
{
|
||||||
|
if (campaignName == null || campaignName == "")
|
||||||
|
{
|
||||||
|
return _omResources;
|
||||||
|
}
|
||||||
|
else if (_fmResources.TryGetValue(campaignName, out var campaign))
|
||||||
|
{
|
||||||
|
if (!campaign.initialised)
|
||||||
|
{
|
||||||
|
var fmPath = Path.Join(_fmsDir, campaignName);
|
||||||
|
campaign.Initialise(fmPath, fmPath, _omResources);
|
||||||
|
}
|
||||||
|
return campaign;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("No campaign found with given name", nameof(campaignName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> GetObjectTexturePaths(string root)
|
||||||
|
{
|
||||||
|
string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
|
||||||
|
|
||||||
|
var dirOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
var texOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
|
RecurseSubdirectories = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var pathMap = new Dictionary<string, string>();
|
||||||
|
foreach (var dir in Directory.EnumerateDirectories(root, "obj", dirOptions))
|
||||||
|
{
|
||||||
|
foreach (var path in Directory.EnumerateFiles(dir, "*", texOptions))
|
||||||
|
{
|
||||||
|
var convertedPath = ConvertSeparator(path);
|
||||||
|
var ext = Path.GetExtension(convertedPath);
|
||||||
|
if (validExtensions.Contains(ext.ToLower()))
|
||||||
|
{
|
||||||
|
var key = Path.GetFileNameWithoutExtension(convertedPath).ToLower();
|
||||||
|
pathMap.TryAdd(key, convertedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> GetTexturePaths(string root)
|
||||||
|
{
|
||||||
|
string[] validExtensions = { ".dds", ".png", ".tga", ".pcx", ".gif", ".bmp", ".cel", };
|
||||||
|
|
||||||
|
var famOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
var textureOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
|
RecurseSubdirectories = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var pathMap = new Dictionary<string, string>();
|
||||||
|
foreach (var dir in Directory.EnumerateDirectories(root, "fam", famOptions))
|
||||||
|
{
|
||||||
|
foreach (var path in Directory.EnumerateFiles(dir, "*", textureOptions))
|
||||||
|
{
|
||||||
|
var convertedPath = ConvertSeparator(path);
|
||||||
|
var ext = Path.GetExtension(convertedPath);
|
||||||
|
if (validExtensions.Contains(ext.ToLower()))
|
||||||
|
{
|
||||||
|
var key = Path.GetRelativePath(root, convertedPath)[..^ext.Length].ToLower();
|
||||||
|
pathMap.TryAdd(key, convertedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> GetObjectPaths(string root)
|
||||||
|
{
|
||||||
|
var dirOptions = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
var binOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
|
RecurseSubdirectories = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var pathMap = new Dictionary<string, string>();
|
||||||
|
foreach (var dir in Directory.EnumerateDirectories(root, "obj", dirOptions))
|
||||||
|
{
|
||||||
|
foreach (var path in Directory.EnumerateFiles(dir, "*.bin", binOptions))
|
||||||
|
{
|
||||||
|
var convertedPath = ConvertSeparator(path);
|
||||||
|
var key = Path.GetRelativePath(dir, convertedPath).ToLower();
|
||||||
|
pathMap.TryAdd(key, convertedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determine if the given directory contains a Thief executable at the top level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dir">The directory to search</param>
|
||||||
|
/// <returns><c>true</c> if a Thief executable was found, <c>false</c> otherwise.</returns>
|
||||||
|
private static bool DirContainsThiefExe(string dir)
|
||||||
|
{
|
||||||
|
var searchOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var path in Directory.GetFiles(dir, "*.exe", searchOptions))
|
||||||
|
{
|
||||||
|
var baseName = Path.GetFileName(path).ToLower();
|
||||||
|
if (baseName.Contains("thief"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an array of of all the Dark config file paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="installPath">Root directory of the Thief installation.</param>
|
||||||
|
/// <param name="configPaths">Output array of config file paths</param>
|
||||||
|
/// <returns><c>true</c> if all config files were found, <c>false</c> otherwise.</returns>
|
||||||
|
private static bool TryGetConfigPaths(string installPath, out string[] configPaths)
|
||||||
|
{
|
||||||
|
configPaths = new string[(int)ConfigFile.ConfigFileCount];
|
||||||
|
|
||||||
|
var searchOptions = new EnumerationOptions
|
||||||
|
{
|
||||||
|
MatchCasing = MatchCasing.CaseInsensitive,
|
||||||
|
};
|
||||||
|
|
||||||
|
// `cam.cfg`, `cam_ext.cfg`, and `cam_mod.ini` are always in the root of the install.
|
||||||
|
// The first two configs will tell us if any other configs are in non-default locations.
|
||||||
|
// We can't just do a recursive search for everything else because they can potentially
|
||||||
|
// be *outside* of the Thief installation.
|
||||||
|
foreach (var path in Directory.GetFiles(installPath, "cam*", searchOptions))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path).ToLower();
|
||||||
|
if (name == "cam.cfg")
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.Cam] = path;
|
||||||
|
}
|
||||||
|
else if (name == "cam_ext.cfg")
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.CamExt] = path;
|
||||||
|
}
|
||||||
|
else if (name == "cam_mod.ini")
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.CamMod] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var camExtLines = File.ReadAllLines(configPaths[(int)ConfigFile.CamExt]);
|
||||||
|
var camLines = File.ReadAllLines(configPaths[(int)ConfigFile.Cam]);
|
||||||
|
|
||||||
|
bool FindCamVar(string varName, out string value, string defaultValue = "")
|
||||||
|
{
|
||||||
|
return FindConfigVar(camExtLines, varName, out value, defaultValue) ||
|
||||||
|
FindConfigVar(camLines, varName, out value, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
FindCamVar("include_path", out var includePath, "./");
|
||||||
|
FindCamVar("game", out var gameName);
|
||||||
|
FindCamVar($"{gameName}_include_install_cfg", out var installCfgName);
|
||||||
|
FindCamVar("include_user_cfg", out var userCfgName);
|
||||||
|
|
||||||
|
// TODO: How to handle case-insensitive absolute paths?
|
||||||
|
// Fixup the include path to "work" cross-platform
|
||||||
|
includePath = ConvertSeparator(includePath);
|
||||||
|
includePath = Path.Join(installPath, includePath);
|
||||||
|
if (!Directory.Exists(includePath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var path in Directory.GetFiles(includePath, "*.cfg", searchOptions))
|
||||||
|
{
|
||||||
|
var name = Path.GetFileName(path).ToLower();
|
||||||
|
if (name == $"{gameName}.cfg")
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.Game] = path;
|
||||||
|
}
|
||||||
|
else if (name == installCfgName.ToLower())
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.Install] = path;
|
||||||
|
}
|
||||||
|
else if (name == userCfgName.ToLower())
|
||||||
|
{
|
||||||
|
configPaths[(int)ConfigFile.User] = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we found everything
|
||||||
|
var i = 0;
|
||||||
|
foreach (var path in configPaths)
|
||||||
|
{
|
||||||
|
if (path == null || path == "")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FindConfigVar(string[] lines, string varName, out string value, string defaultValue = "")
|
||||||
|
{
|
||||||
|
value = defaultValue;
|
||||||
|
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
if (line.StartsWith(varName))
|
||||||
|
{
|
||||||
|
value = line[(line.IndexOf(' ') + 1)..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,10 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 17.0.31903.59
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepersCompound.Lightmapper", "KeepersCompound.Lightmapper\KeepersCompound.Lightmapper.csproj", "{24FE5A7B-3E74-43CE-8F61-92AEFB07C544}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeepersCompound.LGS", "KeepersCompound.LGS\KeepersCompound.LGS.csproj", "{7262E507-A972-4693-8C99-67E163E8BF7C}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -11,4 +15,14 @@ Global
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{24FE5A7B-3E74-43CE-8F61-92AEFB07C544}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{24FE5A7B-3E74-43CE-8F61-92AEFB07C544}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{24FE5A7B-3E74-43CE-8F61-92AEFB07C544}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{24FE5A7B-3E74-43CE-8F61-92AEFB07C544}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7262E507-A972-4693-8C99-67E163E8BF7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7262E507-A972-4693-8C99-67E163E8BF7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7262E507-A972-4693-8C99-67E163E8BF7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7262E507-A972-4693-8C99-67E163E8BF7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in New Issue