/// <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); }
/// <summary> /// Factory method to parse a <c>byte</c> array into a <see cref="DisplacementVertices"/> object. /// </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="DisplacementVertices"/> object.</returns> /// <exception cref="ArgumentNullException"><paramref name="data"/> parameter was <c>null</c>.</exception> public static DisplacementVertices LumpFactory(byte[] data, BSP bsp, LumpInfo lumpInfo) { if (data == null) { throw new ArgumentNullException(); } return(new DisplacementVertices(data, GetStructLength(bsp.version, lumpInfo.version), bsp, lumpInfo)); }
/// <summary> /// Factory method for an <see cref="Entities"/> object from a <c>byte</c> array. /// </summary> /// <param name="data">The data to parse.</param> /// <param name="type">The map type.</param> /// <param name="version">The version of this lump.</param> /// <returns>An <see cref="Entities"/> object, which is a <c>List</c> of <see cref="Entity"/>s.</returns> public static Entities LumpFactory(byte[] data, BSP bsp, LumpInfo lumpInfo) { if (data == null) { throw new ArgumentNullException(); } return(new Entities(data, bsp, lumpInfo)); }
/// <summary> /// Factory method to parse a <c>byte</c> array into a <see cref="Lump{Leaf}"/>. /// </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{Leaf}"/>.</returns> /// <exception cref="ArgumentNullException"><paramref name="data"/> parameter was <c>null</c>.</exception> public static Lump <Leaf> LumpFactory(byte[] data, BSP bsp, LumpInfo lumpInfo) { if (data == null) { throw new ArgumentNullException(); } return(new Lump <Leaf>(data, GetStructLength(bsp.version, lumpInfo.version), bsp, lumpInfo)); }
/// <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(LumpInfo)) : 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(LumpInfo)) { if (data == null) { throw new ArgumentNullException(); } Bsp = bsp; LumpInfo = lumpInfo; this.data = data; this.type = type; }
/// <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> /// 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> /// 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"/> 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(LumpInfo)) : 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), new object[] { bytes, this })); } }
/// <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 = int.Parse(f1.Name.Substring(startIndex, f1EndIndex - startIndex)); int f2Position = int.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> /// Creates a new <see cref="Textures"/> that contains elements copied from the passed <see cref="IEnumerable{Texture}"/>. /// </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(LumpInfo)) : 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(LumpInfo)) : base(bsp, lumpInfo) { }
/// <summary> /// Creates a new <see cref="StaticProps"/> that contains elements copied from the passed <see cref="IEnumerable{StaticProp}"/> 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(LumpInfo)) : base(items, bsp, lumpInfo) { this.ModelDictionary = dictionary.ToArray(); }
/// <summary> /// Parses the passed <c>byte</c> array into a <see cref="DisplacementVertices"/> 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.</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" /> was <c>null</c>.</exception> public DisplacementVertices(byte[] data, int structLength, BSP bsp = null, LumpInfo lumpInfo = default(LumpInfo)) : base(data, structLength, bsp, lumpInfo) { }
/// <summary> /// Creates a new <see cref="DisplacementVertices"/> that contains elements copied from the passed <see cref="IEnumerable{DisplacementVertex}"/>. /// </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 DisplacementVertices(IEnumerable <DisplacementVertex> items, BSP bsp = null, LumpInfo lumpInfo = default(LumpInfo)) : base(items, bsp, 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(LumpInfo)) : 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(string.Format("Brace mismatch when parsing entities! Entity: {0} Brace level: {1}", Count, braceCount)); } }
/// <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(LumpInfo)) : base(items) { Bsp = bsp; LumpInfo = lumpInfo; }
/// <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(LumpInfo)) : 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(LumpInfo)) : base(initialCapacity, bsp, lumpInfo) { }
/// <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(LumpInfo)) : base(entities, bsp, lumpInfo) { }
/// <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(LumpInfo)) : base(capacity, bsp, lumpInfo) { }
/// <summary> /// Creates an empty <see cref="StaticProps"/> 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 StaticProps(BSP bsp = null, LumpInfo lumpInfo = default(LumpInfo)) : base(bsp, lumpInfo) { dictionary = new string[] { }; }
/// <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(LumpInfo)) : 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] == (byte)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 <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(LumpInfo)) { Bsp = bsp; LumpInfo = lumpInfo; }
/// <summary> /// Creates an empty <see cref="DisplacementVertices"/> 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 DisplacementVertices(BSP bsp = null, LumpInfo lumpInfo = default(LumpInfo)) : 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(LumpInfo)) { return(new NumList(data, type, bsp, lumpInfo)); }
/// <summary> /// Creates an empty <see cref="DisplacementVertices"/> 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 DisplacementVertices(int capacity, BSP bsp = null, LumpInfo lumpInfo = default(LumpInfo)) : base(capacity, 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(LumpInfo)) : base(capacity, bsp, lumpInfo) { ModelDictionary = new string[] { }; }
/// <summary> /// Tries to get the <see cref="MapType"/> member most closely represented by the referenced file. /// </summary> /// <param name="bigEndian">Set to <c>true</c> to attempt reading the data in big-endian byte order.</param> /// <returns>The <see cref="MapType"/> of this BSP, <see cref="MapType.Undefined"/> if it could not be determined.</returns> private MapType GetVersion(bool bigEndian) { MapType current = MapType.Undefined; using (FileStream stream = new FileStream(bspFile.FullName, FileMode.Open, FileAccess.Read)) { if (stream.Length < 4) { return(current); } BinaryReader binaryReader = new BinaryReader(stream); stream.Seek(0, SeekOrigin.Begin); int num = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(num); Array.Reverse(bytes); num = BitConverter.ToInt32(bytes, 0); } switch (num) { case 1347633737: { // 1347633737 reads in ASCII as "IBSP" // Versions: CoD, CoD2, CoD4, Quake 2, Daikatana, Quake 3 (RtCW), Soldier of Fortune int num2 = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(num2); Array.Reverse(bytes); num2 = BitConverter.ToInt32(bytes, 0); } switch (num2) { case 4: { current = MapType.CoD2; break; } case 22: { current = MapType.CoD4; break; } case 38: { current = MapType.Quake2; break; } case 41: { current = MapType.Daikatana; break; } case 46: { current = MapType.Quake3; // This version number is both Quake 3 and Soldier of Fortune. Find out the length of the // header, based on offsets. for (int i = 0; i < 17; i++) { stream.Seek((i + 1) * 8, SeekOrigin.Begin); int temp = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(temp); Array.Reverse(bytes); temp = BitConverter.ToInt32(bytes, 0); } if (temp == 184) { current = MapType.SoF; break; } else { if (temp == 144) { break; } } } break; } case 47: { current = MapType.Quake3; break; } case 59: { current = MapType.CoD; break; } } break; } case 892416050: case 1095516485: { // 892416050 reads in ASCII as "2015," the game studio which developed MoHAA // 1095516485 reads in ASCII as "EALA," the ones who developed MoHAA Spearhead and Breakthrough current = MapType.MOHAA; break; } case 1347633750: { // 1347633750 reads in ASCII as "VBSP." Indicates Source engine. // Some source games handle this as 2 shorts. // TODO: Big endian? // Formats: Source 17-23 and 27, DMoMaM, Vindictus int num2 = (int)binaryReader.ReadUInt16(); switch (num2) { case 17: { current = MapType.Source17; break; } case 18: { current = MapType.Source18; break; } case 19: { current = MapType.Source19; break; } case 20: { int version2 = (int)binaryReader.ReadUInt16(); if (version2 == 4) { // TODO: This doesn't necessarily mean the whole map should be read as DMoMaM. current = MapType.DMoMaM; } else { current = MapType.Source20; // Hack for detecting Vindictus: Look in the GameLump for offset/length/flags outside of ranges we'd expect LumpInfo gameLumpInfo = GetLumpInfo(35, MapType.Source20); stream.Seek(gameLumpInfo.offset, SeekOrigin.Begin); int numGameLumps = binaryReader.ReadInt32(); if (numGameLumps > 0) { // Normally this would be the offset and length for the first game lump. // But in Vindictus it's the version indicator for it instead. stream.Seek(gameLumpInfo.offset + 12, SeekOrigin.Begin); int testOffset = binaryReader.ReadInt32(); if (numGameLumps > 1) { if (testOffset < 24) { current = MapType.Vindictus; break; } } else { // Normally this would be the ident for the second game lump. // But in Vindictus it's the length for the first instead. stream.Seek(gameLumpInfo.offset + 20, SeekOrigin.Begin); int testName = binaryReader.ReadInt32(); // A lump ident tends to have a value far above 1090519040, longer than any GameLump should be. if (testOffset < 24 && testName < gameLumpInfo.length) { current = MapType.Vindictus; break; } } } } break; } case 21: { current = MapType.Source21; // Hack to determine if this is a L4D2 map. Read what would normally be the offset of // a lump. If it is less than the header length it's probably not an offset, indicating L4D2. stream.Seek(8, SeekOrigin.Begin); int test = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(test); Array.Reverse(bytes); test = BitConverter.ToInt32(bytes, 0); } if (test < 1032) { current = MapType.L4D2; } break; } case 22: { current = MapType.Source22; break; } case 23: { current = MapType.Source23; break; } case 27: { current = MapType.Source27; break; } } break; } case 1347633746: { // Reads in ASCII as "RBSP". Raven software's modification of Q3BSP, or Ritual's modification of Q2. // Formats: Raven, SiN current = MapType.Raven; for (int i = 0; i < 17; i++) { // Find out where the first lump starts, based on offsets. stream.Seek((i + 1) * 8, SeekOrigin.Begin); int temp = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(temp); Array.Reverse(bytes); temp = BitConverter.ToInt32(bytes, 0); } if (temp == 168) { current = MapType.SiN; break; } else { if (temp == 152) { break; } } } break; } case 556942917: { // "EF2!" current = MapType.STEF2; break; } case 1347633778: { // "rBSP". Respawn's format for Titanfall current = MapType.Titanfall; break; } case 1263223110: { // "FAKK" // Formats: STEF2 demo, Heavy Metal FAKK2 (American McGee's Alice) int num2 = binaryReader.ReadInt32(); if (bigEndian) { byte[] bytes = BitConverter.GetBytes(num2); Array.Reverse(bytes); num2 = BitConverter.ToInt32(bytes, 0); } switch (num2) { case 19: { current = MapType.STEF2Demo; break; } case 12: case 42: { // American McGee's Alice current = MapType.FAKK; break; } } break; } // Various numbers not representing a string // Formats: HL1, Quake, Nightfire, or perhaps Tactical Intervention's encrypted format case 29: case 30: { current = MapType.Quake; break; } case 42: { current = MapType.Nightfire; break; } default: { // Hack to get Tactical Intervention's encryption key. At offset 384, there are two unused lumps whose // values in the header are always 0s. Grab these 32 bytes (256 bits) and see if they match an expected value. stream.Seek(384, SeekOrigin.Begin); key = binaryReader.ReadBytes(32); stream.Seek(0, SeekOrigin.Begin); int num2 = BitConverter.ToInt32(XorWithKeyStartingAtIndex(binaryReader.ReadBytes(4)), 0); if (num2 == 1347633750) { current = MapType.TacticalInterventionEncrypted; } else { current = MapType.Undefined; } break; } } binaryReader.Close(); } return(current); }
/// <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(LumpInfo)) : base(capacity) { Bsp = bsp; LumpInfo = lumpInfo; }