From c995ec1dcaa6ec2048a139a4e3c8678b9cf53252 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Tue, 19 Nov 2024 12:40:32 -0500 Subject: [PATCH] Miscellaneous cleanup --- .../Deserializers/AACS.cs | 58 +- .../Deserializers/BDPlus.cs | 4 +- .../Deserializers/BFPK.cs | 29 +- SabreTools.Serialization/Deserializers/BSP.cs | 676 +++---- SabreTools.Serialization/Deserializers/CFB.cs | 7 +- SabreTools.Serialization/Deserializers/CIA.cs | 3 - SabreTools.Serialization/Deserializers/GCF.cs | 8 +- .../Deserializers/Hashfile.cs | 24 + SabreTools.Serialization/Deserializers/IRD.cs | 3 - .../Deserializers/InstallShieldArchiveV3.cs | 5 +- .../Deserializers/InstallShieldCabinet.cs | 11 +- .../Deserializers/LinearExecutable.cs | 4 +- .../Deserializers/MoPaQ.cs | 11 +- .../Deserializers/N3DS.cs | 3 - SabreTools.Serialization/Deserializers/NCF.cs | 5 +- .../Deserializers/NewExecutable.cs | 4 +- .../Deserializers/Nitro.cs | 3 - SabreTools.Serialization/Deserializers/PAK.cs | 7 +- SabreTools.Serialization/Deserializers/PFF.cs | 6 +- .../Deserializers/PKZIP.cs | 4 +- .../Deserializers/PlayJAudio.cs | 6 +- .../Deserializers/PlayJPlaylist.cs | 7 +- .../Deserializers/Quantum.cs | 32 +- SabreTools.Serialization/Deserializers/SFB.cs | 3 - SabreTools.Serialization/Deserializers/SFO.cs | 3 - SabreTools.Serialization/Deserializers/SGA.cs | 772 +++++--- .../Deserializers/VBSP.cs | 1577 ++++++++--------- .../Deserializers/WAD3.cs | 3 - SabreTools.Serialization/Deserializers/XZP.cs | 26 +- .../Printers/BDPlusSVM.cs | 2 +- SabreTools.Serialization/Printers/BSP.cs | 522 +++--- SabreTools.Serialization/Printers/VBSP.cs | 1157 +++++++++--- .../StringBuilderExtensions.cs | 30 + 33 files changed, 2946 insertions(+), 2069 deletions(-) diff --git a/SabreTools.Serialization/Deserializers/AACS.cs b/SabreTools.Serialization/Deserializers/AACS.cs index 52a50973..b441e3bc 100644 --- a/SabreTools.Serialization/Deserializers/AACS.cs +++ b/SabreTools.Serialization/Deserializers/AACS.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -20,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new media key block to fill var mediaKeyBlock = new MediaKeyBlock(); @@ -73,38 +69,27 @@ namespace SabreTools.Serialization.Deserializers /// Filled record on success, null on error private static Record? ParseRecord(Stream data) { - // TODO: Use marshalling here instead of building - // The first 4 bytes make up the type and length - byte[]? typeAndLength = data.ReadBytes(4); - if (typeAndLength == null) - return null; - - RecordType type = (RecordType)typeAndLength[0]; - - // Remove the first byte and parse as big-endian - typeAndLength[0] = 0x00; - Array.Reverse(typeAndLength); - uint length = BitConverter.ToUInt32(typeAndLength, 0); + RecordType type = (RecordType)data.ReadByteValue(); + uint length = data.ReadUInt24(); // Create a record based on the type - switch (type) + return type switch { - // Recognized record types - case RecordType.EndOfMediaKeyBlock: return ParseEndOfMediaKeyBlockRecord(data, type, length); - case RecordType.ExplicitSubsetDifference: return ParseExplicitSubsetDifferenceRecord(data, type, length); - case RecordType.MediaKeyData: return ParseMediaKeyDataRecord(data, type, length); - case RecordType.SubsetDifferenceIndex: return ParseSubsetDifferenceIndexRecord(data, type, length); - case RecordType.TypeAndVersion: return ParseTypeAndVersionRecord(data, type, length); - case RecordType.DriveRevocationList: return ParseDriveRevocationListRecord(data, type, length); - case RecordType.HostRevocationList: return ParseHostRevocationListRecord(data, type, length); - case RecordType.VerifyMediaKey: return ParseVerifyMediaKeyRecord(data, type, length); - case RecordType.Copyright: return ParseCopyrightRecord(data, type, length); - - // Unrecognized record type - default: - return null; - } + // Known record types + RecordType.EndOfMediaKeyBlock => ParseEndOfMediaKeyBlockRecord(data, type, length), + RecordType.ExplicitSubsetDifference => ParseExplicitSubsetDifferenceRecord(data, type, length), + RecordType.MediaKeyData => ParseMediaKeyDataRecord(data, type, length), + RecordType.SubsetDifferenceIndex => ParseSubsetDifferenceIndexRecord(data, type, length), + RecordType.TypeAndVersion => ParseTypeAndVersionRecord(data, type, length), + RecordType.DriveRevocationList => ParseDriveRevocationListRecord(data, type, length), + RecordType.HostRevocationList => ParseHostRevocationListRecord(data, type, length), + RecordType.VerifyMediaKey => ParseVerifyMediaKeyRecord(data, type, length), + RecordType.Copyright => ParseCopyrightRecord(data, type, length), + + // Unknown record type + _ => null, + }; } /// @@ -118,7 +103,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.EndOfMediaKeyBlock) return null; - // TODO: Use marshalling here instead of building var record = new EndOfMediaKeyBlockRecord(); record.RecordType = type; @@ -140,7 +124,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.ExplicitSubsetDifference) return null; - // TODO: Use marshalling here instead of building var record = new ExplicitSubsetDifferenceRecord(); record.RecordType = type; @@ -184,7 +167,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.MediaKeyData) return null; - // TODO: Use marshalling here instead of building var record = new MediaKeyDataRecord(); record.RecordType = type; @@ -221,7 +203,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.SubsetDifferenceIndex) return null; - // TODO: Use marshalling here instead of building var record = new SubsetDifferenceIndexRecord(); record.RecordType = type; @@ -259,7 +240,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.TypeAndVersion) return null; - // TODO: Use marshalling here instead of building var record = new TypeAndVersionRecord(); record.RecordType = type; @@ -281,7 +261,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.DriveRevocationList) return null; - // TODO: Use marshalling here instead of building var record = new DriveRevocationListRecord(); record.RecordType = type; @@ -342,7 +321,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.HostRevocationList) return null; - // TODO: Use marshalling here instead of building var record = new HostRevocationListRecord(); record.RecordType = type; @@ -403,7 +381,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.VerifyMediaKey) return null; - // TODO: Use marshalling here instead of building var record = new VerifyMediaKeyRecord(); record.RecordType = type; @@ -424,7 +401,6 @@ namespace SabreTools.Serialization.Deserializers if (type != RecordType.Copyright) return null; - // TODO: Use marshalling here instead of building var record = new CopyrightRecord(); record.RecordType = type; diff --git a/SabreTools.Serialization/Deserializers/BDPlus.cs b/SabreTools.Serialization/Deserializers/BDPlus.cs index b07bb6f6..b9b0dbfb 100644 --- a/SabreTools.Serialization/Deserializers/BDPlus.cs +++ b/SabreTools.Serialization/Deserializers/BDPlus.cs @@ -56,8 +56,8 @@ namespace SabreTools.Serialization.Deserializers svm.Unknown2 = data.ReadBytes(4); svm.Length = data.ReadUInt32(); - // if (svm.Length > 0) - // svm.Data = data.ReadBytes((int)svm.Length); + if (svm.Length > 0) + svm.Data = data.ReadBytes((int)svm.Length); return svm; } diff --git a/SabreTools.Serialization/Deserializers/BFPK.cs b/SabreTools.Serialization/Deserializers/BFPK.cs index d3607500..8a567505 100644 --- a/SabreTools.Serialization/Deserializers/BFPK.cs +++ b/SabreTools.Serialization/Deserializers/BFPK.cs @@ -40,24 +40,21 @@ namespace SabreTools.Serialization.Deserializers #region Files // If we have any files - if (header.Files > 0) + var files = new FileEntry[header.Files]; + + // Read all entries in turn + for (int i = 0; i < header.Files; i++) { - var files = new FileEntry[header.Files]; + var file = ParseFileEntry(data); + if (file == null) + return null; - // Read all entries in turn - for (int i = 0; i < header.Files; i++) - { - var file = ParseFileEntry(data); - if (file == null) - return null; - - files[i] = file; - } - - // Set the files - archive.Files = files; + files[i] = file; } + // Set the files + archive.Files = files; + #endregion return archive; @@ -72,9 +69,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType
(); - if (header == null) - return null; - if (header.Magic != SignatureString) + if (header?.Magic != SignatureString) return null; return header; diff --git a/SabreTools.Serialization/Deserializers/BSP.cs b/SabreTools.Serialization/Deserializers/BSP.cs index 04cde230..01c89f71 100644 --- a/SabreTools.Serialization/Deserializers/BSP.cs +++ b/SabreTools.Serialization/Deserializers/BSP.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using System.Text; @@ -20,9 +21,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new Half-Life Level to fill var file = new BspFile(); @@ -40,326 +38,71 @@ namespace SabreTools.Serialization.Deserializers #region Lumps - // LUMP_ENTITIES [0] - var lumpEntry = header.Lumps[(int)LumpType.LUMP_ENTITIES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) + for (int l = 0; l < BSP_HEADER_LUMPS; l++) { + // Get the next lump entry + var lumpEntry = header.Lumps[l]; + if (lumpEntry == null) + continue; + if (lumpEntry.Offset == 0 || lumpEntry.Length == 0) + continue; + // Seek to the lump offset data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - // Read the lump data - var entities = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) + // Read according to the lump type + switch ((LumpType)l) { - // TODO: Read this into sets of key-value pairs - var sb = new StringBuilder(); - char c = '\0'; - do - { - c = (char)data.ReadByteValue(); - sb.Append(c); - } while (c != '}'); - - var entity = new Entity(); - entity.Attributes = new List> - { - new("REPLACE", sb.ToString()), - }; - entities.Add(entity); + case LumpType.LUMP_ENTITIES: + file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_PLANES: + file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXTURES: + file.TextureLump = ParseTextureLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_VERTICES: + file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_VISIBILITY: + // TODO: Assign when Models supports it + _ = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_NODES: + file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXINFO: + file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_FACES: + file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LIGHTING: + file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_CLIPNODES: + file.ClipnodesLump = ParseClipnodesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAVES: + file.LeavesLump = ParseLeavesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_MARKSURFACES: + file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_EDGES: + file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_SURFEDGES: + file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_MODELS: + file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + default: + // Unsupported LumpType value, ignore + break; } - - var lump = new EntitiesLump(); - lump.Entities = [.. entities]; - - file.Entities = lump; - } - - // LUMP_PLANES [1] - lumpEntry = header.Lumps[(int)LumpType.LUMP_PLANES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var planes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var plane = data.ReadType(); - if (plane != null) - planes.Add(plane); - } - - var lump = new PlanesLump(); - lump.Planes = [.. planes]; - - file.PlanesLump = lump; - } - - // LUMP_TEXTURES [2] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXTURES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the header - var lump = new TextureLump(); - lump.Header = ParseTextureHeader(data); - - // Read the lump data - var textures = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var texture = data.ReadType(); - if (texture != null) - textures.Add(texture); - } - - lump.Textures = [.. textures]; - - file.TextureLump = lump; - } - - // LUMP_VERTICES [3] - lumpEntry = header.Lumps[(int)LumpType.LUMP_VERTICES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var vertices = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - vertices.Add(data.ReadType()); - } - - var lump = new VerticesLump(); - lump.Vertices = [.. vertices]; - - file.VerticesLump = lump; - } - - // LUMP_VISIBILITY [4] - lumpEntry = header.Lumps[(int)LumpType.LUMP_VISIBILITY]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // TODO: Parse LUMP_VISIBILITY when added to model - } - - // LUMP_NODES [5] - lumpEntry = header.Lumps[(int)LumpType.LUMP_NODES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var nodes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var node = data.ReadType(); - if (node != null) - nodes.Add(node); - } - - var lump = new BspNodesLump(); - lump.Nodes = [.. nodes]; - - file.NodesLump = lump; - } - - // LUMP_TEXINFO [6] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXINFO]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var texinfos = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var texinfo = data.ReadType(); - if (texinfo != null) - texinfos.Add(texinfo); - } - - var lump = new BspTexinfoLump(); - lump.Texinfos = [.. texinfos]; - - file.TexinfoLump = lump; - } - - // LUMP_FACES [7] - lumpEntry = header.Lumps[(int)LumpType.LUMP_FACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var faces = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var face = data.ReadType(); - if (face != null) - faces.Add(face); - } - - var lump = new BspFacesLump(); - lump.Faces = [.. faces]; - - file.FacesLump = lump; - } - - // LUMP_LIGHTING [8] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LIGHTING]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lump = new LightmapLump(); - lump.Lightmap = new byte[lumpEntry.Length / 3, 3]; - - for (int i = 0; i < lumpEntry.Length / 3; i++) - for (int j = 0; j < 3; j++) - { - lump.Lightmap[i, j] = data.ReadByteValue(); - } - - file.LightmapLump = lump; - } - - // LUMP_CLIPNODES [9] - lumpEntry = header.Lumps[(int)LumpType.LUMP_CLIPNODES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var clipnodes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var clipnode = data.ReadType(); - if (clipnode != null) - clipnodes.Add(clipnode); - } - - var lump = new ClipnodesLump(); - lump.Clipnodes = [.. clipnodes]; - - file.ClipnodesLump = lump; - } - - // LUMP_LEAVES [10] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAVES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var leaves = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var leaf = data.ReadType(); - if (leaf != null) - leaves.Add(leaf); - } - - var lump = new BspLeavesLump(); - lump.Leaves = [.. leaves]; - - file.LeavesLump = lump; - } - - // LUMP_MARKSURFACES [11] - lumpEntry = header.Lumps[(int)LumpType.LUMP_MARKSURFACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var marksurfaces = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - marksurfaces.Add(data.ReadUInt16()); - } - - var lump = new MarksurfacesLump(); - lump.Marksurfaces = [.. marksurfaces]; - - file.MarksurfacesLump = lump; - } - - // LUMP_EDGES [12] - lumpEntry = header.Lumps[(int)LumpType.LUMP_EDGES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var edges = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var edge = data.ReadType(); - if (edge != null) - edges.Add(edge); - } - - var lump = new EdgesLump(); - lump.Edges = [.. edges]; - - file.EdgesLump = lump; - } - - // LUMP_SURFEDGES [13] - lumpEntry = header.Lumps[(int)LumpType.LUMP_SURFEDGES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var surfedges = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - surfedges.Add(data.ReadInt32()); - } - - var lump = new SurfedgesLump(); - lump.Surfedges = [.. surfedges]; - - file.SurfedgesLump = lump; - } - - // LUMP_MODELS [14] - lumpEntry = header.Lumps[(int)LumpType.LUMP_MODELS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var models = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var model = data.ReadType(); - if (model != null) - models.Add(model); - } - - var lump = new BspModelsLump(); - lump.Models = [.. models]; - - file.ModelsLump = lump; } #endregion @@ -391,6 +134,88 @@ namespace SabreTools.Serialization.Deserializers return header; } + /// + /// 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('}'); + Array.ForEach(lumpSections, s => s.Trim('{', '}')); + + // 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]; + string[] lines = section.Split('\n'); + Array.ForEach(lines, l => l.Trim()); + + // 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 != null) + planes.Add(plane); + } + + return new PlanesLump { Planes = [.. planes] }; + } + + /// + /// Parse a Stream into LUMP_TEXTURES + /// + /// Stream to parse + /// Filled LUMP_TEXTURES on success, null on error + private static TextureLump? ParseTextureLump(Stream data, int offset, int length) + { + var lump = new TextureLump(); + + lump.Header = ParseTextureHeader(data); + var textures = new List(); + while (data.Position < offset + length) + { + var texture = data.ReadType(); + if (texture != null) + textures.Add(texture); + } + + lump.Textures = [.. textures]; + return lump; + } + /// /// Parse a Stream into a Half-Life Level texture header /// @@ -410,5 +235,222 @@ namespace SabreTools.Serialization.Deserializers return textureHeader; } + + /// + /// Parse a Stream into LUMP_VERTICES + /// + /// Stream to parse + /// Filled LUMP_VERTICES on success, null on error + private static VerticesLump? ParseVerticesLump(Stream data, int offset, int length) + { + var vertices = new List(); + while (data.Position < offset + length) + { + vertices.Add(data.ReadType()); + } + + 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.ReadInt32(); + lump.ByteOffsets = new int[lump.NumClusters, 2]; + for (int i = 0; i < lump.NumClusters; i++) + { + for (int j = 0; j < 2; j++) + { + lump.ByteOffsets[i, j] = data.ReadInt32(); + } + } + + return lump; + } + + /// + /// Parse a Stream into LUMP_NODES + /// + /// Stream to parse + /// Filled LUMP_NODES on success, null on error + private static BspNodesLump? ParseNodesLump(Stream data, int offset, int length) + { + var nodes = new List(); + while (data.Position < offset + length) + { + var node = data.ReadType(); + if (node != null) + nodes.Add(node); + } + + return new BspNodesLump { Nodes = [.. nodes] }; + } + + /// + /// Parse a Stream into LUMP_TEXINFO + /// + /// Stream to parse + /// Filled LUMP_TEXINFO on success, null on error + private static BspTexinfoLump? ParseTexinfoLump(Stream data, int offset, int length) + { + var texinfos = new List(); + while (data.Position < offset + length) + { + var texinfo = data.ReadType(); + if (texinfo != null) + texinfos.Add(texinfo); + } + + return new BspTexinfoLump { Texinfos = [.. texinfos] }; + } + + /// + /// Parse a Stream into LUMP_FACES + /// + /// Stream to parse + /// Filled LUMP_FACES on success, null on error + private static BspFacesLump? ParseFacesLump(Stream data, int offset, int length) + { + var faces = new List(); + while (data.Position < offset + length) + { + var face = data.ReadType(); + if (face != null) + faces.Add(face); + } + + return new BspFacesLump { Faces = [.. faces] }; + } + + /// + /// 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, 3]; + + for (int i = 0; i < length / 3; i++) + { + for (int j = 0; j < 3; j++) + { + lump.Lightmap[i, j] = data.ReadByteValue(); + } + } + + return lump; + } + + /// + /// Parse a Stream into LUMP_CLIPNODES + /// + /// Stream to parse + /// Filled LUMP_CLIPNODES on success, null on error + private static ClipnodesLump? ParseClipnodesLump(Stream data, int offset, int length) + { + var clipnodes = new List(); + while (data.Position < offset + length) + { + var clipnode = data.ReadType(); + if (clipnode != null) + clipnodes.Add(clipnode); + } + + return new ClipnodesLump { Clipnodes = [.. clipnodes] }; + } + + /// + /// Parse a Stream into LUMP_LEAVES + /// + /// Stream to parse + /// Filled LUMP_LEAVES on success, null on error + private static BspLeavesLump? ParseLeavesLump(Stream data, int offset, int length) + { + var leaves = new List(); + while (data.Position < offset + length) + { + var leaf = data.ReadType(); + if (leaf != null) + leaves.Add(leaf); + } + + return new BspLeavesLump { Leaves = [.. leaves] }; + } + + /// + /// Parse a Stream into LUMP_MARKSURFACES + /// + /// Stream to parse + /// Filled LUMP_MARKSURFACES 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.ReadUInt16()); + } + + 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 != 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.ReadInt32()); + } + + return new SurfedgesLump { Surfedges = [.. surfedges] }; + } + + /// + /// Parse a Stream into LUMP_MODELS + /// + /// Stream to parse + /// Filled LUMP_MODELS on success, null on error + private static BspModelsLump? ParseModelsLump(Stream data, int offset, int length) + { + var models = new List(); + while (data.Position < offset + length) + { + var model = data.ReadType(); + if (model != null) + models.Add(model); + } + + return new BspModelsLump { Models = [.. models] }; + } } } \ No newline at end of file diff --git a/SabreTools.Serialization/Deserializers/CFB.cs b/SabreTools.Serialization/Deserializers/CFB.cs index 75e6b933..cc92efd4 100644 --- a/SabreTools.Serialization/Deserializers/CFB.cs +++ b/SabreTools.Serialization/Deserializers/CFB.cs @@ -21,9 +21,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new binary to fill var binary = new Binary(); @@ -235,9 +232,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType(); - if (header == null) - return null; - if (header.Signature != SignatureUInt64) + if (header?.Signature != SignatureUInt64) return null; if (header.ByteOrder != 0xFFFE) return null; diff --git a/SabreTools.Serialization/Deserializers/CIA.cs b/SabreTools.Serialization/Deserializers/CIA.cs index a5ee3d1e..96add723 100644 --- a/SabreTools.Serialization/Deserializers/CIA.cs +++ b/SabreTools.Serialization/Deserializers/CIA.cs @@ -19,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new CIA archive to fill var cia = new Models.N3DS.CIA(); diff --git a/SabreTools.Serialization/Deserializers/GCF.cs b/SabreTools.Serialization/Deserializers/GCF.cs index c2e7cca1..895f6bf4 100644 --- a/SabreTools.Serialization/Deserializers/GCF.cs +++ b/SabreTools.Serialization/Deserializers/GCF.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.IO; using System.Text; using SabreTools.IO.Extensions; @@ -173,7 +172,7 @@ namespace SabreTools.Serialization.Deserializers long directoryNamesEnd = data.Position + directoryHeader.NameSize; // Create the string dictionary - file.DirectoryNames = new Dictionary(); + file.DirectoryNames = []; // Loop and read the null-terminated strings while (data.Position < directoryNamesEnd) @@ -184,10 +183,7 @@ namespace SabreTools.Serialization.Deserializers { data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current); byte[]? endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position)); - if (endingData != null) - directoryName = Encoding.ASCII.GetString(endingData); - else - directoryName = null; + directoryName = endingData != null ? Encoding.ASCII.GetString(endingData) : null; } file.DirectoryNames[nameOffset] = directoryName; diff --git a/SabreTools.Serialization/Deserializers/Hashfile.cs b/SabreTools.Serialization/Deserializers/Hashfile.cs index d45d46b9..8dffd4ca 100644 --- a/SabreTools.Serialization/Deserializers/Hashfile.cs +++ b/SabreTools.Serialization/Deserializers/Hashfile.cs @@ -88,6 +88,8 @@ namespace SabreTools.Serialization.Deserializers // Create lists for each hash type var sfvList = new List(); + var md2List = new List(); + var md4List = new List(); var md5List = new List(); var sha1List = new List(); var sha256List = new List(); @@ -115,6 +117,22 @@ namespace SabreTools.Serialization.Deserializers }; sfvList.Add(sfv); break; + case HashType.MD2: + var md2 = new MD2 + { + Hash = lineParts[0], + File = string.Join(" ", lineParts, 1, lineParts.Length - 1), + }; + md2List.Add(md2); + break; + case HashType.MD4: + var md4 = new MD4 + { + Hash = lineParts[0], + File = string.Join(" ", lineParts, 1, lineParts.Length - 1), + }; + md4List.Add(md4); + break; case HashType.MD5: var md5 = new MD5 { @@ -172,6 +190,12 @@ namespace SabreTools.Serialization.Deserializers case HashType.CRC32: dat.SFV = [.. sfvList]; break; + case HashType.MD2: + dat.MD2 = [.. md2List]; + break; + case HashType.MD4: + dat.MD4 = [.. md4List]; + break; case HashType.MD5: dat.MD5 = [.. md5List]; break; diff --git a/SabreTools.Serialization/Deserializers/IRD.cs b/SabreTools.Serialization/Deserializers/IRD.cs index 37686caa..5233033c 100644 --- a/SabreTools.Serialization/Deserializers/IRD.cs +++ b/SabreTools.Serialization/Deserializers/IRD.cs @@ -17,9 +17,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new IRD to fill var ird = new Models.IRD.File(); diff --git a/SabreTools.Serialization/Deserializers/InstallShieldArchiveV3.cs b/SabreTools.Serialization/Deserializers/InstallShieldArchiveV3.cs index 5fc303c6..d83c18af 100644 --- a/SabreTools.Serialization/Deserializers/InstallShieldArchiveV3.cs +++ b/SabreTools.Serialization/Deserializers/InstallShieldArchiveV3.cs @@ -18,9 +18,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new archive to fill var archive = new Archive(); @@ -100,7 +97,7 @@ namespace SabreTools.Serialization.Deserializers if (header == null) return null; - if (header.Signature1 != 0x8C655D13) // TODO: Move constant to Models + if (header?.Signature1 != Constants.HeaderSignature) return null; if (header.TocAddress >= data.Length) return null; diff --git a/SabreTools.Serialization/Deserializers/InstallShieldCabinet.cs b/SabreTools.Serialization/Deserializers/InstallShieldCabinet.cs index e6d5cb0b..9f800490 100644 --- a/SabreTools.Serialization/Deserializers/InstallShieldCabinet.cs +++ b/SabreTools.Serialization/Deserializers/InstallShieldCabinet.cs @@ -21,9 +21,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new cabinet to fill var cabinet = new Cabinet(); @@ -337,9 +334,7 @@ namespace SabreTools.Serialization.Deserializers { var commonHeader = data.ReadType(); - if (commonHeader == null) - return null; - if (commonHeader.Signature != SignatureString) + if (commonHeader?.Signature != SignatureString) return null; return commonHeader; @@ -353,7 +348,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled volume header on success, null on error public static VolumeHeader ParseVolumeHeader(Stream data, int majorVersion) { - VolumeHeader volumeHeader = new VolumeHeader(); + var volumeHeader = new VolumeHeader(); // Read the descriptor based on version if (majorVersion <= 5) @@ -649,7 +644,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled file descriptor on success, null on error public static FileDescriptor ParseFileDescriptor(Stream data, int majorVersion, uint descriptorOffset) { - FileDescriptor fileDescriptor = new FileDescriptor(); + var fileDescriptor = new FileDescriptor(); // Read the descriptor based on version if (majorVersion <= 5) diff --git a/SabreTools.Serialization/Deserializers/LinearExecutable.cs b/SabreTools.Serialization/Deserializers/LinearExecutable.cs index 8203b23d..3968de39 100644 --- a/SabreTools.Serialization/Deserializers/LinearExecutable.cs +++ b/SabreTools.Serialization/Deserializers/LinearExecutable.cs @@ -427,9 +427,7 @@ namespace SabreTools.Serialization.Deserializers { var informationBlock = data.ReadType(); - if (informationBlock == null) - return null; - if (informationBlock.Signature != LESignatureString && informationBlock.Signature != LXSignatureString) + if (informationBlock?.Signature != LESignatureString && informationBlock?.Signature != LXSignatureString) return null; return informationBlock; diff --git a/SabreTools.Serialization/Deserializers/MoPaQ.cs b/SabreTools.Serialization/Deserializers/MoPaQ.cs index 1596929f..220ce8fe 100644 --- a/SabreTools.Serialization/Deserializers/MoPaQ.cs +++ b/SabreTools.Serialization/Deserializers/MoPaQ.cs @@ -21,9 +21,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new archive to fill var archive = new Archive(); @@ -32,7 +29,7 @@ namespace SabreTools.Serialization.Deserializers // Check for User Data uint possibleSignature = data.ReadUInt32(); data.Seek(-4, SeekOrigin.Current); - if (possibleSignature == 0x1B51504D) + if (possibleSignature == UserDataSignatureUInt32) { // Save the current position for offset correction long basePtr = data.Position; @@ -56,7 +53,7 @@ namespace SabreTools.Serialization.Deserializers // Check for the Header possibleSignature = data.ReadUInt32(); data.Seek(-4, SeekOrigin.Current); - if (possibleSignature == 0x1A51504D) + if (possibleSignature == ArchiveHeaderSignatureUInt32) { // Try to parse the archive header var archiveHeader = ParseArchiveHeader(data); @@ -406,9 +403,7 @@ namespace SabreTools.Serialization.Deserializers { var userData = data.ReadType(); - if (userData == null) - return null; - if (userData.Signature != UserDataSignatureString) + if (userData?.Signature != UserDataSignatureString) return null; return userData; diff --git a/SabreTools.Serialization/Deserializers/N3DS.cs b/SabreTools.Serialization/Deserializers/N3DS.cs index 470bce4e..d9455fe9 100644 --- a/SabreTools.Serialization/Deserializers/N3DS.cs +++ b/SabreTools.Serialization/Deserializers/N3DS.cs @@ -20,9 +20,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new cart image to fill var cart = new Cart(); diff --git a/SabreTools.Serialization/Deserializers/NCF.cs b/SabreTools.Serialization/Deserializers/NCF.cs index 081981b6..6ab1bdc5 100644 --- a/SabreTools.Serialization/Deserializers/NCF.cs +++ b/SabreTools.Serialization/Deserializers/NCF.cs @@ -91,10 +91,7 @@ namespace SabreTools.Serialization.Deserializers { data.Seek(-directoryName?.Length ?? 0, SeekOrigin.Current); byte[]? endingData = data.ReadBytes((int)(directoryNamesEnd - data.Position)); - if (endingData != null) - directoryName = Encoding.ASCII.GetString(endingData); - else - directoryName = null; + directoryName = endingData != null ? Encoding.ASCII.GetString(endingData) : null; } file.DirectoryNames[nameOffset] = directoryName; diff --git a/SabreTools.Serialization/Deserializers/NewExecutable.cs b/SabreTools.Serialization/Deserializers/NewExecutable.cs index a83d1d65..54fcb960 100644 --- a/SabreTools.Serialization/Deserializers/NewExecutable.cs +++ b/SabreTools.Serialization/Deserializers/NewExecutable.cs @@ -214,9 +214,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType(); - if (header == null) - return null; - if (header.Magic != SignatureString) + if (header?.Magic != SignatureString) return null; return header; diff --git a/SabreTools.Serialization/Deserializers/Nitro.cs b/SabreTools.Serialization/Deserializers/Nitro.cs index 2397ce9c..b3eb122e 100644 --- a/SabreTools.Serialization/Deserializers/Nitro.cs +++ b/SabreTools.Serialization/Deserializers/Nitro.cs @@ -19,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new cart image to fill var cart = new Cart(); diff --git a/SabreTools.Serialization/Deserializers/PAK.cs b/SabreTools.Serialization/Deserializers/PAK.cs index e8473a9a..6acb4570 100644 --- a/SabreTools.Serialization/Deserializers/PAK.cs +++ b/SabreTools.Serialization/Deserializers/PAK.cs @@ -18,9 +18,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new Half-Life Package to fill var file = new Models.PAK.File(); @@ -73,9 +70,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType
(); - if (header == null) - return null; - if (header.Signature != SignatureString) + if (header?.Signature != SignatureString) return null; return header; diff --git a/SabreTools.Serialization/Deserializers/PFF.cs b/SabreTools.Serialization/Deserializers/PFF.cs index 8743ea6e..f9d287ec 100644 --- a/SabreTools.Serialization/Deserializers/PFF.cs +++ b/SabreTools.Serialization/Deserializers/PFF.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Runtime.InteropServices; using System.Text; using SabreTools.IO.Extensions; using SabreTools.Models.PFF; @@ -20,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new archive to fill var archive = new Archive(); @@ -56,7 +52,7 @@ namespace SabreTools.Serialization.Deserializers { var file = ParseSegment(data, header.FileSegmentSize); if (file == null) - return null; + continue; archive.Segments[i] = file; } diff --git a/SabreTools.Serialization/Deserializers/PKZIP.cs b/SabreTools.Serialization/Deserializers/PKZIP.cs index 33f3cd48..1f03c51b 100644 --- a/SabreTools.Serialization/Deserializers/PKZIP.cs +++ b/SabreTools.Serialization/Deserializers/PKZIP.cs @@ -168,7 +168,7 @@ namespace SabreTools.Serialization.Deserializers #region Local File // Setup all of the collections - var localFileHeaders = new List(); + var localFileHeaders = new List(); var encryptionHeaders = new List(); var fileData = new List(); // TODO: Should this data be read here? var dataDescriptors = new List(); @@ -200,7 +200,7 @@ namespace SabreTools.Serialization.Deserializers if (localFileHeader == null) { // Add a placeholder null item - localFileHeaders.Add(null); + localFileHeaders.Add(new LocalFileHeader()); encryptionHeaders.Add(null); fileData.Add([]); dataDescriptors.Add(null); diff --git a/SabreTools.Serialization/Deserializers/PlayJAudio.cs b/SabreTools.Serialization/Deserializers/PlayJAudio.cs index 5534038b..c237cd60 100644 --- a/SabreTools.Serialization/Deserializers/PlayJAudio.cs +++ b/SabreTools.Serialization/Deserializers/PlayJAudio.cs @@ -303,7 +303,7 @@ namespace SabreTools.Serialization.Deserializers private static UnknownBlock1 ParseUnknownBlock1(Stream data) { // TODO: Use marshalling here instead of building - UnknownBlock1 unknownBlock1 = new UnknownBlock1(); + var unknownBlock1 = new UnknownBlock1(); unknownBlock1.Length = data.ReadUInt32(); unknownBlock1.Data = data.ReadBytes((int)unknownBlock1.Length); @@ -319,7 +319,7 @@ namespace SabreTools.Serialization.Deserializers private static UnknownBlock3 ParseUnknownBlock3(Stream data) { // TODO: Use marshalling here instead of building - UnknownBlock3 unknownBlock3 = new UnknownBlock3(); + var unknownBlock3 = new UnknownBlock3(); // No-op because we don't even know the length @@ -334,7 +334,7 @@ namespace SabreTools.Serialization.Deserializers private static DataFile ParseDataFile(Stream data) { // TODO: Use marshalling here instead of building - DataFile dataFile = new DataFile(); + var dataFile = new DataFile(); dataFile.FileNameLength = data.ReadUInt16(); byte[]? fileName = data.ReadBytes(dataFile.FileNameLength); diff --git a/SabreTools.Serialization/Deserializers/PlayJPlaylist.cs b/SabreTools.Serialization/Deserializers/PlayJPlaylist.cs index 449a0b56..d7d08a15 100644 --- a/SabreTools.Serialization/Deserializers/PlayJPlaylist.cs +++ b/SabreTools.Serialization/Deserializers/PlayJPlaylist.cs @@ -17,9 +17,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new playlist to fill var playlist = new Playlist(); @@ -46,7 +43,7 @@ namespace SabreTools.Serialization.Deserializers long currentOffset = data.Position; var entryHeader = PlayJAudio.DeserializeStream(data, currentOffset); if (entryHeader == null) - return null; + continue; playlist.AudioFiles[i] = entryHeader; } @@ -64,7 +61,7 @@ namespace SabreTools.Serialization.Deserializers private static PlaylistHeader ParsePlaylistHeader(Stream data) { // TODO: Use marshalling here instead of building - PlaylistHeader playlistHeader = new PlaylistHeader(); + var playlistHeader = new PlaylistHeader(); playlistHeader.TrackCount = data.ReadUInt32(); playlistHeader.Data = data.ReadBytes(52); diff --git a/SabreTools.Serialization/Deserializers/Quantum.cs b/SabreTools.Serialization/Deserializers/Quantum.cs index 4e0f7a1a..3da2a4bd 100644 --- a/SabreTools.Serialization/Deserializers/Quantum.cs +++ b/SabreTools.Serialization/Deserializers/Quantum.cs @@ -19,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new archive to fill var archive = new Archive(); @@ -40,24 +37,21 @@ namespace SabreTools.Serialization.Deserializers #region File List // If we have any files - if (header.FileCount > 0) + var fileDescriptors = new FileDescriptor[header.FileCount]; + + // Read all entries in turn + for (int i = 0; i < header.FileCount; i++) { - var fileDescriptors = new FileDescriptor[header.FileCount]; + var file = ParseFileDescriptor(data, header.MinorVersion); + if (file == null) + return null; - // Read all entries in turn - for (int i = 0; i < header.FileCount; i++) - { - var file = ParseFileDescriptor(data, header.MinorVersion); - if (file == null) - return null; - - fileDescriptors[i] = file; - } - - // Set the file list - archive.FileList = fileDescriptors; + fileDescriptors[i] = file; } + // Set the file list + archive.FileList = fileDescriptors; + #endregion // Cache the compressed data offset @@ -75,9 +69,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType
(); - if (header == null) - return null; - if (header.Signature != SignatureString) + if (header?.Signature != SignatureString) return null; return header; diff --git a/SabreTools.Serialization/Deserializers/SFB.cs b/SabreTools.Serialization/Deserializers/SFB.cs index 5d2026e2..6cffcc19 100644 --- a/SabreTools.Serialization/Deserializers/SFB.cs +++ b/SabreTools.Serialization/Deserializers/SFB.cs @@ -17,9 +17,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Deserialize the SFB var sfb = data.ReadType(); if (sfb == null) diff --git a/SabreTools.Serialization/Deserializers/SFO.cs b/SabreTools.Serialization/Deserializers/SFO.cs index b09b992e..6b9457c1 100644 --- a/SabreTools.Serialization/Deserializers/SFO.cs +++ b/SabreTools.Serialization/Deserializers/SFO.cs @@ -17,9 +17,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - int initialOffset = (int)data.Position; - // Create a new SFO to fill var sfo = new Models.PlayStation3.SFO(); diff --git a/SabreTools.Serialization/Deserializers/SGA.cs b/SabreTools.Serialization/Deserializers/SGA.cs index 48884bfa..c22639b7 100644 --- a/SabreTools.Serialization/Deserializers/SGA.cs +++ b/SabreTools.Serialization/Deserializers/SGA.cs @@ -20,9 +20,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new SGA to fill var file = new Models.SGA.File(); @@ -78,7 +75,7 @@ namespace SabreTools.Serialization.Deserializers // Versions 4 and 5 share the same header case 4: case 5: - Header4 header4 = new Header4(); + var header4 = new Header4(); header4.Signature = signature; header4.MajorVersion = majorVersion; @@ -97,7 +94,7 @@ namespace SabreTools.Serialization.Deserializers // Versions 6 and 7 share the same header case 6: case 7: - Header6 header6 = new Header6(); + var header6 = new Header6(); header6.Signature = signature; header6.MajorVersion = majorVersion; @@ -125,20 +122,24 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA directory on success, null on error private static Models.SGA.Directory? ParseDirectory(Stream data, ushort majorVersion) { - #region Directory - - // Create the appropriate type of directory - Models.SGA.Directory directory; - switch (majorVersion) + return majorVersion switch { - case 4: directory = new Directory4(); break; - case 5: directory = new Directory5(); break; - case 6: directory = new Directory6(); break; - case 7: directory = new Directory7(); break; - default: return null; - } + 4 => ParseDirectory4(data), + 5 => ParseDirectory5(data), + 6 => ParseDirectory6(data), + 7 => ParseDirectory7(data), + _ => null, + }; + } - #endregion + /// + /// Parse a Stream into an SGA directory + /// + /// Stream to parse + /// Filled SGA directory on success, null on error + private static Directory4? ParseDirectory4(Stream data) + { + var directory = new Directory4(); // Cache the current offset long currentOffset = data.Position; @@ -146,36 +147,19 @@ namespace SabreTools.Serialization.Deserializers #region Directory Header // Try to parse the directory header - var directoryHeader = ParseDirectoryHeader(data, majorVersion); + var directoryHeader = ParseDirectory4Header(data); if (directoryHeader == null) return null; // Set the directory header - switch (majorVersion) - { - case 4: (directory as Directory4)!.DirectoryHeader = directoryHeader as DirectoryHeader4; break; - case 5: (directory as Directory5)!.DirectoryHeader = directoryHeader as DirectoryHeader5; break; - case 6: (directory as Directory6)!.DirectoryHeader = directoryHeader as DirectoryHeader5; break; - case 7: (directory as Directory7)!.DirectoryHeader = directoryHeader as DirectoryHeader7; break; - default: return null; - } + directory.DirectoryHeader = directoryHeader; #endregion #region Sections - // Get the sections offset - long sectionOffset; - switch (majorVersion) - { - case 4: sectionOffset = (directoryHeader as DirectoryHeader4)!.SectionOffset; break; - case 5: - case 6: sectionOffset = (directoryHeader as DirectoryHeader5)!.SectionOffset; break; - case 7: sectionOffset = (directoryHeader as DirectoryHeader7)!.SectionOffset; break; - default: return null; - } - - // Adjust the sections offset based on the directory + // Get and adjust the sections offset + long sectionOffset = directoryHeader.SectionOffset; sectionOffset += currentOffset; // Validate the offset @@ -185,67 +169,21 @@ namespace SabreTools.Serialization.Deserializers // Seek to the sections data.Seek(sectionOffset, SeekOrigin.Begin); - // Get the section count - uint sectionCount; - switch (majorVersion) - { - case 4: sectionCount = (directoryHeader as DirectoryHeader4)!.SectionCount; break; - case 5: - case 6: sectionCount = (directoryHeader as DirectoryHeader5)!.SectionCount; break; - case 7: sectionCount = (directoryHeader as DirectoryHeader7)!.SectionCount; break; - default: return null; - } - // Create the sections array - object[] sections; - switch (majorVersion) - { - case 4: sections = new Section4[sectionCount]; break; - case 5: - case 6: - case 7: sections = new Section5[sectionCount]; break; - default: return null; - } + directory.Sections = new Section4[directoryHeader.SectionCount]; // Try to parse the sections - for (int i = 0; i < sections.Length; i++) + for (int i = 0; i < directory.Sections.Length; i++) { - switch (majorVersion) - { - case 4: sections[i] = ParseSection4(data); break; - case 5: - case 6: - case 7: sections[i] = ParseSection5(data); break; - default: return null; - } - } - - // Assign the sections - switch (majorVersion) - { - case 4: (directory as Directory4)!.Sections = sections as Section4[]; break; - case 5: (directory as Directory5)!.Sections = sections as Section5[]; break; - case 6: (directory as Directory6)!.Sections = sections as Section5[]; break; - case 7: (directory as Directory7)!.Sections = sections as Section5[]; break; - default: return null; + directory.Sections[i] = ParseSection4(data); } #endregion #region Folders - // Get the folders offset - long folderOffset; - switch (majorVersion) - { - case 4: folderOffset = (directoryHeader as DirectoryHeader4)!.FolderOffset; break; - case 5: folderOffset = (directoryHeader as DirectoryHeader5)!.FolderOffset; break; - case 6: folderOffset = (directoryHeader as DirectoryHeader5)!.FolderOffset; break; - case 7: folderOffset = (directoryHeader as DirectoryHeader7)!.FolderOffset; break; - default: return null; - } - - // Adjust the folders offset based on the directory + // Get and adjust the folders offset + long folderOffset = directoryHeader.FolderOffset; folderOffset += currentOffset; // Validate the offset @@ -255,67 +193,21 @@ namespace SabreTools.Serialization.Deserializers // Seek to the folders data.Seek(folderOffset, SeekOrigin.Begin); - // Get the folder count - uint folderCount; - switch (majorVersion) - { - case 4: folderCount = (directoryHeader as DirectoryHeader4)!.FolderCount; break; - case 5: folderCount = (directoryHeader as DirectoryHeader5)!.FolderCount; break; - case 6: folderCount = (directoryHeader as DirectoryHeader5)!.FolderCount; break; - case 7: folderCount = (directoryHeader as DirectoryHeader7)!.FolderCount; break; - default: return null; - } - // Create the folders array - object[] folders; - switch (majorVersion) - { - case 4: folders = new Folder4[folderCount]; break; - case 5: folders = new Folder5[folderCount]; break; - case 6: folders = new Folder5[folderCount]; break; - case 7: folders = new Folder5[folderCount]; break; - default: return null; - } + directory.Folders = new Folder4[directoryHeader.FolderCount]; // Try to parse the folders - for (int i = 0; i < folders.Length; i++) + for (int i = 0; i < directory.Folders.Length; i++) { - switch (majorVersion) - { - case 4: folders[i] = ParseFolder4(data); break; - case 5: folders[i] = ParseFolder5(data); break; - case 6: folders[i] = ParseFolder5(data); break; - case 7: folders[i] = ParseFolder5(data); break; - default: return null; - } - } - - // Assign the folders - switch (majorVersion) - { - case 4: (directory as Directory4)!.Folders = folders as Folder4[]; break; - case 5: (directory as Directory5)!.Folders = folders as Folder5[]; break; - case 6: (directory as Directory6)!.Folders = folders as Folder5[]; break; - case 7: (directory as Directory7)!.Folders = folders as Folder5[]; break; - default: return null; + directory.Folders[i] = ParseFolder4(data); } #endregion #region Files - // Get the files offset - long fileOffset; - switch (majorVersion) - { - case 4: fileOffset = (directoryHeader as DirectoryHeader4)!.FileOffset; break; - case 5: fileOffset = (directoryHeader as DirectoryHeader5)!.FileOffset; break; - case 6: fileOffset = (directoryHeader as DirectoryHeader5)!.FileOffset; break; - case 7: fileOffset = (directoryHeader as DirectoryHeader7)!.FileOffset; break; - default: return null; - } - - // Adjust the files offset based on the directory + // Get and adjust the files offset + long fileOffset = directoryHeader.FileOffset; fileOffset += currentOffset; // Validate the offset @@ -326,66 +218,23 @@ namespace SabreTools.Serialization.Deserializers data.Seek(fileOffset, SeekOrigin.Begin); // Get the file count - uint fileCount; - switch (majorVersion) - { - case 4: fileCount = (directoryHeader as DirectoryHeader4)!.FileCount; break; - case 5: fileCount = (directoryHeader as DirectoryHeader5)!.FileCount; break; - case 6: fileCount = (directoryHeader as DirectoryHeader5)!.FileCount; break; - case 7: fileCount = (directoryHeader as DirectoryHeader7)!.FileCount; break; - default: return null; - } + uint fileCount = directoryHeader.FileCount; // Create the files array - object[] files; - switch (majorVersion) - { - case 4: files = new File4[fileCount]; break; - case 5: files = new File4[fileCount]; break; - case 6: files = new File6[fileCount]; break; - case 7: files = new File7[fileCount]; break; - default: return null; - } + directory.Files = new File4[fileCount]; // Try to parse the files - for (int i = 0; i < files.Length; i++) + for (int i = 0; i < directory.Files.Length; i++) { - switch (majorVersion) - { - case 4: files[i] = ParseFile4(data); break; - case 5: files[i] = ParseFile4(data); break; - case 6: files[i] = ParseFile6(data); break; - case 7: files[i] = ParseFile7(data); break; - default: return null; - } - } - - // Assign the files - switch (majorVersion) - { - case 4: (directory as Directory4)!.Files = files as File4[]; break; - case 5: (directory as Directory5)!.Files = files as File4[]; break; - case 6: (directory as Directory6)!.Files = files as File6[]; break; - case 7: (directory as Directory7)!.Files = files as File7[]; break; - default: return null; + directory.Files[i] = ParseFile4(data); } #endregion #region String Table - // Get the string table offset - long stringTableOffset; - switch (majorVersion) - { - case 4: stringTableOffset = (directoryHeader as DirectoryHeader4)!.StringTableOffset; break; - case 5: stringTableOffset = (directoryHeader as DirectoryHeader5)!.StringTableOffset; break; - case 6: stringTableOffset = (directoryHeader as DirectoryHeader5)!.StringTableOffset; break; - case 7: stringTableOffset = (directoryHeader as DirectoryHeader7)!.StringTableOffset; break; - default: return null; - } - - // Adjust the string table offset based on the directory + // Get and adjust the string table offset + long stringTableOffset = directoryHeader.StringTableOffset; stringTableOffset += currentOffset; // Validate the offset @@ -395,87 +244,40 @@ namespace SabreTools.Serialization.Deserializers // Seek to the string table data.Seek(stringTableOffset, SeekOrigin.Begin); - // Get the string table count - uint stringCount; - switch (majorVersion) - { - case 4: stringCount = (directoryHeader as DirectoryHeader4)!.StringTableCount; break; - case 5: stringCount = (directoryHeader as DirectoryHeader5)!.StringTableCount; break; - case 6: stringCount = (directoryHeader as DirectoryHeader5)!.StringTableCount; break; - case 7: stringCount = (directoryHeader as DirectoryHeader7)!.StringTableCount; break; - default: return null; - } - // TODO: Are these strings actually indexed by number and not position? // TODO: If indexed by position, I think it needs to be adjusted by start of table // Create the strings dictionary - Dictionary strings = new Dictionary((int)stringCount); + directory.StringTable = new Dictionary((int)directoryHeader.StringTableCount); // Get the current position to adjust the offsets long stringTableStart = data.Position; // Try to parse the strings - for (int i = 0; i < stringCount; i++) + for (int i = 0; i < directoryHeader.StringTableCount; i++) { long currentPosition = data.Position - stringTableStart; - strings[currentPosition] = data.ReadNullTerminatedAnsiString(); - } - - // Assign the files - switch (majorVersion) - { - case 4: (directory as Directory4)!.StringTable = strings; break; - case 5: (directory as Directory5)!.StringTable = strings; break; - case 6: (directory as Directory6)!.StringTable = strings; break; - case 7: (directory as Directory7)!.StringTable = strings; break; - default: return null; + directory.StringTable[currentPosition] = data.ReadNullTerminatedAnsiString(); } // Loop through all folders to assign names - for (int i = 0; i < folderCount; i++) + for (int i = 0; i < directory.Folders.Length; i++) { - uint nameOffset; - switch (majorVersion) - { - case 4: nameOffset = (directory as Directory4)!.Folders![i]!.NameOffset; break; - case 5: nameOffset = (directory as Directory5)!.Folders![i]!.NameOffset; break; - case 6: nameOffset = (directory as Directory6)!.Folders![i]!.NameOffset; break; - case 7: nameOffset = (directory as Directory7)!.Folders![i]!.NameOffset; break; - default: return null; - } + var folder = directory.Folders[i]; + if (folder == null) + continue; - switch (majorVersion) - { - case 4: (directory as Directory4)!.Folders![i]!.Name = strings[nameOffset]; break; - case 5: (directory as Directory5)!.Folders![i]!.Name = strings[nameOffset]; break; - case 6: (directory as Directory6)!.Folders![i]!.Name = strings[nameOffset]; break; - case 7: (directory as Directory7)!.Folders![i]!.Name = strings[nameOffset]; break; - default: return null; - } + folder.Name = directory.StringTable[folder.NameOffset]; } // Loop through all files to assign names - for (int i = 0; i < fileCount; i++) + for (int i = 0; i < directory.Files.Length; i++) { - uint nameOffset; - switch (majorVersion) - { - case 4: nameOffset = (directory as Directory4)!.Files![i]!.NameOffset; break; - case 5: nameOffset = (directory as Directory5)!.Files![i]!.NameOffset; break; - case 6: nameOffset = (directory as Directory6)!.Files![i]!.NameOffset; break; - case 7: nameOffset = (directory as Directory7)!.Files![i]!.NameOffset; break; - default: return null; - } + var file = directory.Files[i]; + if (file == null) + continue; - switch (majorVersion) - { - case 4: (directory as Directory4)!.Files![i]!.Name = strings[nameOffset]; break; - case 5: (directory as Directory5)!.Files![i]!.Name = strings[nameOffset]; break; - case 6: (directory as Directory6)!.Files![i]!.Name = strings[nameOffset]; break; - case 7: (directory as Directory7)!.Files![i]!.Name = strings[nameOffset]; break; - default: return null; - } + file.Name = directory.StringTable[file.NameOffset]; } #endregion @@ -484,21 +286,453 @@ namespace SabreTools.Serialization.Deserializers } /// - /// Parse a Stream into an SGA directory header + /// Parse a Stream into an SGA directory /// /// Stream to parse - /// SGA major version - /// Filled SGA directory header on success, null on error - private static object? ParseDirectoryHeader(Stream data, ushort majorVersion) + /// Filled SGA directory on success, null on error + private static Directory5? ParseDirectory5(Stream data) { - switch (majorVersion) + var directory = new Directory5(); + + // Cache the current offset + long currentOffset = data.Position; + + #region Directory Header + + // Try to parse the directory header + var directoryHeader = ParseDirectory5Header(data); + if (directoryHeader == null) + return null; + + // Set the directory header + directory.DirectoryHeader = directoryHeader; + + #endregion + + #region Sections + + // Get and adjust the sections offset + long sectionOffset = directoryHeader.SectionOffset; + sectionOffset += currentOffset; + + // Validate the offset + if (sectionOffset < 0 || sectionOffset >= data.Length) + return null; + + // Seek to the sections + data.Seek(sectionOffset, SeekOrigin.Begin); + + // Create the sections array + directory.Sections = new Section5[directoryHeader.SectionCount]; + + // Try to parse the sections + for (int i = 0; i < directory.Sections.Length; i++) { - case 4: return ParseDirectory4Header(data); - case 5: return ParseDirectory5Header(data); - case 6: return ParseDirectory5Header(data); - case 7: return ParseDirectory7Header(data); - default: return null; + directory.Sections[i] = ParseSection5(data); } + + #endregion + + #region Folders + + // Get and adjust the folders offset + long folderOffset = directoryHeader.FolderOffset; + folderOffset += currentOffset; + + // Validate the offset + if (folderOffset < 0 || folderOffset >= data.Length) + return null; + + // Seek to the folders + data.Seek(folderOffset, SeekOrigin.Begin); + + // Create the folders array + directory.Folders = new Folder5[directoryHeader.FolderCount]; + + // Try to parse the folders + for (int i = 0; i < directory.Folders.Length; i++) + { + directory.Folders[i] = ParseFolder5(data); + } + + #endregion + + #region Files + + // Get and adjust the files offset + long fileOffset = directoryHeader.FileOffset; + fileOffset += currentOffset; + + // Validate the offset + if (fileOffset < 0 || fileOffset >= data.Length) + return null; + + // Seek to the files + data.Seek(fileOffset, SeekOrigin.Begin); + + // Create the files array + directory.Files = new File4[directoryHeader.FileCount]; + + // Try to parse the files + for (int i = 0; i < directory.Files.Length; i++) + { + directory.Files[i] = ParseFile4(data); + } + + #endregion + + #region String Table + + // Get and adjust the string table offset + long stringTableOffset = directoryHeader.StringTableOffset; + stringTableOffset += currentOffset; + + // Validate the offset + if (stringTableOffset < 0 || stringTableOffset >= data.Length) + return null; + + // Seek to the string table + data.Seek(stringTableOffset, SeekOrigin.Begin); + + // TODO: Are these strings actually indexed by number and not position? + // TODO: If indexed by position, I think it needs to be adjusted by start of table + + // Create the strings dictionary + directory.StringTable = new Dictionary((int)directoryHeader.StringTableCount); + + // Get the current position to adjust the offsets + long stringTableStart = data.Position; + + // Try to parse the strings + for (int i = 0; i < directoryHeader.StringTableCount; i++) + { + long currentPosition = data.Position - stringTableStart; + directory.StringTable[currentPosition] = data.ReadNullTerminatedAnsiString(); + } + + // Loop through all folders to assign names + for (int i = 0; i < directory.Folders.Length; i++) + { + var folder = directory.Folders[i]; + if (folder == null) + continue; + + folder.Name = directory.StringTable[folder.NameOffset]; + } + + // Loop through all files to assign names + for (int i = 0; i < directory.Files.Length; i++) + { + var file = directory.Files[i]; + if (file == null) + continue; + + file.Name = directory.StringTable[file.NameOffset]; + } + + #endregion + + return directory; + } + + /// + /// Parse a Stream into an SGA directory + /// + /// Stream to parse + /// Filled SGA directory on success, null on error + private static Directory6? ParseDirectory6(Stream data) + { + var directory = new Directory6(); + + // Cache the current offset + long currentOffset = data.Position; + + #region Directory Header + + // Try to parse the directory header + var directoryHeader = ParseDirectory5Header(data); + if (directoryHeader == null) + return null; + + // Set the directory header + directory.DirectoryHeader = directoryHeader; + + #endregion + + #region Sections + + // Get and adjust the sections offset + long sectionOffset = directoryHeader.SectionOffset; + sectionOffset += currentOffset; + + // Validate the offset + if (sectionOffset < 0 || sectionOffset >= data.Length) + return null; + + // Seek to the sections + data.Seek(sectionOffset, SeekOrigin.Begin); + + // Create the sections array + directory.Sections = new Section5[directoryHeader.SectionCount]; + + // Try to parse the sections + for (int i = 0; i < directory.Sections.Length; i++) + { + directory.Sections[i] = ParseSection5(data); + } + + #endregion + + #region Folders + + // Get and adjust the folders offset + long folderOffset = directoryHeader.FolderOffset; + folderOffset += currentOffset; + + // Validate the offset + if (folderOffset < 0 || folderOffset >= data.Length) + return null; + + // Seek to the folders + data.Seek(folderOffset, SeekOrigin.Begin); + + // Create the folders array + directory.Folders = new Folder5[directoryHeader.FolderCount]; + + // Try to parse the folders + for (int i = 0; i < directory.Folders.Length; i++) + { + directory.Folders[i] = ParseFolder5(data); + } + + #endregion + + #region Files + + // Get and adjust the files offset + long fileOffset = directoryHeader.FileOffset; + fileOffset += currentOffset; + + // Validate the offset + if (fileOffset < 0 || fileOffset >= data.Length) + return null; + + // Seek to the files + data.Seek(fileOffset, SeekOrigin.Begin); + + // Create the files array + directory.Files = new File6[directoryHeader.FileCount]; + + // Try to parse the files + for (int i = 0; i < directory.Files.Length; i++) + { + directory.Files[i] = ParseFile6(data); + } + + #endregion + + #region String Table + + // Get and adjust the string table offset + long stringTableOffset = directoryHeader.StringTableOffset; + stringTableOffset += currentOffset; + + // Validate the offset + if (stringTableOffset < 0 || stringTableOffset >= data.Length) + return null; + + // Seek to the string table + data.Seek(stringTableOffset, SeekOrigin.Begin); + + // TODO: Are these strings actually indexed by number and not position? + // TODO: If indexed by position, I think it needs to be adjusted by start of table + + // Create the strings dictionary + directory.StringTable = new Dictionary((int)directoryHeader.StringTableCount); + + // Get the current position to adjust the offsets + long stringTableStart = data.Position; + + // Try to parse the strings + for (int i = 0; i < directoryHeader.StringTableCount; i++) + { + long currentPosition = data.Position - stringTableStart; + directory.StringTable[currentPosition] = data.ReadNullTerminatedAnsiString(); + } + + // Loop through all folders to assign names + for (int i = 0; i < directory.Folders.Length; i++) + { + var folder = directory.Folders[i]; + if (folder == null) + continue; + + folder.Name = directory.StringTable[folder.NameOffset]; + } + + // Loop through all files to assign names + for (int i = 0; i < directory.Files.Length; i++) + { + var file = directory.Files[i]; + if (file == null) + continue; + + file.Name = directory.StringTable[file.NameOffset]; + } + + #endregion + + return directory; + } + + /// + /// Parse a Stream into an SGA directory + /// + /// Stream to parse + /// Filled SGA directory on success, null on error + private static Directory7? ParseDirectory7(Stream data) + { + var directory = new Directory7(); + + // Cache the current offset + long currentOffset = data.Position; + + #region Directory Header + + // Try to parse the directory header + var directoryHeader = ParseDirectory7Header(data); + if (directoryHeader == null) + return null; + + // Set the directory header + directory.DirectoryHeader = directoryHeader; + + #endregion + + #region Sections + + // Get and adjust the sections offset + long sectionOffset = directoryHeader.SectionOffset; + sectionOffset += currentOffset; + + // Validate the offset + if (sectionOffset < 0 || sectionOffset >= data.Length) + return null; + + // Seek to the sections + data.Seek(sectionOffset, SeekOrigin.Begin); + + // Create the sections array + directory.Sections = new Section5[directoryHeader.SectionCount]; + + // Try to parse the sections + for (int i = 0; i < directory.Sections.Length; i++) + { + directory.Sections[i] = ParseSection5(data); + } + + #endregion + + #region Folders + + // Get and adjust the folders offset + long folderOffset = directoryHeader.FolderOffset; + folderOffset += currentOffset; + + // Validate the offset + if (folderOffset < 0 || folderOffset >= data.Length) + return null; + + // Seek to the folders + data.Seek(folderOffset, SeekOrigin.Begin); + + // Create the folders array + directory.Folders = new Folder5[directoryHeader.FolderCount]; + + // Try to parse the folders + for (int i = 0; i < directory.Folders.Length; i++) + { + directory.Folders[i] = ParseFolder5(data); + } + + #endregion + + #region Files + + // Get and adjust the files offset + long fileOffset = directoryHeader.FileOffset; + fileOffset += currentOffset; + + // Validate the offset + if (fileOffset < 0 || fileOffset >= data.Length) + return null; + + // Seek to the files + data.Seek(fileOffset, SeekOrigin.Begin); + + // Create the files array + directory.Files = new File7[directoryHeader.FileCount]; + + // Try to parse the files + for (int i = 0; i < directory.Files.Length; i++) + { + directory.Files[i] = ParseFile7(data); + } + + #endregion + + #region String Table + + // Get and adjust the string table offset + long stringTableOffset = directoryHeader.StringTableOffset; + stringTableOffset += currentOffset; + + // Validate the offset + if (stringTableOffset < 0 || stringTableOffset >= data.Length) + return null; + + // Seek to the string table + data.Seek(stringTableOffset, SeekOrigin.Begin); + + // TODO: Are these strings actually indexed by number and not position? + // TODO: If indexed by position, I think it needs to be adjusted by start of table + + // Create the strings dictionary + directory.StringTable = new Dictionary((int)directoryHeader.StringTableCount); + + // Get the current position to adjust the offsets + long stringTableStart = data.Position; + + // Try to parse the strings + for (int i = 0; i < directoryHeader.StringTableCount; i++) + { + long currentPosition = data.Position - stringTableStart; + directory.StringTable[currentPosition] = data.ReadNullTerminatedAnsiString(); + } + + // Loop through all folders to assign names + for (int i = 0; i < directory.Folders.Length; i++) + { + var folder = directory.Folders[i]; + if (folder == null) + continue; + + folder.Name = directory.StringTable[folder.NameOffset]; + } + + // Loop through all files to assign names + for (int i = 0; i < directory.Files.Length; i++) + { + var file = directory.Files[i]; + if (file == null) + continue; + + file.Name = directory.StringTable[file.NameOffset]; + } + + #endregion + + return directory; } /// @@ -508,7 +742,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA directory header version 4 on success, null on error private static DirectoryHeader4 ParseDirectory4Header(Stream data) { - DirectoryHeader4 directoryHeader4 = new DirectoryHeader4(); + var directoryHeader4 = new DirectoryHeader4(); directoryHeader4.SectionOffset = data.ReadUInt32(); directoryHeader4.SectionCount = data.ReadUInt16(); @@ -529,7 +763,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA directory header version 5 on success, null on error private static DirectoryHeader5 ParseDirectory5Header(Stream data) { - DirectoryHeader5 directoryHeader5 = new DirectoryHeader5(); + var directoryHeader5 = new DirectoryHeader5(); directoryHeader5.SectionOffset = data.ReadUInt32(); directoryHeader5.SectionCount = data.ReadUInt32(); @@ -550,7 +784,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA directory header version 7 on success, null on error private static DirectoryHeader7 ParseDirectory7Header(Stream data) { - DirectoryHeader7 directoryHeader7 = new DirectoryHeader7(); + var directoryHeader7 = new DirectoryHeader7(); directoryHeader7.SectionOffset = data.ReadUInt32(); directoryHeader7.SectionCount = data.ReadUInt32(); @@ -574,7 +808,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA section version 4 on success, null on error private static Section4 ParseSection4(Stream data) { - Section4 section4 = new Section4(); + var section4 = new Section4(); byte[]? section4Alias = data.ReadBytes(64); if (section4Alias != null) @@ -599,7 +833,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA section version 5 on success, null on error private static Section5 ParseSection5(Stream data) { - Section5 section5 = new Section5(); + var section5 = new Section5(); byte[]? section5Alias = data.ReadBytes(64); if (section5Alias != null) @@ -624,7 +858,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA folder version 4 on success, null on error private static Folder4 ParseFolder4(Stream data) { - Folder4 folder4 = new Folder4(); + var folder4 = new Folder4(); folder4.NameOffset = data.ReadUInt32(); folder4.Name = null; // Read from string table @@ -644,7 +878,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA folder version 5 on success, null on error private static Folder5 ParseFolder5(Stream data) { - Folder5 folder5 = new Folder5(); + var folder5 = new Folder5(); folder5.NameOffset = data.ReadUInt32(); folder5.Name = null; // Read from string table @@ -664,7 +898,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA file version 4 on success, null on error private static File4 ParseFile4(Stream data) { - File4 file4 = new File4(); + var file4 = new File4(); file4.NameOffset = data.ReadUInt32(); file4.Name = null; // Read from string table @@ -686,7 +920,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA file version 6 on success, null on error private static File6 ParseFile6(Stream data) { - File6 file6 = new File6(); + var file6 = new File6(); file6.NameOffset = data.ReadUInt32(); file6.Name = null; // Read from string table @@ -709,7 +943,7 @@ namespace SabreTools.Serialization.Deserializers /// Filled SGA file version 7 on success, null on error private static File7 ParseFile7(Stream data) { - File7 file7 = new File7(); + var file7 = new File7(); file7.NameOffset = data.ReadUInt32(); file7.Name = null; // Read from string table diff --git a/SabreTools.Serialization/Deserializers/VBSP.cs b/SabreTools.Serialization/Deserializers/VBSP.cs index 5c67dc66..52309265 100644 --- a/SabreTools.Serialization/Deserializers/VBSP.cs +++ b/SabreTools.Serialization/Deserializers/VBSP.cs @@ -21,9 +21,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new Half-Life 2 Level to fill var file = new VbspFile(); @@ -41,804 +38,220 @@ namespace SabreTools.Serialization.Deserializers #region Lumps - // LUMP_ENTITIES [0] - var lumpEntry = header.Lumps[(int)LumpType.LUMP_ENTITIES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) + for (int l = 0; l < VBSP_HEADER_LUMPS; l++) { + // Get the next lump entry + var lumpEntry = header.Lumps[l]; + if (lumpEntry == null) + continue; + if (lumpEntry.Offset == 0 || lumpEntry.Length == 0) + continue; + // Seek to the lump offset data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - // Read the lump data - var entities = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) + // Read according to the lump type + switch ((LumpType)l) { - // TODO: Read this into sets of key-value pairs - var sb = new StringBuilder(); - char c = '\0'; - do - { - c = (char)data.ReadByteValue(); - sb.Append(c); - } while (c != '}'); + case LumpType.LUMP_ENTITIES: + file.Entities = ParseEntitiesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_PLANES: + file.PlanesLump = ParsePlanesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXTURES: + file.TexdataLump = ParseTexdataLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_VERTICES: + file.VerticesLump = ParseVerticesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_VISIBILITY: + file.VisibilityLump = ParseVisibilityLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_NODES: + file.NodesLump = ParseNodesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXINFO: + file.TexinfoLump = ParseTexinfoLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_FACES: + file.FacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LIGHTING: + file.LightmapLump = ParseLightmapLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_CLIPNODES: + file.OcclusionLump = ParseOcclusionLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAVES: + file.LeavesLump = ParseLeavesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_MARKSURFACES: + file.MarksurfacesLump = ParseMarksurfacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_EDGES: + file.EdgesLump = ParseEdgesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_SURFEDGES: + file.SurfedgesLump = ParseSurfedgesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_MODELS: + file.ModelsLump = ParseModelsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_WORLDLIGHTS: + file.LDRWorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAFFACES: + file.LeafFacesLump = ParseLeafFacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAFBRUSHES: + file.LeafBrushesLump = ParseLeafBrushesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_BRUSHES: + file.BrushesLump = ParseBrushesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_BRUSHSIDES: + file.BrushsidesLump = ParseBrushsidesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_AREAS: + // TODO: Support LUMP_AREAS [20] when in Models + break; + case LumpType.LUMP_AREAPORTALS: + // TODO: Support LUMP_AREAPORTALS [21] when in Models + break; + case LumpType.LUMP_PORTALS: + // TODO: Support LUMP_PORTALS / LUMP_UNUSED0 / LUMP_PROPCOLLISION [22] when in Models + break; + case LumpType.LUMP_CLUSTERS: + // TODO: Support LUMP_CLUSTERS / LUMP_UNUSED1 / LUMP_PROPHULLS [23] when in Models + break; + case LumpType.LUMP_PORTALVERTS: + // TODO: Support LUMP_PORTALVERTS / LUMP_UNUSED2 / LUMP_FAKEENTITIES / LUMP_PROPHULLVERTS [24] when in Models + break; + case LumpType.LUMP_CLUSTERPORTALS: + // TODO: Support LUMP_CLUSTERPORTALS / LUMP_UNUSED3 / LUMP_PROPTRIS [25] when in Models + break; + case LumpType.LUMP_DISPINFO: + file.DispInfoLump = ParseDispInfosLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_ORIGINALFACES: + file.OriginalFacesLump = ParseFacesLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_PHYSDISP: + // TODO: Support LUMP_PHYSDISP [28] when in Models + break; + case LumpType.LUMP_PHYSCOLLIDE: + // TODO: Support LUMP_PHYSCOLLIDE [29] when in Models + break; + case LumpType.LUMP_VERTNORMALS: + // TODO: Support LUMP_VERTNORMALS [30] when in Models + break; + case LumpType.LUMP_VERTNORMALINDICES: + // TODO: Support LUMP_VERTNORMALINDICES [31] when in Models + break; + case LumpType.LUMP_DISP_LIGHTMAP_ALPHAS: + // TODO: Support LUMP_DISP_LIGHTMAP_ALPHAS [32] when in Models + break; + case LumpType.LUMP_DISP_VERTS: + file.DispVertLump = ParseDispVertsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS: + // TODO: Support LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS [34] when in Models + break; + case LumpType.LUMP_GAME_LUMP: + file.GameLump = ParseGameLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAFWATERDATA: + // TODO: Support LUMP_LEAFWATERDATA [36] when in Models + break; + case LumpType.LUMP_PRIMITIVES: + // TODO: Support LUMP_PRIMITIVES [37] when in Models + break; + case LumpType.LUMP_PRIMVERTS: + // TODO: Support LUMP_PRIMVERTS [38] when in Models + break; + case LumpType.LUMP_PRIMINDICES: + // TODO: Support LUMP_PRIMINDICES [39] when in Models + break; + case LumpType.LUMP_PAKFILE: + // TODO: Support LUMP_PAKFILE [40] when in Models + break; + case LumpType.LUMP_CLIPPORTALVERTS: + // TODO: Support LUMP_CLIPPORTALVERTS [41] when in Models + break; + case LumpType.LUMP_CUBEMAPS: + file.CubemapLump = ParseCubemapsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXDATA_STRING_DATA: + file.TexdataStringData = ParseTexdataStringData(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_TEXDATA_STRING_TABLE: + file.TexdataStringTable = ParseTexdataStringTable(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_OVERLAYS: + file.OverlaysLump = ParseOverlaysLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAFMINDISTTOWATER: + // TODO: Support LUMP_LEAFMINDISTTOWATER [46] when in Models + break; + case LumpType.LUMP_FACE_MACRO_TEXTURE_INFO: + // TODO: Support LUMP_FACE_MACRO_TEXTURE_INFO [47] when in Models + break; + case LumpType.LUMP_DISP_TRIS: + file.DispTrisLump = ParseDispTrisLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_PHYSCOLLIDESURFACE: + // TODO: Support LUMP_PHYSCOLLIDESURFACE / LUMP_PROP_BLOB [49] when in Models + break; + case LumpType.LUMP_WATEROVERLAYS: + // TODO: Support LUMP_WATEROVERLAYS [50] when in Models + break; + case LumpType.LUMP_LIGHTMAPPAGES: + file.HDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LIGHTMAPPAGEINFOS: + file.LDRAmbientIndexLump = ParseAmbientIndexLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LIGHTING_HDR: + // TODO: Support LUMP_LIGHTING_HDR [53] when in Models + break; + case LumpType.LUMP_WORLDLIGHTS_HDR: + file.WorldLightsLump = ParseWorldLightsLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAF_AMBIENT_LIGHTING_HDR: + file.HDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_LEAF_AMBIENT_LIGHTING: + file.LDRAmbientLightingLump = ParseAmbientLightingLump(data, lumpEntry.Offset, lumpEntry.Length); + break; + case LumpType.LUMP_XZIPPAKFILE: + // TODO: Support LUMP_XZIPPAKFILE [57] when in Models + break; + case LumpType.LUMP_FACES_HDR: + // TODO: Support LUMP_FACES_HDR [58] when in Models + break; + case LumpType.LUMP_MAP_FLAGS: + // TODO: Support LUMP_MAP_FLAGS [59] when in Models + break; + case LumpType.LUMP_OVERLAY_FADES: + // TODO: Support LUMP_OVERLAY_FADES [60] when in Models + break; + case LumpType.LUMP_OVERLAY_SYSTEM_LEVELS: + // TODO: Support LUMP_OVERLAY_SYSTEM_LEVELS [61] when in Models + break; + case LumpType.LUMP_PHYSLEVEL: + // TODO: Support LUMP_PHYSLEVEL [62] when in Models + break; + case LumpType.LUMP_DISP_MULTIBLEND: + // TODO: Support LUMP_DISP_MULTIBLEND [63] when in Models + break; - var entity = new Entity(); - entity.Attributes = new List> - { - new("REPLACE", sb.ToString()), - }; - entities.Add(entity); + default: + // Unsupported LumpType value, ignore + break; } - - var lump = new EntitiesLump(); - lump.Entities = [.. entities]; - - file.Entities = lump; } - // LUMP_PLANES [1] - lumpEntry = header.Lumps[(int)LumpType.LUMP_PLANES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var planes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var plane = data.ReadType(); - if (plane != null) - planes.Add(plane); - } - - var lump = new PlanesLump(); - lump.Planes = [.. planes]; - - file.PlanesLump = lump; - } - - // LUMP_TEXDATA [2] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXTURES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var texdatas = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var texdata = data.ReadType(); - if (texdata != null) - texdatas.Add(texdata); - } - - var lump = new TexdataLump(); - lump.Texdatas = [.. texdatas]; - - file.TexdataLump = lump; - } - - // LUMP_VERTEXES [3] - lumpEntry = header.Lumps[(int)LumpType.LUMP_VERTICES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var vertices = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - vertices.Add(data.ReadType()); - } - - var lump = new VerticesLump(); - lump.Vertices = [.. vertices]; - - file.VerticesLump = lump; - } - - // LUMP_VISIBILITY [4] - lumpEntry = header.Lumps[(int)LumpType.LUMP_VISIBILITY]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lump = new VisibilityLump(); - - lump.NumClusters = data.ReadInt32(); - lump.ByteOffsets = new int[lump.NumClusters, 2]; - for (int i = 0; i < lump.NumClusters; i++) - for (int j = 0; j < 2; j++) - { - lump.ByteOffsets[i, j] = data.ReadByteValue(); - } - - file.VisibilityLump = lump; - } - - // LUMP_NODES [5] - lumpEntry = header.Lumps[(int)LumpType.LUMP_NODES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var nodes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var node = data.ReadType(); - if (node != null) - nodes.Add(node); - } - - var lump = new VbspNodesLump(); - lump.Nodes = [.. nodes]; - - file.NodesLump = lump; - } - - // LUMP_TEXINFO [6] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXINFO]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var texinfos = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var texinfo = data.ReadType(); - if (texinfo != null) - texinfos.Add(texinfo); - } - - var lump = new VbspTexinfoLump(); - lump.Texinfos = [.. texinfos]; - - file.TexinfoLump = lump; - } - - // LUMP_FACES [7] - lumpEntry = header.Lumps[(int)LumpType.LUMP_FACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var faces = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var face = data.ReadType(); - if (face != null) - faces.Add(face); - } - - var lump = new VbspFacesLump(); - lump.Faces = [.. faces]; - - file.FacesLump = lump; - } - - // LUMP_LIGHTING [8] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LIGHTING]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lump = new LightmapLump(); - lump.Lightmap = new byte[lumpEntry.Length / 3, 3]; - - for (int i = 0; i < lumpEntry.Length / 3; i++) - for (int j = 0; j < 3; j++) - { - lump.Lightmap[i, j] = data.ReadByteValue(); - } - - file.LightmapLump = lump; - } - - // LUMP_OCCLUSION [9] - lumpEntry = header.Lumps[(int)LumpType.LUMP_CLIPNODES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lump = new OcclusionLump(); - - lump.Count = data.ReadInt32(); - lump.Data = new OccluderData[lump.Count]; - for (int i = 0; i < lump.Count; i++) - { - var occluderData = data.ReadType(); - if (occluderData != null) - lump.Data[i] = occluderData; - } - lump.PolyDataCount = data.ReadInt32(); - lump.PolyData = new OccluderPolyData[lump.Count]; - for (int i = 0; i < lump.Count; i++) - { - var polyData = data.ReadType(); - if (polyData != null) - lump.PolyData[i] = polyData; - } - lump.VertexIndexCount = data.ReadInt32(); - lump.VertexIndices = new int[lump.VertexIndexCount]; - for (int i = 0; i < lump.VertexIndexCount; i++) - { - lump.VertexIndices[i] = data.ReadInt32(); - } - - file.OcclusionLump = lump; - } - - // LUMP_LEAVES [10] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAVES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var leaves = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - // TODO: Fix parsing between V0 and V1+ - var leaf = data.ReadType(); - if (leaf != null) - leaves.Add(leaf); - } - - var lump = new VbspLeavesLump(); - lump.Leaves = [.. leaves]; - - file.LeavesLump = lump; - } - - // LUMP_FACEIDS [11] - lumpEntry = header.Lumps[(int)LumpType.LUMP_MARKSURFACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var marksurfaces = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - marksurfaces.Add(data.ReadUInt16()); - } - - var lump = new MarksurfacesLump(); - lump.Marksurfaces = [.. marksurfaces]; - - file.MarksurfacesLump = lump; - } - - // LUMP_EDGES [12] - lumpEntry = header.Lumps[(int)LumpType.LUMP_EDGES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var edges = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var edge = data.ReadType(); - if (edge != null) - edges.Add(edge); - } - - var lump = new EdgesLump(); - lump.Edges = [.. edges]; - - file.EdgesLump = lump; - } - - // LUMP_SURFEDGES [13] - lumpEntry = header.Lumps[(int)LumpType.LUMP_SURFEDGES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var surfedges = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - surfedges.Add(data.ReadInt32()); - } - - var lump = new SurfedgesLump(); - lump.Surfedges = [.. surfedges]; - - file.SurfedgesLump = lump; - } - - // LUMP_MODELS [14] - lumpEntry = header.Lumps[(int)LumpType.LUMP_MODELS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var models = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var model = data.ReadType(); - if (model != null) - models.Add(model); - } - - var lump = new VbspModelsLump(); - lump.Models = [.. models]; - - file.ModelsLump = lump; - } - - // LUMP_WORLDLIGHTS [15] - lumpEntry = header.Lumps[(int)LumpType.LUMP_WORLDLIGHTS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var worldLights = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var worldLight = data.ReadType(); - if (worldLight != null) - worldLights.Add(worldLight); - } - - var lump = new WorldLightsLump(); - lump.WorldLights = [.. worldLights]; - - file.LDRWorldLightsLump = lump; - } - - // LUMP_LEAFFACES [16] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAFFACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var map = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - map.Add(data.ReadUInt16()); - } - - var lump = new LeafFacesLump(); - lump.Map = [.. map]; - - file.LeafFacesLump = lump; - } - - // LUMP_LEAFBRUSHES [17] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAFBRUSHES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var map = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - map.Add(data.ReadUInt16()); - } - - var lump = new LeafBrushesLump(); - lump.Map = [.. map]; - - file.LeafBrushesLump = lump; - } - - // LUMP_BRUSHES [18] - lumpEntry = header.Lumps[(int)LumpType.LUMP_BRUSHES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var brushes = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var brush = data.ReadType(); - if (brush != null) - brushes.Add(brush); - } - - var lump = new BrushesLump(); - lump.Brushes = [.. brushes]; - - file.BrushesLump = lump; - } - - // LUMP_BRUSHSIDES [19] - lumpEntry = header.Lumps[(int)LumpType.LUMP_BRUSHSIDES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var brushsides = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var brushside = data.ReadType(); - if (brushside != null) - brushsides.Add(brushside); - } - - var lump = new BrushsidesLump(); - lump.Brushsides = [.. brushsides]; - - file.BrushsidesLump = lump; - } - - // TODO: Support LUMP_AREAS [20] when in Models - // TODO: Support LUMP_AREAPORTALS [21] when in Models - // TODO: Support LUMP_PORTALS / LUMP_UNUSED0 / LUMP_PROPCOLLISION [22] when in Models - // TODO: Support LUMP_CLUSTERS / LUMP_UNUSED1 / LUMP_PROPHULLS [23] when in Models - // TODO: Support LUMP_PORTALVERTS / LUMP_UNUSED2 / LUMP_FAKEENTITIES / LUMP_PROPHULLVERTS [24] when in Models - // TODO: Support LUMP_CLUSTERPORTALS / LUMP_UNUSED3 / LUMP_PROPTRIS [25] when in Models - - // LUMP_DISPINFO [26] - lumpEntry = header.Lumps[(int)LumpType.LUMP_DISPINFO]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var dispInfos = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var dispInfo = data.ReadType(); - if (dispInfo != null) - dispInfos.Add(dispInfo); - } - - var lump = new DispInfosLump(); - lump.Infos = [.. dispInfos]; - - file.DispInfoLump = lump; - } - - // LUMP_ORIGINALFACES [27] - lumpEntry = header.Lumps[(int)LumpType.LUMP_ORIGINALFACES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var faces = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var face = data.ReadType(); - if (face != null) - faces.Add(face); - } - - var lump = new VbspFacesLump(); - lump.Faces = [.. faces]; - - file.OriginalFacesLump = lump; - } - - // TODO: Support LUMP_PHYSDISP [28] when in Models - // TODO: Support LUMP_PHYSCOLLIDE [29] when in Models - // TODO: Support LUMP_VERTNORMALS [30] when in Models - // TODO: Support LUMP_VERTNORMALINDICES [31] when in Models - // TODO: Support LUMP_DISP_LIGHTMAP_ALPHAS [32] when in Models - - // LUMP_DISP_VERTS [33] - lumpEntry = header.Lumps[(int)LumpType.LUMP_DISP_VERTS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var verts = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var vert = data.ReadType(); - if (vert != null) - verts.Add(vert); - } - - var lump = new DispVertsLump(); - lump.Verts = [.. verts]; - - file.DispVertLump = lump; - } - - // TODO: Support LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS [34] when in Models - - // LUMP_GAME_LUMP [35] - lumpEntry = header.Lumps[(int)LumpType.LUMP_GAME_LUMP]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lump = new GameLump(); - - lump.LumpCount = data.ReadInt32(); - lump.Directories = new GameLumpDirectory[lump.LumpCount]; - for (int i = 0; i < lump.LumpCount; i++) - { - var dir = data.ReadType(); - if (dir != null) - lump.Directories[i] = dir; - } - - file.GameLump = lump; - } - - // TODO: Support LUMP_LEAFWATERDATA [36] when in Models - // TODO: Support LUMP_PRIMITIVES [37] when in Models - // TODO: Support LUMP_PRIMVERTS [38] when in Models - // TODO: Support LUMP_PRIMINDICES [39] when in Models - // TODO: Support LUMP_PAKFILE [40] when in Models - // TODO: Support LUMP_CLIPPORTALVERTS [41] when in Models - - // LUMP_CUBEMAPS [42] - lumpEntry = header.Lumps[(int)LumpType.LUMP_CUBEMAPS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var cubemaps = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var cubemap = data.ReadType(); - if (cubemap != null) - cubemaps.Add(cubemap); - } - - var lump = new CubemapsLump(); - lump.Cubemaps = [.. cubemaps]; - - file.CubemapLump = lump; - } - - // LUMP_TEXDATA_STRING_DATA [43] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXDATA_STRING_DATA]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var strings = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var str = data.ReadNullTerminatedAnsiString(); - if (str != null) - strings.Add(str); - } - - var lump = new TexdataStringData(); - lump.Strings = [.. strings]; - - file.TexdataStringData = lump; - } - - // LUMP_TEXDATA_STRING_TABLE [44] - lumpEntry = header.Lumps[(int)LumpType.LUMP_TEXDATA_STRING_TABLE]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var offsets = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - offsets.Add(data.ReadInt32()); - } - - var lump = new TexdataStringTable(); - lump.Offsets = [.. offsets]; - - file.TexdataStringTable = lump; - } - - // LUMP_OVERLAYS [45] - lumpEntry = header.Lumps[(int)LumpType.LUMP_OVERLAYS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var overlays = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var overlay = data.ReadType(); - if (overlay != null) - overlays.Add(overlay); - } - - var lump = new OverlaysLump(); - lump.Overlays = [.. overlays]; - - file.OverlaysLump = lump; - } - - // TODO: Support LUMP_LEAFMINDISTTOWATER [46] when in Models - // TODO: Support LUMP_FACE_MACRO_TEXTURE_INFO [47] when in Models - - // LUMP_DISP_TRIS [48] - lumpEntry = header.Lumps[(int)LumpType.LUMP_DISP_TRIS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var tris = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var tri = data.ReadType(); - if (tri != null) - tris.Add(tri); - } - - var lump = new DispTrisLump(); - lump.Tris = [.. tris]; - - file.DispTrisLump = lump; - } - - // TODO: Support LUMP_PHYSCOLLIDESURFACE / LUMP_PROP_BLOB [49] when in Models - // TODO: Support LUMP_WATEROVERLAYS [50] when in Models - - // LUMP_LIGHTMAPPAGES / LUMP_LEAF_AMBIENT_INDEX_HDR [51] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LIGHTMAPPAGES]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var indicies = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var index = data.ReadType(); - if (index != null) - indicies.Add(index); - } - - var lump = new AmbientIndexLump(); - lump.Indicies = [.. indicies]; - - file.HDRAmbientIndexLump = lump; - } - - // LUMP_LIGHTMAPPAGEINFOS / LUMP_LEAF_AMBIENT_INDEX [52] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LIGHTMAPPAGEINFOS]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var indicies = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var index = data.ReadType(); - if (index != null) - indicies.Add(index); - } - - var lump = new AmbientIndexLump(); - lump.Indicies = [.. indicies]; - - file.LDRAmbientIndexLump = lump; - } - - // TODO: Support LUMP_LIGHTING_HDR [53] when in Models - - // LUMP_WORLDLIGHTS_HDR [54] - lumpEntry = header.Lumps[(int)LumpType.LUMP_WORLDLIGHTS_HDR]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var worldLights = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var worldLight = data.ReadType(); - if (worldLight != null) - worldLights.Add(worldLight); - } - - var lump = new WorldLightsLump(); - lump.WorldLights = [.. worldLights]; - - file.WorldLightsLump = lump; - } - - // LUMP_LEAF_AMBIENT_LIGHTING_HDR [55] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAF_AMBIENT_LIGHTING_HDR]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lightings = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var lighting = data.ReadType(); - if (lighting != null) - lightings.Add(lighting); - } - - var lump = new AmbientLightingLump(); - lump.Lightings = [.. lightings]; - - file.HDRAmbientLightingLump = lump; - } - - // LUMP_LEAF_AMBIENT_LIGHTING [56] - lumpEntry = header.Lumps[(int)LumpType.LUMP_LEAF_AMBIENT_LIGHTING]; - if (lumpEntry != null && lumpEntry.Offset > 0 && lumpEntry.Length > 0) - { - // Seek to the lump offset - data.Seek(lumpEntry.Offset, SeekOrigin.Begin); - - // Read the lump data - var lightings = new List(); - while (data.Position < lumpEntry.Offset + lumpEntry.Length) - { - var lighting = data.ReadType(); - if (lighting != null) - lightings.Add(lighting); - } - - var lump = new AmbientLightingLump(); - lump.Lightings = [.. lightings]; - - file.LDRAmbientLightingLump = lump; - } - - // TODO: Support LUMP_XZIPPAKFILE [57] when in Models - // TODO: Support LUMP_FACES_HDR [58] when in Models - // TODO: Support LUMP_MAP_FLAGS [59] when in Models - // TODO: Support LUMP_OVERLAY_FADES [60] when in Models - // TODO: Support LUMP_OVERLAY_SYSTEM_LEVELS [61] when in Models - // TODO: Support LUMP_PHYSLEVEL [62] when in Models - // TODO: Support LUMP_DISP_MULTIBLEND [63] when in Models - #endregion return file; @@ -860,5 +273,585 @@ namespace SabreTools.Serialization.Deserializers return header; } + + /// + /// 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('}'); + Array.ForEach(lumpSections, s => s.Trim('{', '}')); + + // 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]; + string[] lines = section.Split('\n'); + Array.ForEach(lines, l => l.Trim()); + + // 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 != 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 != 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) + { + vertices.Add(data.ReadType()); + } + + 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.ReadInt32(); + lump.ByteOffsets = new int[lump.NumClusters, 2]; + for (int i = 0; i < lump.NumClusters; i++) + { + for (int j = 0; j < 2; j++) + { + lump.ByteOffsets[i, j] = data.ReadInt32(); + } + } + + 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 != 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 != 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 != null) + faces.Add(face); + } + + return new VbspFacesLump { Faces = [.. faces] }; + } + + /// + /// 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, 3]; + + for (int i = 0; i < length / 3; i++) + { + for (int j = 0; j < 3; j++) + { + lump.Lightmap[i, j] = data.ReadByteValue(); + } + } + + 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.ReadInt32(); + lump.Data = new OccluderData[lump.Count]; + for (int i = 0; i < lump.Count; i++) + { + var occluderData = data.ReadType(); + if (occluderData != null) + lump.Data[i] = occluderData; + } + lump.PolyDataCount = data.ReadInt32(); + lump.PolyData = new OccluderPolyData[lump.Count]; + for (int i = 0; i < lump.Count; i++) + { + var polyData = data.ReadType(); + if (polyData != null) + lump.PolyData[i] = polyData; + } + lump.VertexIndexCount = data.ReadInt32(); + lump.VertexIndices = new int[lump.VertexIndexCount]; + for (int i = 0; i < lump.VertexIndexCount; i++) + { + lump.VertexIndices[i] = data.ReadInt32(); + } + + 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, int offset, int length) + { + var leaves = new List(); + while (data.Position < offset + length) + { + // TODO: Fix parsing between V0 and V1+ + var leaf = data.ReadType(); + if (leaf != null) + leaves.Add(leaf); + } + + return new VbspLeavesLump { Leaves = [.. leaves] }; + } + + /// + /// 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.ReadUInt16()); + } + + 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 != 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.ReadInt32()); + } + + 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 != 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 != 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.ReadUInt16()); + } + + 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.ReadUInt16()); + } + + 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 != 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 != 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 != 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 != 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.ReadInt32(); + lump.Directories = new GameLumpDirectory[lump.LumpCount]; + for (int i = 0; i < lump.LumpCount; i++) + { + var dir = data.ReadType(); + if (dir != 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 != null) + cubemaps.Add(cubemap); + } + + return new CubemapsLump { Cubemaps = [.. cubemaps] }; + } + + /// + /// 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 != 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.ReadInt32()); + } + + 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 != 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 != 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 != 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 != null) + lightings.Add(lighting); + } + + return new AmbientLightingLump { Lightings = [.. lightings] }; + } } } \ No newline at end of file diff --git a/SabreTools.Serialization/Deserializers/WAD3.cs b/SabreTools.Serialization/Deserializers/WAD3.cs index 6f3a4d73..a8181436 100644 --- a/SabreTools.Serialization/Deserializers/WAD3.cs +++ b/SabreTools.Serialization/Deserializers/WAD3.cs @@ -19,9 +19,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new Half-Life Texture Package to fill var file = new Models.WAD3.File(); diff --git a/SabreTools.Serialization/Deserializers/XZP.cs b/SabreTools.Serialization/Deserializers/XZP.cs index cf2aceaf..5c93c269 100644 --- a/SabreTools.Serialization/Deserializers/XZP.cs +++ b/SabreTools.Serialization/Deserializers/XZP.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Text; using SabreTools.IO.Extensions; using SabreTools.Models.XZP; using static SabreTools.Models.XZP.Constants; @@ -19,9 +18,6 @@ namespace SabreTools.Serialization.Deserializers if (data.Position < 0 || data.Position >= data.Length) return null; - // Cache the current offset - long initialOffset = data.Position; - // Create a new XBox Package File to fill var file = new Models.XZP.File(); @@ -43,11 +39,11 @@ namespace SabreTools.Serialization.Deserializers file.DirectoryEntries = new DirectoryEntry[header.DirectoryEntryCount]; // Try to parse the directory entries - for (int i = 0; i < header.DirectoryEntryCount; i++) + for (int i = 0; i < file.DirectoryEntries.Length; i++) { var directoryEntry = ParseDirectoryEntry(data); if (directoryEntry == null) - return null; + continue; file.DirectoryEntries[i] = directoryEntry; } @@ -62,11 +58,11 @@ namespace SabreTools.Serialization.Deserializers file.PreloadDirectoryEntries = new DirectoryEntry[header.PreloadDirectoryEntryCount]; // Try to parse the preload directory entries - for (int i = 0; i < header.PreloadDirectoryEntryCount; i++) + for (int i = 0; i < file.PreloadDirectoryEntries.Length; i++) { var directoryEntry = ParseDirectoryEntry(data); if (directoryEntry == null) - return null; + continue; file.PreloadDirectoryEntries[i] = directoryEntry; } @@ -82,11 +78,11 @@ namespace SabreTools.Serialization.Deserializers file.PreloadDirectoryMappings = new DirectoryMapping[header.PreloadDirectoryEntryCount]; // Try to parse the preload directory mappings - for (int i = 0; i < header.PreloadDirectoryEntryCount; i++) + for (int i = 0; i < file.PreloadDirectoryMappings.Length; i++) { var directoryMapping = ParseDirectoryMapping(data); if (directoryMapping == null) - return null; + continue; file.PreloadDirectoryMappings[i] = directoryMapping; } @@ -110,7 +106,7 @@ namespace SabreTools.Serialization.Deserializers file.DirectoryItems = new DirectoryItem[header.DirectoryItemCount]; // Try to parse the directory items - for (int i = 0; i < header.DirectoryItemCount; i++) + for (int i = 0; i < file.DirectoryItems.Length; i++) { var directoryItem = ParseDirectoryItem(data); file.DirectoryItems[i] = directoryItem; @@ -146,9 +142,7 @@ namespace SabreTools.Serialization.Deserializers { var header = data.ReadType
(); - if (header == null) - return null; - if (header.Signature != HeaderSignatureString) + if (header?.Signature != HeaderSignatureString) return null; if (header.Version != 6) return null; @@ -214,9 +208,7 @@ namespace SabreTools.Serialization.Deserializers { var footer = data.ReadType