mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-04-05 22:01:33 +00:00
1006 lines
41 KiB
C#
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] };
|
|
}
|
|
}
|
|
}
|