#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);
}
}
}