Files
SabreTools.Serialization/SabreTools.Serialization.Readers/VBSP.cs
2026-03-26 09:29:32 -04:00

1006 lines
41 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using SabreTools.Data.Models.BSP;
using SabreTools.IO.Extensions;
using SabreTools.Numerics.Extensions;
using SabreTools.Text.Extensions;
using static SabreTools.Data.Models.BSP.Constants;
// TODO: Finish replacing ReadType
#pragma warning disable IDE0017 // Simplify object initialization
#pragma warning disable IDE0060 // Remove unused parameter
namespace SabreTools.Serialization.Readers
{
public class VBSP : BaseBinaryReader<VbspFile>
{
/// <inheritdoc/>
public override VbspFile? Deserialize(Stream? data)
{
// If the data is invalid
if (data is null || !data.CanRead)
return null;
try
{
// Cache the current offset
long initialOffset = data.Position;
// Create a new Half-Life 2 Level to fill
var file = new VbspFile();
#region Header
// Try to parse the header
var header = ParseVbspHeader(data);
if (header.Signature != SignatureString)
return null;
if (Array.IndexOf([17, 18, 19, 20, 21, 22, 23, 25, 27, 29, 0x00040014], header.Version) > -1)
return null;
if (header.Lumps is null || header.Lumps.Length != VBSP_HEADER_LUMPS)
return null;
// Set the package header
file.Header = header;
#endregion
#region Lumps
for (int l = 0; l < VBSP_HEADER_LUMPS; l++)
{
// Get the next lump entry
var lumpEntry = header.Lumps[l];
if (lumpEntry is null)
continue;
if (lumpEntry.Offset == 0 || lumpEntry.Length == 0)
continue;
// Seek to the lump offset
data.SeekIfPossible(initialOffset + lumpEntry.Offset, SeekOrigin.Begin);
// Read according to the lump type
switch ((VbspLumpType)l)
{
case VbspLumpType.LUMP_ENTITIES:
file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_PLANES:
file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_TEXTURES:
file.TexdataLump = ParseTexdataLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_VERTICES:
file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_VISIBILITY:
file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_NODES:
file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_TEXINFO:
file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_FACES:
file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LIGHTING:
file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_CLIPNODES:
file.OcclusionLump = ParseOcclusionLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAVES:
file.LeavesLump = ParseLeavesLump(data, lumpEntry.Version, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_MARKSURFACES:
file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_EDGES:
file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_SURFEDGES:
file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_MODELS:
file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_WORLDLIGHTS:
file.LDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAFFACES:
file.LeafFacesLump = ParseLeafFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAFBRUSHES:
file.LeafBrushesLump = ParseLeafBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_BRUSHES:
file.BrushesLump = ParseBrushesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_BRUSHSIDES:
file.BrushsidesLump = ParseBrushsidesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_AREAS:
// TODO: Support LUMP_AREAS [20] when in Models
break;
case VbspLumpType.LUMP_AREAPORTALS:
// TODO: Support LUMP_AREAPORTALS [21] when in Models
break;
case VbspLumpType.LUMP_PORTALS:
// TODO: Support LUMP_PORTALS / LUMP_UNUSED0 / LUMP_PROPCOLLISION [22] when in Models
break;
case VbspLumpType.LUMP_CLUSTERS:
// TODO: Support LUMP_CLUSTERS / LUMP_UNUSED1 / LUMP_PROPHULLS [23] when in Models
break;
case VbspLumpType.LUMP_PORTALVERTS:
// TODO: Support LUMP_PORTALVERTS / LUMP_UNUSED2 / LUMP_FAKEENTITIES / LUMP_PROPHULLVERTS [24] when in Models
break;
case VbspLumpType.LUMP_CLUSTERPORTALS:
// TODO: Support LUMP_CLUSTERPORTALS / LUMP_UNUSED3 / LUMP_PROPTRIS [25] when in Models
break;
case VbspLumpType.LUMP_DISPINFO:
file.DispInfosLump = ParseDispInfosLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_ORIGINALFACES:
file.OriginalFacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_PHYSDISP:
// TODO: Support LUMP_PHYSDISP [28] when in Models
break;
case VbspLumpType.LUMP_PHYSCOLLIDE:
file.PhysCollideLump = ParsePhysCollideLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_VERTNORMALS:
// TODO: Support LUMP_VERTNORMALS [30] when in Models
break;
case VbspLumpType.LUMP_VERTNORMALINDICES:
// TODO: Support LUMP_VERTNORMALINDICES [31] when in Models
break;
case VbspLumpType.LUMP_DISP_LIGHTMAP_ALPHAS:
// TODO: Support LUMP_DISP_LIGHTMAP_ALPHAS [32] when in Models
break;
case VbspLumpType.LUMP_DISP_VERTS:
file.DispVertsLump = ParseDispVertsLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS:
// TODO: Support LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS [34] when in Models
break;
case VbspLumpType.LUMP_GAME_LUMP:
file.GameLump = ParseGameLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAFWATERDATA:
// TODO: Support LUMP_LEAFWATERDATA [36] when in Models
break;
case VbspLumpType.LUMP_PRIMITIVES:
// TODO: Support LUMP_PRIMITIVES [37] when in Models
break;
case VbspLumpType.LUMP_PRIMVERTS:
// TODO: Support LUMP_PRIMVERTS [38] when in Models
break;
case VbspLumpType.LUMP_PRIMINDICES:
// TODO: Support LUMP_PRIMINDICES [39] when in Models
break;
case VbspLumpType.LUMP_PAKFILE:
file.PakfileLump = ParsePakfileLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_CLIPPORTALVERTS:
// TODO: Support LUMP_CLIPPORTALVERTS [41] when in Models
break;
case VbspLumpType.LUMP_CUBEMAPS:
file.CubemapsLump = ParseCubemapsLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_TEXDATA_STRING_DATA:
file.TexdataStringData = ParseTexdataStringData(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_TEXDATA_STRING_TABLE:
file.TexdataStringTable = ParseTexdataStringTable(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_OVERLAYS:
file.OverlaysLump = ParseOverlaysLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAFMINDISTTOWATER:
// TODO: Support LUMP_LEAFMINDISTTOWATER [46] when in Models
break;
case VbspLumpType.LUMP_FACE_MACRO_TEXTURE_INFO:
// TODO: Support LUMP_FACE_MACRO_TEXTURE_INFO [47] when in Models
break;
case VbspLumpType.LUMP_DISP_TRIS:
file.DispTrisLump = ParseDispTrisLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_PHYSCOLLIDESURFACE:
// TODO: Support LUMP_PHYSCOLLIDESURFACE / LUMP_PROP_BLOB [49] when in Models
break;
case VbspLumpType.LUMP_WATEROVERLAYS:
// TODO: Support LUMP_WATEROVERLAYS [50] when in Models
break;
case VbspLumpType.LUMP_LIGHTMAPPAGES:
file.HDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LIGHTMAPPAGEINFOS:
file.LDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LIGHTING_HDR:
// TODO: Support LUMP_LIGHTING_HDR [53] when in Models
break;
case VbspLumpType.LUMP_WORLDLIGHTS_HDR:
file.HDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAF_AMBIENT_LIGHTING_HDR:
file.HDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_LEAF_AMBIENT_LIGHTING:
file.LDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length);
break;
case VbspLumpType.LUMP_XZIPPAKFILE:
// TODO: Support LUMP_XZIPPAKFILE [57] when in Models
break;
case VbspLumpType.LUMP_FACES_HDR:
// TODO: Support LUMP_FACES_HDR [58] when in Models
break;
case VbspLumpType.LUMP_MAP_FLAGS:
// TODO: Support LUMP_MAP_FLAGS [59] when in Models
break;
case VbspLumpType.LUMP_OVERLAY_FADES:
// TODO: Support LUMP_OVERLAY_FADES [60] when in Models
break;
case VbspLumpType.LUMP_OVERLAY_SYSTEM_LEVELS:
// TODO: Support LUMP_OVERLAY_SYSTEM_LEVELS [61] when in Models
break;
case VbspLumpType.LUMP_PHYSLEVEL:
// TODO: Support LUMP_PHYSLEVEL [62] when in Models
break;
case VbspLumpType.LUMP_DISP_MULTIBLEND:
// TODO: Support LUMP_DISP_MULTIBLEND [63] when in Models
break;
default:
// Unsupported VbspLumpType value, ignore
break;
}
}
#endregion
return file;
}
catch
{
// Ignore the actual error
return null;
}
}
/// <summary>
/// Parse a Stream into VbspHeader
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled VbspHeader on success, null on error</returns>
public static VbspHeader ParseVbspHeader(Stream data)
{
var obj = new VbspHeader();
byte[] signature = data.ReadBytes(4);
obj.Signature = Encoding.ASCII.GetString(signature);
obj.Version = data.ReadInt32LittleEndian();
obj.Lumps = new VbspLumpEntry[VBSP_HEADER_LUMPS];
for (int i = 0; i < VBSP_HEADER_LUMPS; i++)
{
obj.Lumps[i] = ParseVbspLumpEntry(data);
}
obj.MapRevision = data.ReadInt32LittleEndian();
return obj;
}
/// <summary>
/// Parse a Stream into VbspLumpEntry
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled VbspLumpEntry on success, null on error</returns>
public static VbspLumpEntry ParseVbspLumpEntry(Stream data)
{
var obj = new VbspLumpEntry();
obj.Version = data.ReadUInt32LittleEndian();
obj.FourCC = data.ReadBytes(4);
return obj;
}
/// <summary>
/// Parse a Stream into LUMP_ENTITIES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_ENTITIES on success, null on error</returns>
private static EntitiesLump ParseEntitiesLump(Stream data, int offset, int length)
{
var entities = new List<Entity>();
// Read the entire lump as text
byte[] lumpData = data.ReadBytes(length);
string lumpText = Encoding.ASCII.GetString(lumpData);
// Break the text by ending curly braces
string[] lumpSections = lumpText.Split('}');
// Loop through all sections
for (int i = 0; i < lumpSections.Length; i++)
{
// Prepare an attributes list
var attributes = new List<KeyValuePair<string, string>>();
// Split the section by newlines
string section = lumpSections[i].Trim('{', '}');
string[] lines = section.Split('\n');
// Convert each line into a key-value pair and add
for (int j = 0; j < lines.Length; j++)
{
// TODO: Split lines and add
}
// Create a new entity and add
var entity = new Entity { Attributes = attributes };
entities.Add(entity);
}
return new EntitiesLump { Entities = [.. entities] };
}
/// <summary>
/// Parse a Stream into LUMP_PLANES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_PLANES on success, null on error</returns>
private static PlanesLump ParsePlanesLump(Stream data, int offset, int length)
{
var planes = new List<Plane>();
while (data.Position < offset + length)
{
var plane = data.ReadType<Plane>();
if (plane is not null)
planes.Add(plane);
}
return new PlanesLump { Planes = [.. planes] };
}
/// <summary>
/// Parse a Stream into LUMP_TEXDATA
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_TEXDATA on success, null on error</returns>
private static TexdataLump ParseTexdataLump(Stream data, int offset, int length)
{
var texdatas = new List<Texdata>();
while (data.Position < offset + length)
{
var texdata = data.ReadType<Texdata>();
if (texdata is not null)
texdatas.Add(texdata);
}
return new TexdataLump { Texdatas = [.. texdatas] };
}
/// <summary>
/// Parse a Stream into LUMP_VERTEXES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_VERTEXES on success, null on error</returns>
private static VerticesLump ParseVerticesLump(Stream data, int offset, int length)
{
var vertices = new List<Vector3D>();
while (data.Position < offset + length)
{
var vertex = data.ReadType<Vector3D>();
if (vertex is null)
break;
vertices.Add(vertex);
}
return new VerticesLump { Vertices = [.. vertices] };
}
/// <summary>
/// Parse a Stream into LUMP_VISIBILITY
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_VISIBILITY on success, null on error</returns>
private static VisibilityLump ParseVisibilityLump(Stream data, int offset, int length)
{
var lump = new VisibilityLump();
lump.NumClusters = data.ReadInt32LittleEndian();
lump.ByteOffsets = new int[lump.NumClusters][];
for (int i = 0; i < lump.NumClusters; i++)
{
lump.ByteOffsets[i] = new int[2];
for (int j = 0; j < 2; j++)
{
lump.ByteOffsets[i][j] = data.ReadInt32LittleEndian();
}
}
return lump;
}
/// <summary>
/// Parse a Stream into LUMP_NODES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_NODES on success, null on error</returns>
private static VbspNodesLump ParseNodesLump(Stream data, int offset, int length)
{
var nodes = new List<VbspNode>();
while (data.Position < offset + length)
{
var node = data.ReadType<VbspNode>();
if (node is not null)
nodes.Add(node);
}
return new VbspNodesLump { Nodes = [.. nodes] };
}
/// <summary>
/// Parse a Stream into LUMP_TEXINFO
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_TEXINFO on success, null on error</returns>
private static VbspTexinfoLump ParseTexinfoLump(Stream data, int offset, int length)
{
var texinfos = new List<VbspTexinfo>();
while (data.Position < offset + length)
{
var texinfo = data.ReadType<VbspTexinfo>();
if (texinfo is not null)
texinfos.Add(texinfo);
}
return new VbspTexinfoLump { Texinfos = [.. texinfos] };
}
/// <summary>
/// Parse a Stream into LUMP_FACES / LUMP_ORIGINALFACES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_FACES / LUMP_ORIGINALFACES on success, null on error</returns>
private static VbspFacesLump ParseFacesLump(Stream data, int offset, int length)
{
var faces = new List<VbspFace>();
while (data.Position < offset + length)
{
var face = data.ReadType<VbspFace>();
if (face is not null)
faces.Add(face);
}
return new VbspFacesLump { Faces = [.. faces] };
}
/// <summary>
/// Parse a Stream into LUMP_PHYSCOLLIDE
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_PHYSCOLLIDE on success, null on error</returns>
private static PhysCollideLump ParsePhysCollideLump(Stream data, int offset, int length)
{
var models = new List<PhysModel>();
while (data.Position < offset + length)
{
var model = ParsePhysModel(data);
if (model is not null)
models.Add(model);
}
return new PhysCollideLump { Models = [.. models] };
}
/// <summary>
/// Parse a Stream into PhysModel
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled PhysModel on success, null on error</returns>
private static PhysModel ParsePhysModel(Stream data)
{
var model = new PhysModel();
model.ModelIndex = data.ReadInt32LittleEndian();
model.DataSize = data.ReadInt32LittleEndian();
model.KeydataSize = data.ReadInt32LittleEndian();
model.SolidCount = data.ReadInt32LittleEndian();
model.Solids = new PhysSolid[model.SolidCount];
for (int i = 0; i < model.Solids.Length; i++)
{
var solid = ParsePhysSolid(data);
if (solid is not null)
model.Solids[i] = solid;
}
return model;
}
/// <summary>
/// Parse a Stream into PhysSolid
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled PhysSolid on success, null on error</returns>
private static PhysSolid ParsePhysSolid(Stream data)
{
var solid = new PhysSolid();
solid.Size = data.ReadInt32LittleEndian();
if (solid.Size > 0)
solid.CollisionData = data.ReadBytes(solid.Size);
return solid;
}
/// <summary>
/// Parse a Stream into LUMP_LIGHTING
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LIGHTING on success, null on error</returns>
private static LightmapLump ParseLightmapLump(Stream data, int offset, int length)
{
var lump = new LightmapLump();
lump.Lightmap = new byte[length / 3][];
for (int i = 0; i < length / 3; i++)
{
lump.Lightmap[i] = data.ReadBytes(3);
}
return lump;
}
/// <summary>
/// Parse a Stream into LUMP_OCCLUSION
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_OCCLUSION on success, null on error</returns>
private static OcclusionLump ParseOcclusionLump(Stream data, int offset, int length)
{
var lump = new OcclusionLump();
lump.Count = data.ReadInt32LittleEndian();
lump.Data = new OccluderData[lump.Count];
for (int i = 0; i < lump.Count; i++)
{
var occluderData = data.ReadType<OccluderData>();
if (occluderData is not null)
lump.Data[i] = occluderData;
}
lump.PolyDataCount = data.ReadInt32LittleEndian();
lump.PolyData = new OccluderPolyData[lump.Count];
for (int i = 0; i < lump.Count; i++)
{
var polyData = data.ReadType<OccluderPolyData>();
if (polyData is not null)
lump.PolyData[i] = polyData;
}
lump.VertexIndexCount = data.ReadInt32LittleEndian();
lump.VertexIndicies = new int[lump.VertexIndexCount];
for (int i = 0; i < lump.VertexIndexCount; i++)
{
lump.VertexIndicies[i] = data.ReadInt32LittleEndian();
}
return lump;
}
/// <summary>
/// Parse a Stream into LUMP_LEAVES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LEAVES on success, null on error</returns>
private static VbspLeavesLump ParseLeavesLump(Stream data, uint version, int offset, int length)
{
var leaves = new List<VbspLeaf>();
while (data.Position < offset + length)
{
var leaf = ParseVbspLeaf(data, version);
if (leaf is not null)
leaves.Add(leaf);
}
return new VbspLeavesLump { Leaves = [.. leaves] };
}
/// <summary>
/// Parse a Stream into VbspLeaf
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled VbspLeaf on success, null on error</returns>
private static VbspLeaf ParseVbspLeaf(Stream data, uint version)
{
var leaf = new VbspLeaf();
leaf.Contents = (VbspContents)data.ReadUInt32LittleEndian();
leaf.Cluster = data.ReadInt16LittleEndian();
leaf.AreaFlags = data.ReadInt16LittleEndian();
leaf.Mins = new short[3];
for (int i = 0; i < leaf.Mins.Length; i++)
{
leaf.Mins[i] = data.ReadInt16LittleEndian();
}
leaf.Maxs = new short[3];
for (int i = 0; i < leaf.Maxs.Length; i++)
{
leaf.Maxs[i] = data.ReadInt16LittleEndian();
}
leaf.FirstLeafFace = data.ReadUInt16LittleEndian();
leaf.NumLeafFaces = data.ReadUInt16LittleEndian();
leaf.FirstLeafBrush = data.ReadUInt16LittleEndian();
leaf.NumLeafBrushes = data.ReadUInt16LittleEndian();
leaf.LeafWaterDataID = data.ReadInt16LittleEndian();
if (version == 1)
leaf.AmbientLighting = data.ReadType<CompressedLightCube>() ?? new();
else
leaf.Padding = data.ReadInt16LittleEndian();
return leaf;
}
/// <summary>
/// Parse a Stream into LUMP_FACEIDS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_FACEIDS on success, null on error</returns>
private static MarksurfacesLump ParseMarksurfacesLump(Stream data, int offset, int length)
{
var marksurfaces = new List<ushort>();
while (data.Position < offset + length)
{
marksurfaces.Add(data.ReadUInt16LittleEndian());
}
return new MarksurfacesLump { Marksurfaces = [.. marksurfaces] };
}
/// <summary>
/// Parse a Stream into LUMP_EDGES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_EDGES on success, null on error</returns>
private static EdgesLump ParseEdgesLump(Stream data, int offset, int length)
{
var edges = new List<Edge>();
while (data.Position < offset + length)
{
var edge = data.ReadType<Edge>();
if (edge is not null)
edges.Add(edge);
}
return new EdgesLump { Edges = [.. edges] };
}
/// <summary>
/// Parse a Stream into LUMP_SURFEDGES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_SURFEDGES on success, null on error</returns>
private static SurfedgesLump ParseSurfedgesLump(Stream data, int offset, int length)
{
var surfedges = new List<int>();
while (data.Position < offset + length)
{
surfedges.Add(data.ReadInt32LittleEndian());
}
return new SurfedgesLump { Surfedges = [.. surfedges] };
}
/// <summary>
/// Parse a Stream into LUMP_MODELS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_MODELS on success, null on error</returns>
private static VbspModelsLump ParseModelsLump(Stream data, int offset, int length)
{
var models = new List<VbspModel>();
while (data.Position < offset + length)
{
var model = data.ReadType<VbspModel>();
if (model is not null)
models.Add(model);
}
return new VbspModelsLump { Models = [.. models] };
}
/// <summary>
/// Parse a Stream into LUMP_WORLDLIGHTS / LUMP_WORLDLIGHTS_HDR
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_WORLDLIGHTS / LUMP_WORLDLIGHTS_HDR on success, null on error</returns>
private static WorldLightsLump ParseWorldLightsLump(Stream data, int offset, int length)
{
var worldLights = new List<WorldLight>();
while (data.Position < offset + length)
{
var worldLight = data.ReadType<WorldLight>();
if (worldLight is not null)
worldLights.Add(worldLight);
}
return new WorldLightsLump { WorldLights = [.. worldLights] };
}
/// <summary>
/// Parse a Stream into LUMP_LEAFFACES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LEAFFACES on success, null on error</returns>
private static LeafFacesLump ParseLeafFacesLump(Stream data, int offset, int length)
{
var map = new List<ushort>();
while (data.Position < offset + length)
{
map.Add(data.ReadUInt16LittleEndian());
}
return new LeafFacesLump { Map = [.. map] };
}
/// <summary>
/// Parse a Stream into LUMP_LEAFBRUSHES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LEAFBRUSHES on success, null on error</returns>
private static LeafBrushesLump ParseLeafBrushesLump(Stream data, int offset, int length)
{
var map = new List<ushort>();
while (data.Position < offset + length)
{
map.Add(data.ReadUInt16LittleEndian());
}
return new LeafBrushesLump { Map = [.. map] };
}
/// <summary>
/// Parse a Stream into LUMP_BRUSHES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_BRUSHES on success, null on error</returns>
private static BrushesLump ParseBrushesLump(Stream data, int offset, int length)
{
var brushes = new List<Brush>();
while (data.Position < offset + length)
{
var brush = data.ReadType<Brush>();
if (brush is not null)
brushes.Add(brush);
}
return new BrushesLump { Brushes = [.. brushes] };
}
/// <summary>
/// Parse a Stream into LUMP_BRUSHSIDES
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_BRUSHSIDES on success, null on error</returns>
private static BrushsidesLump ParseBrushsidesLump(Stream data, int offset, int length)
{
var brushsides = new List<Brushside>();
while (data.Position < offset + length)
{
var brushside = data.ReadType<Brushside>();
if (brushside is not null)
brushsides.Add(brushside);
}
return new BrushsidesLump { Brushsides = [.. brushsides] };
}
/// <summary>
/// Parse a Stream into LUMP_DISPINFO
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_DISPINFO on success, null on error</returns>
private static DispInfosLump ParseDispInfosLump(Stream data, int offset, int length)
{
var dispInfos = new List<DispInfo>();
while (data.Position < offset + length)
{
var dispInfo = data.ReadType<DispInfo>();
if (dispInfo is not null)
dispInfos.Add(dispInfo);
}
return new DispInfosLump { Infos = [.. dispInfos] };
}
/// <summary>
/// Parse a Stream into LUMP_DISP_VERTS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_DISP_VERTS on success, null on error</returns>
private static DispVertsLump ParseDispVertsLump(Stream data, int offset, int length)
{
var verts = new List<DispVert>();
while (data.Position < offset + length)
{
var vert = data.ReadType<DispVert>();
if (vert is not null)
verts.Add(vert);
}
return new DispVertsLump { Verts = [.. verts] };
}
/// <summary>
/// Parse a Stream into LUMP_GAME_LUMP
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_GAME_LUMP on success, null on error</returns>
private static GameLump ParseGameLump(Stream data, int offset, int length)
{
var lump = new GameLump();
lump.LumpCount = data.ReadInt32LittleEndian();
lump.Directories = new GameLumpDirectory[lump.LumpCount];
for (int i = 0; i < lump.LumpCount; i++)
{
var dir = data.ReadType<GameLumpDirectory>();
if (dir is not null)
lump.Directories[i] = dir;
}
return lump;
}
/// <summary>
/// Parse a Stream into LUMP_CUBEMAPS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_CUBEMAPS on success, null on error</returns>
private static CubemapsLump ParseCubemapsLump(Stream data, int offset, int length)
{
var cubemaps = new List<Cubemap>();
while (data.Position < offset + length)
{
var cubemap = data.ReadType<Cubemap>();
if (cubemap is not null)
cubemaps.Add(cubemap);
}
return new CubemapsLump { Cubemaps = [.. cubemaps] };
}
/// <summary>
/// Parse a Stream into LUMP_PAKFILE
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_PAKFILE on success, null on error</returns>
private static PakfileLump ParsePakfileLump(Stream data, int offset, int length)
{
var lump = new PakfileLump();
lump.Data = data.ReadBytes(length);
return lump;
}
/// <summary>
/// Parse a Stream into LUMP_TEXDATA_STRING_DATA
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_TEXDATA_STRING_DATA on success, null on error</returns>
private static TexdataStringData ParseTexdataStringData(Stream data, int offset, int length)
{
var strings = new List<string>();
while (data.Position < offset + length)
{
var str = data.ReadNullTerminatedAnsiString();
if (str is not null)
strings.Add(str);
}
return new TexdataStringData { Strings = [.. strings] };
}
/// <summary>
/// Parse a Stream into LUMP_TEXDATA_STRING_TABLE
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_TEXDATA_STRING_TABLE on success, null on error</returns>
private static TexdataStringTable ParseTexdataStringTable(Stream data, int offset, int length)
{
var offsets = new List<int>();
while (data.Position < offset + length)
{
offsets.Add(data.ReadInt32LittleEndian());
}
return new TexdataStringTable { Offsets = [.. offsets] };
}
/// <summary>
/// Parse a Stream into LUMP_OVERLAYS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_OVERLAYS on success, null on error</returns>
private static OverlaysLump ParseOverlaysLump(Stream data, int offset, int length)
{
var overlays = new List<Overlay>();
while (data.Position < offset + length)
{
var overlay = data.ReadType<Overlay>();
if (overlay is not null)
overlays.Add(overlay);
}
return new OverlaysLump { Overlays = [.. overlays] };
}
/// <summary>
/// Parse a Stream into LUMP_DISP_TRIS
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_DISP_TRIS on success, null on error</returns>
private static DispTrisLump ParseDispTrisLump(Stream data, int offset, int length)
{
var tris = new List<DispTri>();
while (data.Position < offset + length)
{
var tri = data.ReadType<DispTri>();
if (tri is not null)
tris.Add(tri);
}
return new DispTrisLump { Tris = [.. tris] };
}
/// <summary>
/// Parse a Stream into LUMP_LIGHTMAPPAGES / LUMP_LEAF_AMBIENT_INDEX_HDR / LUMP_LIGHTMAPPAGEINFOS / LUMP_LEAF_AMBIENT_INDEX
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LIGHTMAPPAGES / LUMP_LEAF_AMBIENT_INDEX_HDR / LUMP_LIGHTMAPPAGEINFOS / LUMP_LEAF_AMBIENT_INDEX on success, null on error</returns>
private static AmbientIndexLump ParseAmbientIndexLump(Stream data, int offset, int length)
{
var indicies = new List<LeafAmbientIndex>();
while (data.Position < offset + length)
{
var index = data.ReadType<LeafAmbientIndex>();
if (index is not null)
indicies.Add(index);
}
return new AmbientIndexLump { Indicies = [.. indicies] };
}
/// <summary>
/// Parse a Stream into LUMP_LEAF_AMBIENT_LIGHTING_HDR / LUMP_LEAF_AMBIENT_LIGHTING
/// </summary>
/// <param name="data">Stream to parse</param>
/// <returns>Filled LUMP_LEAF_AMBIENT_LIGHTING_HDR / LUMP_LEAF_AMBIENT_LIGHTING on success, null on error</returns>
private static AmbientLightingLump ParseAmbientLightingLump(Stream data, int offset, int length)
{
var lightings = new List<LeafAmbientLighting>();
while (data.Position < offset + length)
{
var lighting = data.ReadType<LeafAmbientLighting>();
if (lighting is not null)
lightings.Add(lighting);
}
return new AmbientLightingLump { Lightings = [.. lightings] };
}
}
}