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
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
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
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -11,4 +15,14 @@ Global
|
|||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue