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 { /// 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; } } /// /// Parse a Stream into VbspHeader /// /// Stream to parse /// Filled VbspHeader on success, null on error 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; } /// /// Parse a Stream into VbspLumpEntry /// /// Stream to parse /// Filled VbspLumpEntry on success, null on error public static VbspLumpEntry ParseVbspLumpEntry(Stream data) { var obj = new VbspLumpEntry(); obj.Version = data.ReadUInt32LittleEndian(); obj.FourCC = data.ReadBytes(4); return obj; } /// /// Parse a Stream into LUMP_ENTITIES /// /// Stream to parse /// Filled LUMP_ENTITIES on success, null on error private static EntitiesLump ParseEntitiesLump(Stream data, int offset, int length) { var entities = new List(); // 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>(); // 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] }; } /// /// Parse a Stream into LUMP_PLANES /// /// Stream to parse /// Filled LUMP_PLANES on success, null on error private static PlanesLump ParsePlanesLump(Stream data, int offset, int length) { var planes = new List(); while (data.Position < offset + length) { var plane = data.ReadType(); if (plane is not null) planes.Add(plane); } return new PlanesLump { Planes = [.. planes] }; } /// /// Parse a Stream into LUMP_TEXDATA /// /// Stream to parse /// Filled LUMP_TEXDATA on success, null on error private static TexdataLump ParseTexdataLump(Stream data, int offset, int length) { var texdatas = new List(); while (data.Position < offset + length) { var texdata = data.ReadType(); if (texdata is not null) texdatas.Add(texdata); } return new TexdataLump { Texdatas = [.. texdatas] }; } /// /// Parse a Stream into LUMP_VERTEXES /// /// Stream to parse /// Filled LUMP_VERTEXES on success, null on error private static VerticesLump ParseVerticesLump(Stream data, int offset, int length) { var vertices = new List(); while (data.Position < offset + length) { var vertex = data.ReadType(); if (vertex is null) break; vertices.Add(vertex); } return new VerticesLump { Vertices = [.. vertices] }; } /// /// Parse a Stream into LUMP_VISIBILITY /// /// Stream to parse /// Filled LUMP_VISIBILITY on success, null on error 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; } /// /// Parse a Stream into LUMP_NODES /// /// Stream to parse /// Filled LUMP_NODES on success, null on error private static VbspNodesLump ParseNodesLump(Stream data, int offset, int length) { var nodes = new List(); while (data.Position < offset + length) { var node = data.ReadType(); if (node is not null) nodes.Add(node); } return new VbspNodesLump { Nodes = [.. nodes] }; } /// /// Parse a Stream into LUMP_TEXINFO /// /// Stream to parse /// Filled LUMP_TEXINFO on success, null on error private static VbspTexinfoLump ParseTexinfoLump(Stream data, int offset, int length) { var texinfos = new List(); while (data.Position < offset + length) { var texinfo = data.ReadType(); if (texinfo is not null) texinfos.Add(texinfo); } return new VbspTexinfoLump { Texinfos = [.. texinfos] }; } /// /// Parse a Stream into LUMP_FACES / LUMP_ORIGINALFACES /// /// Stream to parse /// Filled LUMP_FACES / LUMP_ORIGINALFACES on success, null on error private static VbspFacesLump ParseFacesLump(Stream data, int offset, int length) { var faces = new List(); while (data.Position < offset + length) { var face = data.ReadType(); if (face is not null) faces.Add(face); } return new VbspFacesLump { Faces = [.. faces] }; } /// /// Parse a Stream into LUMP_PHYSCOLLIDE /// /// Stream to parse /// Filled LUMP_PHYSCOLLIDE on success, null on error private static PhysCollideLump ParsePhysCollideLump(Stream data, int offset, int length) { var models = new List(); while (data.Position < offset + length) { var model = ParsePhysModel(data); if (model is not null) models.Add(model); } return new PhysCollideLump { Models = [.. models] }; } /// /// Parse a Stream into PhysModel /// /// Stream to parse /// Filled PhysModel on success, null on error 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; } /// /// Parse a Stream into PhysSolid /// /// Stream to parse /// Filled PhysSolid on success, null on error 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; } /// /// Parse a Stream into LUMP_LIGHTING /// /// Stream to parse /// Filled LUMP_LIGHTING on success, null on error 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; } /// /// Parse a Stream into LUMP_OCCLUSION /// /// Stream to parse /// Filled LUMP_OCCLUSION on success, null on error 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(); 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(); 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; } /// /// Parse a Stream into LUMP_LEAVES /// /// Stream to parse /// Filled LUMP_LEAVES on success, null on error private static VbspLeavesLump ParseLeavesLump(Stream data, uint version, int offset, int length) { var leaves = new List(); while (data.Position < offset + length) { var leaf = ParseVbspLeaf(data, version); if (leaf is not null) leaves.Add(leaf); } return new VbspLeavesLump { Leaves = [.. leaves] }; } /// /// Parse a Stream into VbspLeaf /// /// Stream to parse /// Filled VbspLeaf on success, null on error 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() ?? new(); else leaf.Padding = data.ReadInt16LittleEndian(); return leaf; } /// /// Parse a Stream into LUMP_FACEIDS /// /// Stream to parse /// Filled LUMP_FACEIDS on success, null on error private static MarksurfacesLump ParseMarksurfacesLump(Stream data, int offset, int length) { var marksurfaces = new List(); while (data.Position < offset + length) { marksurfaces.Add(data.ReadUInt16LittleEndian()); } return new MarksurfacesLump { Marksurfaces = [.. marksurfaces] }; } /// /// Parse a Stream into LUMP_EDGES /// /// Stream to parse /// Filled LUMP_EDGES on success, null on error private static EdgesLump ParseEdgesLump(Stream data, int offset, int length) { var edges = new List(); while (data.Position < offset + length) { var edge = data.ReadType(); if (edge is not null) edges.Add(edge); } return new EdgesLump { Edges = [.. edges] }; } /// /// Parse a Stream into LUMP_SURFEDGES /// /// Stream to parse /// Filled LUMP_SURFEDGES on success, null on error private static SurfedgesLump ParseSurfedgesLump(Stream data, int offset, int length) { var surfedges = new List(); while (data.Position < offset + length) { surfedges.Add(data.ReadInt32LittleEndian()); } return new SurfedgesLump { Surfedges = [.. surfedges] }; } /// /// Parse a Stream into LUMP_MODELS /// /// Stream to parse /// Filled LUMP_MODELS on success, null on error private static VbspModelsLump ParseModelsLump(Stream data, int offset, int length) { var models = new List(); while (data.Position < offset + length) { var model = data.ReadType(); if (model is not null) models.Add(model); } return new VbspModelsLump { Models = [.. models] }; } /// /// Parse a Stream into LUMP_WORLDLIGHTS / LUMP_WORLDLIGHTS_HDR /// /// Stream to parse /// Filled LUMP_WORLDLIGHTS / LUMP_WORLDLIGHTS_HDR on success, null on error private static WorldLightsLump ParseWorldLightsLump(Stream data, int offset, int length) { var worldLights = new List(); while (data.Position < offset + length) { var worldLight = data.ReadType(); if (worldLight is not null) worldLights.Add(worldLight); } return new WorldLightsLump { WorldLights = [.. worldLights] }; } /// /// Parse a Stream into LUMP_LEAFFACES /// /// Stream to parse /// Filled LUMP_LEAFFACES on success, null on error private static LeafFacesLump ParseLeafFacesLump(Stream data, int offset, int length) { var map = new List(); while (data.Position < offset + length) { map.Add(data.ReadUInt16LittleEndian()); } return new LeafFacesLump { Map = [.. map] }; } /// /// Parse a Stream into LUMP_LEAFBRUSHES /// /// Stream to parse /// Filled LUMP_LEAFBRUSHES on success, null on error private static LeafBrushesLump ParseLeafBrushesLump(Stream data, int offset, int length) { var map = new List(); while (data.Position < offset + length) { map.Add(data.ReadUInt16LittleEndian()); } return new LeafBrushesLump { Map = [.. map] }; } /// /// Parse a Stream into LUMP_BRUSHES /// /// Stream to parse /// Filled LUMP_BRUSHES on success, null on error private static BrushesLump ParseBrushesLump(Stream data, int offset, int length) { var brushes = new List(); while (data.Position < offset + length) { var brush = data.ReadType(); if (brush is not null) brushes.Add(brush); } return new BrushesLump { Brushes = [.. brushes] }; } /// /// Parse a Stream into LUMP_BRUSHSIDES /// /// Stream to parse /// Filled LUMP_BRUSHSIDES on success, null on error private static BrushsidesLump ParseBrushsidesLump(Stream data, int offset, int length) { var brushsides = new List(); while (data.Position < offset + length) { var brushside = data.ReadType(); if (brushside is not null) brushsides.Add(brushside); } return new BrushsidesLump { Brushsides = [.. brushsides] }; } /// /// Parse a Stream into LUMP_DISPINFO /// /// Stream to parse /// Filled LUMP_DISPINFO on success, null on error private static DispInfosLump ParseDispInfosLump(Stream data, int offset, int length) { var dispInfos = new List(); while (data.Position < offset + length) { var dispInfo = data.ReadType(); if (dispInfo is not null) dispInfos.Add(dispInfo); } return new DispInfosLump { Infos = [.. dispInfos] }; } /// /// Parse a Stream into LUMP_DISP_VERTS /// /// Stream to parse /// Filled LUMP_DISP_VERTS on success, null on error private static DispVertsLump ParseDispVertsLump(Stream data, int offset, int length) { var verts = new List(); while (data.Position < offset + length) { var vert = data.ReadType(); if (vert is not null) verts.Add(vert); } return new DispVertsLump { Verts = [.. verts] }; } /// /// Parse a Stream into LUMP_GAME_LUMP /// /// Stream to parse /// Filled LUMP_GAME_LUMP on success, null on error 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(); if (dir is not null) lump.Directories[i] = dir; } return lump; } /// /// Parse a Stream into LUMP_CUBEMAPS /// /// Stream to parse /// Filled LUMP_CUBEMAPS on success, null on error private static CubemapsLump ParseCubemapsLump(Stream data, int offset, int length) { var cubemaps = new List(); while (data.Position < offset + length) { var cubemap = data.ReadType(); if (cubemap is not null) cubemaps.Add(cubemap); } return new CubemapsLump { Cubemaps = [.. cubemaps] }; } /// /// Parse a Stream into LUMP_PAKFILE /// /// Stream to parse /// Filled LUMP_PAKFILE on success, null on error private static PakfileLump ParsePakfileLump(Stream data, int offset, int length) { var lump = new PakfileLump(); lump.Data = data.ReadBytes(length); return lump; } /// /// Parse a Stream into LUMP_TEXDATA_STRING_DATA /// /// Stream to parse /// Filled LUMP_TEXDATA_STRING_DATA on success, null on error private static TexdataStringData ParseTexdataStringData(Stream data, int offset, int length) { var strings = new List(); while (data.Position < offset + length) { var str = data.ReadNullTerminatedAnsiString(); if (str is not null) strings.Add(str); } return new TexdataStringData { Strings = [.. strings] }; } /// /// Parse a Stream into LUMP_TEXDATA_STRING_TABLE /// /// Stream to parse /// Filled LUMP_TEXDATA_STRING_TABLE on success, null on error private static TexdataStringTable ParseTexdataStringTable(Stream data, int offset, int length) { var offsets = new List(); while (data.Position < offset + length) { offsets.Add(data.ReadInt32LittleEndian()); } return new TexdataStringTable { Offsets = [.. offsets] }; } /// /// Parse a Stream into LUMP_OVERLAYS /// /// Stream to parse /// Filled LUMP_OVERLAYS on success, null on error private static OverlaysLump ParseOverlaysLump(Stream data, int offset, int length) { var overlays = new List(); while (data.Position < offset + length) { var overlay = data.ReadType(); if (overlay is not null) overlays.Add(overlay); } return new OverlaysLump { Overlays = [.. overlays] }; } /// /// Parse a Stream into LUMP_DISP_TRIS /// /// Stream to parse /// Filled LUMP_DISP_TRIS on success, null on error private static DispTrisLump ParseDispTrisLump(Stream data, int offset, int length) { var tris = new List(); while (data.Position < offset + length) { var tri = data.ReadType(); if (tri is not null) tris.Add(tri); } return new DispTrisLump { Tris = [.. tris] }; } /// /// Parse a Stream into LUMP_LIGHTMAPPAGES / LUMP_LEAF_AMBIENT_INDEX_HDR / LUMP_LIGHTMAPPAGEINFOS / LUMP_LEAF_AMBIENT_INDEX /// /// Stream to parse /// Filled LUMP_LIGHTMAPPAGES / LUMP_LEAF_AMBIENT_INDEX_HDR / LUMP_LIGHTMAPPAGEINFOS / LUMP_LEAF_AMBIENT_INDEX on success, null on error private static AmbientIndexLump ParseAmbientIndexLump(Stream data, int offset, int length) { var indicies = new List(); while (data.Position < offset + length) { var index = data.ReadType(); if (index is not null) indicies.Add(index); } return new AmbientIndexLump { Indicies = [.. indicies] }; } /// /// Parse a Stream into LUMP_LEAF_AMBIENT_LIGHTING_HDR / LUMP_LEAF_AMBIENT_LIGHTING /// /// Stream to parse /// Filled LUMP_LEAF_AMBIENT_LIGHTING_HDR / LUMP_LEAF_AMBIENT_LIGHTING on success, null on error private static AmbientLightingLump ParseAmbientLightingLump(Stream data, int offset, int length) { var lightings = new List(); while (data.Position < offset + length) { var lighting = data.ReadType(); if (lighting is not null) lightings.Add(lighting); } return new AmbientLightingLump { Lightings = [.. lightings] }; } } }