private void ReadHeaders(BinaryReader reader) { // Seek to beginning file.Seek(0, SeekOrigin.Begin); // Check GRP header if (ENCODING.GetString(reader.ReadBytes(GRP_ID.Length)) != GRP_ID) { throw new IOException("Invalid GRP file header."); } // Number of lumps int numlumps = reader.ReadInt32(); if (numlumps < 0) { throw new IOException("Invalid number of lumps in GRP file."); } // Dispose old lumps and create new list if (lumps != null) { foreach (Lump l in lumps) { l.Dispose(); } } lumps = new List <Lump>(numlumps); // Go for all lumps List <LumpInfo> lumpinfos = new List <LumpInfo>(numlumps); for (int i = 0; i < numlumps; i++) { // Read lump information string name = ENCODING.GetString(reader.ReadBytes(GRP_FILENAME_LENGTH)).TrimEnd('\0'); int length = reader.ReadInt32(); // Create lump info lumpinfos.Add(new LumpInfo(name, length)); } // Create lumps int offset = (int)reader.BaseStream.Position; for (int i = 0; i < numlumps; i++) { LumpInfo info = lumpinfos[i]; Lump l = new Lump(file, info.Name, offset, info.Length); lumps.Add(l); offset += info.Length; // Also collect ???.CON files if (l.Name.EndsWith(".CON")) { confiles.Add(i, l.Name); } } }
/// <summary> /// Parses the passed <c>byte</c> array into a <see cref="StaticProps"/> object. /// </summary> /// <param name="data">Array of <c>byte</c>s to parse.</param> /// <param name="structLength">Number of <c>byte</c>s to copy into the children. Will be recalculated based on BSP format.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> /// <exception cref="ArgumentNullException"><paramref name="data"/> or <paramref name="bsp"/> was <c>null</c>.</exception> public StaticProps(byte[] data, int structLength, Bsp bsp, LumpInfo lumpInfo = default) : base(bsp, lumpInfo) { if (data == null || bsp == null) { throw new ArgumentNullException(); } if (data.Length > 0) { int offset = 0; ModelDictionary = new string[BitConverter.ToInt32(data, 0)]; offset += 4; for (int i = 0; i < ModelDictionary.Length; ++i) { ModelDictionary[i] = data.ToNullTerminatedString(offset, 128); offset += 128; } LeafIndices = new short[BitConverter.ToInt32(data, offset)]; offset += 4; for (int i = 0; i < LeafIndices.Length; ++i) { LeafIndices[i] = BitConverter.ToInt16(data, offset); offset += 2; } if (Bsp.Version == MapType.Vindictus && lumpInfo.version == 6) { int numPropScales = BitConverter.ToInt32(data, offset); offset += 4 + (numPropScales * 16); } int numProps = BitConverter.ToInt32(data, offset); if (lumpInfo.version == 12) // So far only Titanfall { offset += 12; } else { offset += 4; } if (numProps > 0) { structLength = (data.Length - offset) / numProps; for (int i = 0; i < numProps; ++i) { byte[] bytes = new byte[structLength]; Array.Copy(data, offset, bytes, 0, structLength); Add(new StaticProp(bytes, this)); offset += structLength; } } } else { ModelDictionary = new string[0]; } }
/// <summary> /// Creates a new <see cref="NumList"/> object from a <c>byte</c> array. /// </summary> /// <param name="data"><c>byte</c> array to parse.</param> /// <param name="type">The type of number to store.</param> /// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception> public NumList(byte[] data, DataType type, Bsp bsp = null, LumpInfo lumpInfo = default) { if (data == null) { throw new ArgumentNullException(); } Bsp = bsp; LumpInfo = lumpInfo; this.data = data; Type = type; }
private void AddFile(string fileName) { names.Add(Path.GetFileNameWithoutExtension(fileName).ToLower()); var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read); streams.Add(stream); string identification; int lumpCount; int lumpInfoTableOffset; { var data = new byte[12]; if (stream.Read(data, 0, data.Length) != data.Length) { throw new Exception("Failed to read the WAD file."); } identification = DoomInterop.ToString(data, 0, 4); lumpCount = BitConverter.ToInt32(data, 4); lumpInfoTableOffset = BitConverter.ToInt32(data, 8); if (identification != "IWAD" && identification != "PWAD") { throw new Exception("The file is not a WAD file."); } } { var data = new byte[LumpInfo.DataSize * lumpCount]; stream.Seek(lumpInfoTableOffset, SeekOrigin.Begin); if (stream.Read(data, 0, data.Length) != data.Length) { throw new Exception("Failed to read the WAD file."); } for (var i = 0; i < lumpCount; i++) { var offset = LumpInfo.DataSize * i; var lumpInfo = new LumpInfo( DoomInterop.ToString(data, offset + 8, 8), stream, BitConverter.ToInt32(data, offset), BitConverter.ToInt32(data, offset + 4)); lumpInfos.Add(lumpInfo); } } }
/// <summary> /// Factory method to parse a <c>byte</c> array into a <see cref="Lump{Vertex}"/>. /// </summary> /// <param name="data">The data to parse.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> /// <returns>A <see cref="Lump{Vertex}"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception> /// <remarks>This function goes here since it can't be in Unity's <c>UIVertex</c> class, and so I can't /// depend on having a constructor taking a byte array.</remarks> public static Lump <Vertex> LumpFactory(byte[] data, Bsp bsp, LumpInfo lumpInfo) { if (data == null) { throw new ArgumentNullException(); } int structLength = GetStructLength(bsp.Version, lumpInfo.version); int numObjects = data.Length / structLength; Lump <Vertex> lump = new Lump <Vertex>(numObjects, bsp, lumpInfo); byte[] bytes = new byte[structLength]; for (int i = 0; i < numObjects; ++i) { Array.Copy(data, i * structLength, bytes, 0, structLength); lump.Add(CreateVertex(bytes, bsp.Version, lumpInfo.version)); } return(lump); }
/// <summary> /// Factory method to parse a <c>byte</c> array into a <see cref="Lump{Plane}"/>. /// </summary> /// <param name="data">The data to parse.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> /// <returns>A <see cref="Lump{Plane}"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="data" /> was null.</exception> /// <remarks>This function goes here since it can't go into Unity's Plane class, and so can't depend /// on having a constructor taking a byte array.</remarks> public static Lump <Plane> LumpFactory(byte[] data, Bsp bsp, LumpInfo lumpInfo) { if (data == null) { throw new ArgumentNullException(); } int structLength = GetStructLength(bsp.Version, lumpInfo.version); int numObjects = data.Length / structLength; Lump <Plane> lump = new Lump <Plane>(numObjects, bsp, lumpInfo); for (int i = 0; i < numObjects; ++i) { Vector3 normal = new Vector3(BitConverter.ToSingle(data, structLength * i), BitConverter.ToSingle(data, (structLength * i) + 4), BitConverter.ToSingle(data, (structLength * i) + 8)); float distance = BitConverter.ToSingle(data, (structLength * i) + 12); lump.Add(new Plane(normal, distance)); } return(lump); }
/// <summary> /// Parses the passed <c>byte</c> array into a <c>Lump</c> of <typeparamref name="T"/> objects. /// </summary> /// <param name="data">Array of <c>byte</c>s to parse.</param> /// <param name="structLength">Number of <c>byte</c>s to copy into the elements. Negative values indicate a variable length, which is not supported by this constructor.</param> /// <param name="bsp">The <see cref="BSP.Bsp"/> which <paramref name="data"/> came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> object for this <c>Lump</c>.</param> /// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception> /// <exception cref="NotSupportedException"><paramref name="structLength"/> is negative.</exception> public Lump(byte[] data, int structLength, Bsp bsp = null, LumpInfo lumpInfo = default) : base(data.Length / structLength) { if (data == null) { throw new ArgumentNullException(); } if (structLength <= 0) { throw new NotSupportedException("Cannot use the base Lump constructor for variable length lumps (structLength was negative). Create a derived class with a new constructor instead."); } Bsp = bsp; LumpInfo = lumpInfo; for (int i = 0; i < data.Length / structLength; ++i) { byte[] bytes = new byte[structLength]; Array.Copy(data, (i * structLength), bytes, 0, structLength); Add((T)Activator.CreateInstance(typeof(T), bytes, this)); } }
/// <summary> /// Creates an empty <c>Lump</c> of <typeparamref name="T"/> objects with the specified initial capactiy. /// </summary> /// <param name="capacity">The number of elements that can initially be stored.</param> /// <param name="bsp">The <see cref="BSP"/> which <paramref name="data"/> came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> object for this <c>Lump</c>.</param> public Lump(int capacity, Bsp bsp = null, LumpInfo lumpInfo = default) : base(capacity) { Bsp = bsp; LumpInfo = lumpInfo; }
/// <summary> /// Creates a new <c>Lump</c> that contains elements copied from the passed <see cref="IEnumerable{T}"/>. /// </summary> /// <param name="items">The elements to copy into this <c>Lump</c>.</param> /// <param name="bsp">The <see cref="BSP"/> which <paramref name="data"/> came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> object for this <c>Lump</c>.</param> public Lump(IEnumerable <T> items, Bsp bsp = null, LumpInfo lumpInfo = default) : base(items) { Bsp = bsp; LumpInfo = lumpInfo; }
/// <summary> /// Creates an empty <c>Lump</c> of <typeparamref name="T"/> objects. /// </summary> /// <param name="bsp">The <see cref="BSP"/> which <paramref name="data"/> came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> object for this <c>Lump</c>.</param> public Lump(Bsp bsp = null, LumpInfo lumpInfo = default) { Bsp = bsp; LumpInfo = lumpInfo; }
/// <summary> /// Initializes a new <see cref="Entities"/> object, and parses the passed <c>byte</c> array as a <c>string</c>. /// </summary> /// <param name="data"><c>Byte</c>s read from a file.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Entities(byte[] data, Bsp bsp = null, LumpInfo lumpInfo = default) : base(bsp, lumpInfo) { // Keep track of whether or not we're currently in a set of quotation marks. // I came across a map where the map maker used { and } within a value. bool inQuotes = false; int braceCount = 0; // The current character being read in the file. This is necessary because // we need to know exactly when the { and } characters occur and capture // all text between them. char currentChar; // This will be the resulting entity, fed into Entity.FromString StringBuilder current = new StringBuilder(); for (int offset = 0; offset < data.Length; ++offset) { currentChar = (char)data[offset]; if (currentChar == '\"') { if (offset == 0) { inQuotes = !inQuotes; } else if ((char)data[offset - 1] != '\\') { // Allow for escape-sequenced quotes to not affect the state machine, but only if the quote isn't at the end of a line. // Some Source engine entities use escape sequence quotes in values, but MoHAA has a map with an obvious erroneous backslash before a quote at the end of a line. if (inQuotes && (offset + 1 >= data.Length || (char)data[offset + 1] == '\n' || (char)data[offset + 1] == '\r')) { inQuotes = false; } } else { inQuotes = !inQuotes; } } if (!inQuotes) { if (currentChar == '{') { // Occasionally, texture paths have been known to contain { or }. Since these aren't always contained // in quotes, we must be a little more precise about how we want to select our delimiters. // As a general rule, though, making sure we're not in quotes is still very effective at error prevention. if (offset == 0 || (char)data[offset - 1] == '\n' || (char)data[offset - 1] == '\t' || (char)data[offset - 1] == ' ' || (char)data[offset - 1] == '\r') { ++braceCount; } } } if (braceCount > 0) { current.Append(currentChar); } if (!inQuotes) { if (currentChar == '}') { if (offset == 0 || (char)data[offset - 1] == '\n' || (char)data[offset - 1] == '\t' || (char)data[offset - 1] == ' ' || (char)data[offset - 1] == '\r') { --braceCount; if (braceCount == 0) { Entity entity = new Entity(this); entity.ParseString(current.ToString()); Add(entity); // Reset StringBuilder current.Length = 0; } } } } } if (braceCount != 0) { throw new ArgumentException( $"Brace mismatch when parsing entities! Entity: {Count} Brace level: {braceCount}"); } }
/// <summary> /// Initializes a new <see cref="Entities"/> object. /// </summary> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Entities(Bsp bsp = null, LumpInfo lumpInfo = default) : base(bsp, lumpInfo) { }
/// <summary> /// Creates a new <see cref="NumList"/> object from a <c>byte</c> array and returns it. /// </summary> /// <param name="data"><c>byte</c> array to parse.</param> /// <param name="type">The type of number to store.</param> /// <returns>The resulting <see cref="NumList"/>.</returns> public static NumList LumpFactory(byte[] data, DataType type, Bsp bsp = null, LumpInfo lumpInfo = default) { return(new NumList(data, type, bsp, lumpInfo)); }
/// <summary> /// Creates an empty <see cref="StaticProps"/> object with the specified initial capactiy. /// </summary> /// <param name="capacity">The number of elements that can initially be stored.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public StaticProps(int capacity, Bsp bsp = null, LumpInfo lumpInfo = default) : base(capacity, bsp, lumpInfo) { ModelDictionary = new string[] { }; }
/// <summary> /// Loads any lump files associated with the BSP. /// </summary> private void LoadLumpFiles() { lumpFiles = new Dictionary<int, LumpInfo>(); // Scan the BSP's directory for lump files DirectoryInfo dir = bspFile.Directory; List<FileInfo> files = dir.GetFiles(bspFile.Name.Substring(0, bspFile.Name.Length - 4) + "_?_*.lmp").ToList(); // Sort the list by the number on the file files.Sort((f1, f2) => { int startIndex = bspFile.Name.Length - 1; int f1EndIndex = f1.Name.LastIndexOf('.'); int f2EndIndex = f2.Name.LastIndexOf('.'); int f1Position = Int32.Parse(f1.Name.Substring(startIndex, f1EndIndex - startIndex)); int f2Position = Int32.Parse(f2.Name.Substring(startIndex, f2EndIndex - startIndex)); return f1Position - f2Position; }); // Read the files in order. The last file in the list for a specific lump will replace that lump. foreach (FileInfo file in files) { using (FileStream fs = new FileStream(file.FullName, FileMode.Open, FileAccess.Read)) { BinaryReader br = new BinaryReader(fs); fs.Seek(0, SeekOrigin.Begin); byte[] input = br.ReadBytes(20); int offset = BitConverter.ToInt32(input, 0); int lumpIndex = BitConverter.ToInt32(input, 4); int version = BitConverter.ToInt32(input, 8); int length = BitConverter.ToInt32(input, 12); lumpFiles[lumpIndex] = new LumpInfo() { offset = offset, version = version, length = length, lumpFile = file }; br.Close(); } } }
/// <summary> /// Parses the passed <c>byte</c> array into a <see cref="Textures"/> object. /// </summary> /// <param name="data">Array of <c>byte</c>s to parse.</param> /// <param name="structLength">Number of <c>byte</c>s to copy into the children. Will be recalculated based on BSP format.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> /// <exception cref="ArgumentNullException"><paramref name="data"/> or <paramref name="bsp"/> was <c>null</c>.</exception> public Textures(byte[] data, int structLength, Bsp bsp, LumpInfo lumpInfo = default) : base(bsp, lumpInfo) { if (data == null || bsp == null) { throw new ArgumentNullException(); } switch (bsp.Version) { case MapType.Nightfire: { structLength = 64; break; } case MapType.Quake3: case MapType.Raven: case MapType.CoD: case MapType.CoD2: case MapType.CoD4: { structLength = 72; break; } case MapType.Quake2: case MapType.Daikatana: case MapType.SoF: case MapType.Stef2: case MapType.Stef2Demo: case MapType.Fakk: { structLength = 76; break; } case MapType.Mohaa: { structLength = 140; break; } case MapType.SiN: { structLength = 180; break; } case MapType.Source17: case MapType.Source18: case MapType.Source19: case MapType.Source20: case MapType.Source21: case MapType.Source22: case MapType.Source23: case MapType.Source27: case MapType.L4D2: case MapType.TacticalInterventionEncrypted: case MapType.Vindictus: case MapType.DMoMaM: { int offset = 0; for (int i = 0; i < data.Length; ++i) { if (data[i] == 0x00) { // They are null-terminated strings, of non-constant length (not padded) byte[] myBytes = new byte[i - offset]; Array.Copy(data, offset, myBytes, 0, i - offset); Add(new Texture(myBytes, this)); offset = i + 1; } } return; } case MapType.Quake: { int numElements = BitConverter.ToInt32(data, 0); structLength = 40; for (int i = 0; i < numElements; ++i) { byte[] myBytes = new byte[structLength]; Array.Copy(data, BitConverter.ToInt32(data, (i + 1) * 4), myBytes, 0, structLength); Add(new Texture(myBytes, this)); } return; } default: { throw new ArgumentException("Lump object Texture does not exist in map type " + bsp.Version + " or has not been implemented."); } } int numObjects = data.Length / structLength; for (int i = 0; i < numObjects; ++i) { byte[] bytes = new byte[structLength]; Array.Copy(data, (i * structLength), bytes, 0, structLength); Add(new Texture(bytes, this)); } }
/// <summary> /// Creates an empty <see cref="Textures"/> object with the specified initial capactiy. /// </summary> /// <param name="capacity">The number of elements that can initially be stored.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Textures(int capacity, Bsp bsp = null, LumpInfo lumpInfo = default) : base(capacity, bsp, lumpInfo) { }
/// <summary> /// Creates a new <see cref="Textures"/> that contains elements copied from the passed <see cref="IEnumerable{T}"/>. /// </summary> /// <param name="items">The elements to copy into this <c>Lump</c>.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Textures(IEnumerable <Texture> items, Bsp bsp = null, LumpInfo lumpInfo = default) : base(items, bsp, lumpInfo) { }
/// <summary> /// Creates an empty <see cref="Textures"/> object. /// </summary> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Textures(Bsp bsp = null, LumpInfo lumpInfo = default) : base(bsp, lumpInfo) { }
/// <summary> /// Initializes a new instance of an <see cref="Entities"/> object with a specified initial capacity. /// </summary> /// <param name="initialCapacity">Initial capacity of the <c>List</c> of <see cref="Entity"/> objects.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Entities(int initialCapacity, Bsp bsp = null, LumpInfo lumpInfo = default) : base(initialCapacity, bsp, lumpInfo) { }
/// <summary> /// Reads the lump in the BSP file using the information in "<paramref name="info"/>". /// </summary> /// <param name="info">The <see cref="LumpInfo"/> object representing the lump's information.</param> /// <returns>A <c>byte</c> array containing the data from the file for the lump at the offset with the length from "<paramref name="info"/>".</returns> public byte[] ReadLump(LumpInfo info) { if (info.length == 0) { return new byte[0]; } byte[] output; if (info.lumpFile != null) { using (FileStream fs = new FileStream(info.lumpFile.FullName, FileMode.Open, FileAccess.Read)) { BinaryReader br = new BinaryReader(fs); fs.Seek(info.offset, SeekOrigin.Begin); output = br.ReadBytes(info.length); br.Close(); return output; } } using (FileStream stream = new FileStream(bspFile.FullName, FileMode.Open, FileAccess.Read)) { BinaryReader binaryReader = new BinaryReader(stream); stream.Seek(info.offset, SeekOrigin.Begin); output = binaryReader.ReadBytes(info.length); binaryReader.Close(); } if (key.Length != 0) { output = XorWithKeyStartingAtIndex(output, info.offset); } return output; }
public static void WriteTo(this LumpInfo metaData, Stream stream) { stream.WriteInt(metaData.Position); stream.WriteInt(metaData.Size); stream.WriteText(metaData.Name.ToString(), totalLength: LumpName.MaxLength); }
/// <summary> /// Initializes a new instance of an <see cref="Entities"/> object copying a passed <c>IEnumerable</c> of <see cref="Entity"/> objects. /// </summary> /// <param name="data">Collection of <see cref="Entity"/> objects to copy.</param> /// <param name="bsp">The <see cref="BSP"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public Entities(IEnumerable <Entity> entities, Bsp bsp = null, LumpInfo lumpInfo = default) : base(entities, bsp, lumpInfo) { }
/// <summary> /// Creates a new <see cref="StaticProps"/> that contains elements copied from the passed <see cref="IEnumerable{T}"/> and the passed <paramref name="dictionary"/>. /// </summary> /// <param name="items">The elements to copy into this <c>Lump</c>.</param> /// <param name="dictionary">A dictionary of static prop models. This is referenced from <see cref="StaticProp"/> objects.</param> /// <param name="bsp">The <see cref="Bsp"/> this lump came from.</param> /// <param name="lumpInfo">The <see cref="LumpInfo"/> associated with this lump.</param> public StaticProps(IEnumerable <StaticProp> items, IList <string> dictionary, Bsp bsp = null, LumpInfo lumpInfo = default) : base(items, bsp, lumpInfo) { ModelDictionary = dictionary.ToArray(); }