#if UNITY_2_6 || UNITY_2_6_1 || UNITY_3_0 || UNITY_3_0_0 || UNITY_3_1 || UNITY_3_2 || UNITY_3_3 || UNITY_3_4 || UNITY_3_5 || UNITY_4_0 || UNITY_4_0_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 || UNITY_5 || UNITY_5_3_OR_NEWER #define UNITY #if !UNITY_5_6_OR_NEWER #define OLDUNITY #endif #endif using System; using System.IO; using System.Collections.Generic; using System.Reflection; #if UNITY using UnityEngine; #endif namespace LibBSP { #if UNITY && !OLDUNITY using Vertex = UIVertex; #endif /// /// Enum of the known different map formats. /// public enum MapType : int { Undefined = 0, Quake = 29, // TYPE_GOLDSRC = 30, // Uses mostly the same structures as Quake Nightfire = 42, Vindictus = 346131372, STEF2 = 556942937, MOHAA = 892416069, // TYPE_MOHBT = 1095516506, // Similar enough to MOHAA to use the same structures STEF2Demo = 1263223129, FAKK = 1263223152, TacticalInterventionEncrypted = 1268885814, CoD2 = 1347633741, SiN = 1347633747, // The headers for SiN and Jedi Outcast are exactly the same Raven = 1347633748, CoD4 = 1347633759, Source17 = 1347633767, Source18 = 1347633768, Source19 = 1347633769, Source20 = 1347633770, Source21 = 1347633771, Source22 = 1347633772, Source23 = 1347633773, L4D2 = 1347633774, Quake2 = 1347633775, Source27 = 1347633777, Daikatana = 1347633778, SoF = 1347633782, // Uses the same header as Q3. Quake3 = 1347633783, // TYPE_RTCW = 1347633784, // Uses same structures as Quake 3 CoD = 1347633796, DMoMaM = 1347895914, } /// /// Class containing basic information for a lump in a BSP file. /// public class LumpInfo { public int ident; public int flags; public int version; public int offset; public int length; public FileInfo lumpFile; } /// /// Holds data for any and all supported BSP formats. Any unused lumps in a given format /// will be left as null. /// public class BSP : Dictionary { private MapType _version; // Map structures // Quake 1/GoldSrc private Entities _entities; private List _planes; private Textures _textures; private List _vertices; private List _nodes; private List _texInfo; private List _faces; private List _leaves; private NumList _markSurfaces; private List _edges; private NumList _surfEdges; private List _models; // public byte[] pvs; // Quake 2 private List _brushes; private List _brushSides; private NumList _markBrushes; // MoHAA private List _lodTerrains; private List _staticModels; // CoD private List _patches; private List _patchVerts; private NumList _patchIndices; // Nightfire private Textures _materials; private NumList _indices; // Source private List _originalFaces; private NumList _texTable; private List _texDatas; private List _dispInfos; private DisplacementVertices _dispVerts; private NumList _displacementTriangles; // public SourceOverlays overlays; private List _cubemaps; private GameLump _gameLump; private StaticProps _staticProps; /// /// The object in use by this class. /// public BSPReader reader { get; private set; } /// /// The version of this BSP. Do not change this unless you want to force reading a BSP as a certain format. /// public MapType version { get { if (_version == MapType.Undefined) { _version = reader.GetVersion(); } return _version; } set { _version = value; } } /// /// Is the BSP file in big endian format? /// public bool bigEndian { get { return reader.bigEndian; } } /// /// The object in the BSP file, if available. /// public Entities entities { get { if (_entities == null) { int index = Entity.GetIndexForLump(version); if (index >= 0) { _entities = Entity.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _entities; } } /// /// A List of objects in the BSP file, if available. /// public List planes { get { if (_planes == null) { int index = PlaneExtensions.GetIndexForLump(version); if (index >= 0) { _planes = PlaneExtensions.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _planes; } } /// /// The object in the BSP file, if available. /// public Textures textures { get { if (_textures == null) { int index = Texture.GetIndexForLump(version); if (index >= 0) { _textures = Texture.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _textures; } } /// /// A List of objects in the BSP file representing the vertices of the BSP, if available. /// public List vertices { get { if (_vertices == null) { int index = VertexExtensions.GetIndexForLump(version); if (index >= 0) { _vertices = VertexExtensions.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _vertices; } } /// /// A List of objects in the BSP file, if available. /// public List nodes { get { if (_nodes == null) { int index = Node.GetIndexForLump(version); if (index >= 0) { _nodes = Node.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _nodes; } } /// /// A List of objects in the BSP file, if available. /// public List texInfo { get { if (_texInfo == null) { int index = TextureInfo.GetIndexForLump(version); if (index >= 0) { _texInfo = TextureInfo.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _texInfo; } } /// /// A List of objects in the BSP file, if available. /// public List faces { get { if (_faces == null) { int index = Face.GetIndexForLump(version); if (index >= 0) { _faces = Face.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _faces; } } /// /// A List of objects in the BSP file, if available. /// public List leaves { get { if (_leaves == null) { int index = Leaf.GetIndexForLump(version); if (index >= 0) { _leaves = Leaf.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _leaves; } } /// /// A List of objects in the BSP file, if available. /// public List edges { get { if (_edges == null) { int index = Edge.GetIndexForLump(version); if (index >= 0) { _edges = Edge.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _edges; } } /// /// A List of objects in the BSP file, if available. /// public List models { get { if (_models == null) { int index = Model.GetIndexForLump(version); if (index >= 0) { _models = Model.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _models; } } /// /// A List of objects in the BSP file, if available. /// public List brushes { get { if (_brushes == null) { int index = Brush.GetIndexForLump(version); if (index >= 0) { _brushes = Brush.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _brushes; } } /// /// A List of objects in the BSP file, if available. /// public List brushSides { get { if (_brushSides == null) { int index = BrushSide.GetIndexForLump(version); if (index >= 0) { _brushSides = BrushSide.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _brushSides; } } /// /// A List of objects in the BSP file representing Materials (shaders), if available. /// public Textures materials { get { if (_materials == null) { int index = Texture.GetIndexForMaterialLump(version); if (index >= 0) { _materials = Texture.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _materials; } } /// /// A List of objects in the BSP file representing the Original Faces, if available. /// public List originalFaces { get { if (_originalFaces == null) { int index = Face.GetIndexForOriginalFacesLump(version); if (index >= 0) { _originalFaces = Face.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _originalFaces; } } /// /// A List of objects in the BSP file, if available. /// public List texDatas { get { if (_texDatas == null) { int index = TextureData.GetIndexForLump(version); if (index >= 0) { _texDatas = TextureData.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _texDatas; } } /// /// A List of objects in the BSP file, if available. /// public List dispInfos { get { if (_dispInfos == null) { int index = DisplacementInfo.GetIndexForLump(version); if (index >= 0) { _dispInfos = DisplacementInfo.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _dispInfos; } } /// /// The object in the BSP file, if available. /// public DisplacementVertices dispVerts { get { if (_dispVerts == null) { int index = DisplacementVertex.GetIndexForLump(version); if (index >= 0) { _dispVerts = DisplacementVertex.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _dispVerts; } } /// /// A List of objects in the BSP file, if available. /// public List cubemaps { get { if (_cubemaps == null) { int index = Cubemap.GetIndexForLump(version); if (index >= 0) { _cubemaps = Cubemap.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _cubemaps; } } /// /// A object containing the Mark Surfaces (Leaf Surfaces) lump, if available. /// public NumList markSurfaces { get { if (_markSurfaces == null) { NumList.DataType type; int index = NumList.GetIndexForMarkSurfacesLump(version, out type); if (index >= 0) { _markSurfaces = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _markSurfaces; } } /// /// A object containing the Surface Edges lump, if available. /// public NumList surfEdges { get { if (_surfEdges == null) { NumList.DataType type; int index = NumList.GetIndexForSurfEdgesLump(version, out type); if (index >= 0) { _surfEdges = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _surfEdges; } } /// /// A object containing the Mark Brushes (Leaf Brushes) lump, if available. /// public NumList markBrushes { get { if (_markBrushes == null) { NumList.DataType type; int index = NumList.GetIndexForMarkBrushesLump(version, out type); if (index >= 0) { _markBrushes = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _markBrushes; } } /// /// A List of objects in the BSP file, if available. /// public List staticModels { get { if (_staticModels == null) { int index = StaticModel.GetIndexForLump(version); if (index >= 0) { _staticModels = StaticModel.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _staticModels; } } /// /// A List of objects in the BSP file, if available. /// public List lodTerrains { get { if (_lodTerrains == null) { int index = LODTerrain.GetIndexForLump(version); if (index >= 0) { _lodTerrains = LODTerrain.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _lodTerrains; } } /// /// A List of objects in the BSP file, if available. /// public List patches { get { if (_patches == null) { int index = Patch.GetIndexForLump(version); if (index >= 0) { _patches = Patch.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _patches; } } /// /// A List of objects in the BSP file representing the patch vertices of the BSP, if available. /// public List patchVerts { get { if (_patchVerts == null) { int index = VertexExtensions.GetIndexForPatchVertsLump(version); if (index >= 0) { _patchVerts = VertexExtensions.LumpFactory(reader.ReadLump(this[index]), version, 1); } } return _patchVerts; } } /// /// A object containing the Patch Vertex Indices lump, if available. /// public NumList patchIndices { get { if (_patchIndices == null) { NumList.DataType type; int index = NumList.GetIndexForPatchIndicesLump(version, out type); if (index >= 0) { _patchIndices = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _patchIndices; } } /// /// A object containing the Face Vertex Indices lump, if available. /// public NumList indices { get { if (_indices == null) { NumList.DataType type; int index = NumList.GetIndexForIndicesLump(version, out type); if (index >= 0) { _indices = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _indices; } } /// /// A object containing the Texture offsets table lump, if available. /// public NumList texTable { get { if (_texTable == null) { NumList.DataType type; int index = NumList.GetIndexForTexTableLump(version, out type); if (index >= 0) { _texTable = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _texTable; } } /// /// A object containing the Displacement Triangles lump, if available. /// public NumList displacementTriangles { get { if (_displacementTriangles == null) { NumList.DataType type; int index = NumList.GetIndexForDisplacementTrianglesLump(version, out type); if (index >= 0) { _displacementTriangles = NumList.LumpFactory(reader.ReadLump(this[index]), type); } } return _displacementTriangles; } } /// /// The object in the BSP file containing internal lumps, if available. /// public GameLump gameLump { get { if (_gameLump == null) { int index = GameLump.GetIndexForLump(version); if (index >= 0) { _gameLump = GameLump.LumpFactory(reader.ReadLump(this[index]), version, this[index].version); } } return _gameLump; } } /// /// The object in the BSP file extracted from the , if available. /// public StaticProps staticProps { get { if (_staticProps == null) { if (gameLump != null && gameLump.ContainsKey(GameLumpType.sprp)) { LumpInfo info = gameLump[GameLumpType.sprp]; byte[] thisLump = new byte[info.length]; Array.Copy(gameLump.rawData, info.offset - gameLump.gameLumpOffset, thisLump, 0, info.length); _staticProps = StaticProp.LumpFactory(thisLump, version, info.version); } } return _staticProps; } } /// /// Gets the path to this BSP file. /// public string filePath { get; private set; } /// /// Gets the file name of this map. /// public string MapName { get { int i; for (i = 0; i < filePath.Length; ++i) { if (filePath[filePath.Length - 1 - i] == '\\') { break; } if (filePath[filePath.Length - 1 - i] == '/') { break; } } return filePath.Substring(filePath.Length - i, (filePath.Length) - (filePath.Length - i)); } } /// /// Gets the file name of this map without the ".BSP" extension. /// public string MapNameNoExtension { get { string name = MapName; int i; for (i = 0; i < name.Length; ++i) { if (name[name.Length - 1 - i] == '.') { break; } } return name.Substring(0, (name.Length - 1 - i) - (0)); } } /// /// Gets the folder path where this map is located. /// public string Folder { get { int i; for (i = 0; i < filePath.Length; ++i) { if (filePath[filePath.Length - 1 - i] == '\\') { break; } if (filePath[filePath.Length - 1 - i] == '/') { break; } } return filePath.Substring(0, (filePath.Length - i) - (0)); } } /// /// Gets the object associated with the lump with index "". /// /// Index of the lump to get information for. /// A object containing information about lump "". public new LumpInfo this[int index] { get { if (!ContainsKey(index)) { base[index] = reader.GetLumpInfo(index, version); } return base[index]; } } /// /// Creates a new instance pointing to the file at . The /// Lists in this class will be read and populated when accessed through their properties. /// /// The path to the .BSP file. public BSP(string filePath) : base(16) { reader = new BSPReader(new FileInfo(filePath)); this.filePath = filePath; } /// /// Creates a new instance using the file referenced by . The /// Lists in this class will be read and populated when accessed through their properties. /// /// A reference to the .BSP file. public BSP(FileInfo file) : base(16) { reader = new BSPReader(file); filePath = file.FullName; } /// /// Gets the number of lumps in a given BSP version. /// /// The version to get the number of lumps for. /// The number of lumps used by a BSP of version . public static int GetNumLumps(MapType version) { switch (version) { case MapType.Quake: { return 15; } case MapType.Daikatana: case MapType.Quake2: { return 16; } case MapType.Quake3: { return 17; } case MapType.Raven: case MapType.Nightfire: { return 18; } case MapType.FAKK: case MapType.SiN: { return 20; } case MapType.SoF: { return 22; } case MapType.MOHAA: { return 28; } case MapType.STEF2: case MapType.STEF2Demo: { return 30; } case MapType.CoD: case MapType.CoD2: { return 31; } case MapType.CoD4: { return 55; } case MapType.TacticalInterventionEncrypted: case MapType.DMoMaM: 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.Vindictus: { return 64; } default: { return -1; } } } /// /// Gets all objects of type referenced through passed object /// contained in the lump stored in this class. This is done by /// reflecting the Type of and looping through its public properties to find /// a member with an attribute and a member with a attribute /// both corresponding to . The index and count are obtained and used to construct /// a new List<> object containing the corresponding objects. /// /// The type of object stored in the lump . /// The object which contains and index and count corresponding to . /// The name of the property in this object to get a List of objects from. /// The List<> of objects in the lump from the index and length specified in . /// The class contains no property corresponding to . /// The object referenced by is missing one or both members with IndexAttribute or CountAttribute attributes corresponding to . /// One or both of or is null. public List GetReferencedObjects(object o, string lumpName) { if (o == null) { throw new ArgumentNullException("Object cannot be null."); } if (lumpName == null) { throw new ArgumentNullException("Lump name cannot be null."); } // First, find the property in this class corresponding to lumpName, and grab its "get" method PropertyInfo targetLump = typeof(BSP).GetProperty(lumpName, BindingFlags.Public | BindingFlags.Instance); if (targetLump == null) { throw new ArgumentException("The lump " + lumpName + " does not exist in the BSP class."); } // Next, find the properties in the passed object corresponding to lumpName, through the Index and Length custom attributes Type objectType = o.GetType(); PropertyInfo[] objectProperties = objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance); PropertyInfo indexProperty = null; PropertyInfo countProperty = null; foreach (PropertyInfo info in objectProperties) { IndexAttribute indexAttribute = info.GetCustomAttribute(); if (indexAttribute != null) { if (indexAttribute.lumpName == lumpName) { indexProperty = info; if (indexProperty != null && countProperty != null) { break; } } } CountAttribute lengthAttribute = info.GetCustomAttribute(); if (lengthAttribute != null) { if (lengthAttribute.lumpName == lumpName) { countProperty = info; if (indexProperty != null && countProperty != null) { break; } } } } if (indexProperty == null || countProperty == null) { throw new ArgumentException("An object of type " + objectType.Name + " does not implement both an Index and Count for lump " + lumpName + "."); } // Get the index and length from the object int index = (int)(indexProperty.GetGetMethod().Invoke(o, null)); int count = (int)(countProperty.GetGetMethod().Invoke(o, null)); // Get the lump from this class List theLump = targetLump.GetGetMethod().Invoke(this, null) as List; return theLump.GetRange(index, count); } } }