Refactor of BSP and BSPReader to enable lump structures to know what version of lump they represent.

As a result, fix reading v2 faces lump from Vindictus.
This commit is contained in:
Will
2016-02-15 02:57:10 -07:00
parent 75ee1e44f1
commit b8c883fd75
24 changed files with 572 additions and 410 deletions

View File

@@ -232,12 +232,13 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Plane"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was null.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</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 List<Plane> LumpFactory(byte[] data, MapType type) {
public static List<Plane> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}

View File

@@ -71,12 +71,13 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <returns>The resulting <see cref="UIVertex"/> object.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was null.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
/// <remarks><see cref="UIVertex"/> has no constructor, so the object must be initialized field-by-field. Even if it
/// did have a constructor, the way data needs to be read wouldn't allow use of it.</remarks>
public static UIVertex CreateVertex(byte[] data, MapType type) {
public static UIVertex CreateVertex(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -135,12 +136,13 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="UIVertex"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
/// <remarks>This function goes here since I can't put it into Unity's <c>UIVertex</c> class, and so I can't
/// depend on having a constructor taking a byte array.</remarks>
public static List<UIVertex> LumpFactory(byte[] data, MapType type) {
public static List<UIVertex> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -187,7 +189,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, i * structLength, bytes, 0, structLength);
lump.Add(CreateVertex(bytes, type));
lump.Add(CreateVertex(bytes, type, version));
}
return lump;
}

View File

@@ -54,19 +54,20 @@ namespace LibBSP {
/// <summary>
/// Struct containing basic information for a lump in a BSP file.
/// </summary>
public struct LumpInfo {
public class LumpInfo {
public int ident;
public int flags;
public int version;
public int offset;
public int length;
public FileInfo lumpFile;
}
/// <summary>
/// Holds data for any and all BSP formats. Any unused lumps in a given format
/// will be left as null.
/// </summary>
public class BSP {
public class BSP : Dictionary<int, LumpInfo> {
private MapType _version;
@@ -137,7 +138,7 @@ namespace LibBSP {
if (_entities == null) {
int index = Entity.GetIndexForLump(version);
if (index >= 0) {
_entities = Entity.LumpFactory(reader.ReadLumpNum(index, version), version);
_entities = Entity.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _entities;
@@ -152,7 +153,7 @@ namespace LibBSP {
if (_planes == null) {
int index = PlaneExtensions.GetIndexForLump(version);
if (index >= 0) {
_planes = PlaneExtensions.LumpFactory(reader.ReadLumpNum(index, version), version);
_planes = PlaneExtensions.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _planes;
@@ -167,7 +168,7 @@ namespace LibBSP {
if (_textures == null) {
int index = Texture.GetIndexForLump(version);
if (index >= 0) {
_textures = Texture.LumpFactory(reader.ReadLumpNum(index, version), version);
_textures = Texture.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _textures;
@@ -182,7 +183,7 @@ namespace LibBSP {
if (_vertices == null) {
int index = UIVertexExtensions.GetIndexForLump(version);
if (index >= 0) {
_vertices = UIVertexExtensions.LumpFactory(reader.ReadLumpNum(index, version), version);
_vertices = UIVertexExtensions.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _vertices;
@@ -197,7 +198,7 @@ namespace LibBSP {
if (_nodes == null) {
int index = Node.GetIndexForLump(version);
if (index >= 0) {
_nodes = Node.LumpFactory(reader.ReadLumpNum(index, version), version);
_nodes = Node.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _nodes;
@@ -212,7 +213,7 @@ namespace LibBSP {
if (_texInfo == null) {
int index = TextureInfo.GetIndexForLump(version);
if (index >= 0) {
_texInfo = TextureInfo.LumpFactory(reader.ReadLumpNum(index, version), version);
_texInfo = TextureInfo.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _texInfo;
@@ -227,7 +228,7 @@ namespace LibBSP {
if (_faces == null) {
int index = Face.GetIndexForLump(version);
if (index >= 0) {
_faces = Face.LumpFactory(reader.ReadLumpNum(index, version), version);
_faces = Face.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _faces;
@@ -242,7 +243,7 @@ namespace LibBSP {
if (_leaves == null) {
int index = Leaf.GetIndexForLump(version);
if (index >= 0) {
_leaves = Leaf.LumpFactory(reader.ReadLumpNum(index, version), version);
_leaves = Leaf.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _leaves;
@@ -257,7 +258,7 @@ namespace LibBSP {
if (_edges == null) {
int index = Edge.GetIndexForLump(version);
if (index >= 0) {
_edges = Edge.LumpFactory(reader.ReadLumpNum(index, version), version);
_edges = Edge.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _edges;
@@ -272,7 +273,7 @@ namespace LibBSP {
if (_models == null) {
int index = Model.GetIndexForLump(version);
if (index >= 0) {
_models = Model.LumpFactory(reader.ReadLumpNum(index, version), version);
_models = Model.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _models;
@@ -287,7 +288,7 @@ namespace LibBSP {
if (_brushes == null) {
int index = Brush.GetIndexForLump(version);
if (index >= 0) {
_brushes = Brush.LumpFactory(reader.ReadLumpNum(index, version), version);
_brushes = Brush.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _brushes;
@@ -302,7 +303,7 @@ namespace LibBSP {
if (_brushSides == null) {
int index = BrushSide.GetIndexForLump(version);
if (index >= 0) {
_brushSides = BrushSide.LumpFactory(reader.ReadLumpNum(index, version), version);
_brushSides = BrushSide.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _brushSides;
@@ -317,7 +318,7 @@ namespace LibBSP {
if (_materials == null) {
int index = Texture.GetIndexForMaterialLump(version);
if (index >= 0) {
_materials = Texture.LumpFactory(reader.ReadLumpNum(index, version), version);
_materials = Texture.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _materials;
@@ -332,7 +333,7 @@ namespace LibBSP {
if (_originalFaces == null) {
int index = Face.GetIndexForOriginalFacesLump(version);
if (index >= 0) {
_originalFaces = Face.LumpFactory(reader.ReadLumpNum(index, version), version);
_originalFaces = Face.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _originalFaces;
@@ -347,7 +348,7 @@ namespace LibBSP {
if (_texDatas == null) {
int index = TextureData.GetIndexForLump(version);
if (index >= 0) {
_texDatas = TextureData.LumpFactory(reader.ReadLumpNum(index, version), version);
_texDatas = TextureData.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _texDatas;
@@ -362,7 +363,7 @@ namespace LibBSP {
if (_dispInfos == null) {
int index = DisplacementInfo.GetIndexForLump(version);
if (index >= 0) {
_dispInfos = DisplacementInfo.LumpFactory(reader.ReadLumpNum(index, version), version);
_dispInfos = DisplacementInfo.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _dispInfos;
@@ -377,7 +378,7 @@ namespace LibBSP {
if (_dispVerts == null) {
int index = DisplacementVertex.GetIndexForLump(version);
if (index >= 0) {
_dispVerts = DisplacementVertex.LumpFactory(reader.ReadLumpNum(index, version), version);
_dispVerts = DisplacementVertex.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _dispVerts;
@@ -392,7 +393,7 @@ namespace LibBSP {
if (_cubemaps == null) {
int index = Cubemap.GetIndexForLump(version);
if (index >= 0) {
_cubemaps = Cubemap.LumpFactory(reader.ReadLumpNum(index, version), version);
_cubemaps = Cubemap.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _cubemaps;
@@ -408,7 +409,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForMarkSurfacesLump(version, out type);
if (index >= 0) {
_markSurfaces = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_markSurfaces = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _markSurfaces;
@@ -424,7 +425,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForSurfEdgesLump(version, out type);
if (index >= 0) {
_surfEdges = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_surfEdges = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _surfEdges;
@@ -440,7 +441,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForMarkBrushesLump(version, out type);
if (index >= 0) {
_markBrushes = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_markBrushes = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _markBrushes;
@@ -456,7 +457,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForIndicesLump(version, out type);
if (index >= 0) {
_indices = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_indices = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _indices;
@@ -472,7 +473,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForTexTableLump(version, out type);
if (index >= 0) {
_texTable = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_texTable = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _texTable;
@@ -488,7 +489,7 @@ namespace LibBSP {
NumList.DataType type;
int index = NumList.GetIndexForDisplacementTrianglesLump(version, out type);
if (index >= 0) {
_displacementTriangles = NumList.LumpFactory(reader.ReadLumpNum(index, version), type);
_displacementTriangles = NumList.LumpFactory(reader.ReadLump(this[index]), type);
}
}
return _displacementTriangles;
@@ -503,7 +504,7 @@ namespace LibBSP {
if (_gameLump == null) {
int index = GameLump.GetIndexForLump(version);
if (index >= 0) {
_gameLump = GameLump.LumpFactory(reader.ReadLumpNum(index, version), version);
_gameLump = GameLump.LumpFactory(reader.ReadLump(this[index]), version, this[index].version);
}
}
return _gameLump;
@@ -584,12 +585,26 @@ namespace LibBSP {
}
}
/// <summary>
/// Gets the <see cref="LumpInfo"/> object associated with the lump with index "<paramref name="index"/>".
/// </summary>
/// <param name="index">Index of the lump to get information for.</param>
/// <returns>A <see cref="LumpInfo"/> object containing information about lump "<paramref name="index"/>".</returns>
public LumpInfo this[int index] {
get {
if (!ContainsKey(index)) {
base[index] = reader.GetLumpInfo(index, version);
}
return base[index];
}
}
/// <summary>
/// Creates a new <see cref="BSP"/> instance pointing to the file at <paramref name="filePath"/>. The
/// <c>List</c>s in this class will be read and populated when accessed through their properties.
/// </summary>
/// <param name="filePath">The path to the .BSP file.</param>
public BSP(string filePath) {
public BSP(string filePath) : base(16) {
reader = new BSPReader(new FileInfo(filePath));
this.filePath = filePath;
}
@@ -599,16 +614,71 @@ namespace LibBSP {
/// <c>List</c>s in this class will be read and populated when accessed through their properties.
/// </summary>
/// <param name="file">A reference to the .BSP file.</param>
public BSP(FileInfo file) {
public BSP(FileInfo file) : base(16) {
reader = new BSPReader(file);
this.filePath = file.FullName;
}
/// <summary>
/// Tells the <see cref="BSPReader"/> object to release file handles for the BSP file.
/// Gets the number of lumps in a given BSP version.
/// </summary>
public void Close() {
reader.Close();
/// <param name="version">The version to get the number of lumps for.</param>
/// <returns>The number of lumps used by a BSP of version <paramref name="version"/>.</returns>
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;
}
}
}
/// <summary>

View File

@@ -17,9 +17,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Brush(byte[] data, MapType type) : this() {
public Brush(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -88,10 +89,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Brush"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Brush> LumpFactory(byte[] data, MapType type) {
public static List<Brush> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -136,7 +138,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Brush(bytes, type));
lump.Add(new Brush(bytes, type, version));
}
return lump;
}

View File

@@ -19,9 +19,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public BrushSide(byte[] data, MapType type) : this() {
public BrushSide(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -107,10 +108,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="BrushSide"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<BrushSide> LumpFactory(byte[] data, MapType type) {
public static List<BrushSide> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -161,7 +163,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; i++) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new BrushSide(bytes, type));
lump.Add(new BrushSide(bytes, type, version));
}
return lump;
}

View File

@@ -25,9 +25,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Cubemap(byte[] data, MapType type) : this() {
public Cubemap(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -58,10 +59,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Cubemap"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Cubemap> LumpFactory(byte[] data, MapType type) {
public static List<Cubemap> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -90,7 +92,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Cubemap(bytes, type));
lump.Add(new Cubemap(bytes, type, version));
offset += structLength;
}
return lump;

View File

@@ -28,9 +28,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public DisplacementInfo(byte[] data, MapType type) : this() {
public DisplacementInfo(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -78,10 +79,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="DisplacementInfo"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<DisplacementInfo> LumpFactory(byte[] data, MapType type) {
public static List<DisplacementInfo> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -119,7 +121,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new DisplacementInfo(bytes, type));
lump.Add(new DisplacementInfo(bytes, type, version));
offset += structLength;
}
return lump;

View File

@@ -26,8 +26,9 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
public DisplacementVertex(byte[] data, MapType type) : this() {
public DisplacementVertex(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -41,13 +42,14 @@ namespace LibBSP {
/// </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>A <see cref="DisplacementVertices"/> object.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
public static DisplacementVertices LumpFactory(byte[] data, MapType type) {
public static DisplacementVertices LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
return new DisplacementVertices(data, type);
return new DisplacementVertices(data, type, version);
}
/// <summary>

View File

@@ -15,9 +15,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Edge(byte[] data, MapType type) : this() {
public Edge(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -57,10 +58,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Edge"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was null.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Edge> LumpFactory(byte[] data, MapType type) {
public static List<Edge> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -96,7 +98,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Edge(bytes, type));
lump.Add(new Edge(bytes, type, version));
}
return lump;
}

View File

@@ -48,9 +48,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Face(byte[] data, MapType type) : this() {
public Face(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -135,7 +136,11 @@ namespace LibBSP {
numEdges = BitConverter.ToInt32(data, 12);
textureScale = BitConverter.ToInt32(data, 16);
displacement = BitConverter.ToInt32(data, 20);
original = BitConverter.ToInt32(data, 56);
if (version == 2) {
original = BitConverter.ToInt32(data, 60);
} else {
original = BitConverter.ToInt32(data, 56);
}
break;
}
case MapType.Nightfire: {
@@ -164,10 +169,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Face"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Face> LumpFactory(byte[] data, MapType type) {
public static List<Face> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -208,7 +214,11 @@ namespace LibBSP {
break;
}
case MapType.Vindictus: {
structLength = 72;
if (version == 2) {
structLength = 76;
} else {
structLength = 72;
}
break;
}
case MapType.Quake3: {
@@ -237,7 +247,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Face(bytes, type));
lump.Add(new Face(bytes, type, version));
}
return lump;
}

View File

@@ -19,9 +19,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Leaf(byte[] data, MapType type) : this() {
public Leaf(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -99,10 +100,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Leaf"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Leaf> LumpFactory(byte[] data, MapType type) {
public static List<Leaf> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -161,7 +163,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Leaf(bytes, type));
lump.Add(new Leaf(bytes, type, version));
}
return lump;
}

View File

@@ -12,8 +12,9 @@ namespace LibBSP {
/// </summary>
/// <param name="data">Array of <c>byte</c>s to parse.</param>
/// <param name="type">Format identifier.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
public DisplacementVertices(byte[] data, MapType type) : base(data.Length / 20) {
public DisplacementVertices(byte[] data, MapType type, int version = 0) : base(data.Length / 20) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -21,7 +22,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
Add(new DisplacementVertex(bytes, type));
Add(new DisplacementVertex(bytes, type, version));
}
}

View File

@@ -46,9 +46,10 @@ namespace LibBSP {
/// </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>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public GameLump(byte[] data, MapType type) {
public GameLump(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -84,28 +85,28 @@ namespace LibBSP {
int lowestLumpOffset = Int32.MaxValue;
for (int i = 0; i < numGameLumps; ++i) {
int ident = BitConverter.ToInt32(data, (i * structLength) + 4);
int flags;
int version;
int offset;
int length;
int lumpIdent = BitConverter.ToInt32(data, (i * structLength) + 4);
int lumpFlags;
int lumpVersion;
int lumpOffset;
int lumpLength;
if (type == MapType.Vindictus) {
flags = BitConverter.ToInt32(data, (i * structLength) + 8);
version = BitConverter.ToInt32(data, (i * structLength) + 12);
offset = BitConverter.ToInt32(data, (i * structLength) + 16);
length = BitConverter.ToInt32(data, (i * structLength) + 20);
lumpFlags = BitConverter.ToInt32(data, (i * structLength) + 8);
lumpVersion = BitConverter.ToInt32(data, (i * structLength) + 12);
lumpOffset = BitConverter.ToInt32(data, (i * structLength) + 16);
lumpLength = BitConverter.ToInt32(data, (i * structLength) + 20);
} else {
flags = BitConverter.ToUInt16(data, (i * structLength) + 8);
version = BitConverter.ToUInt16(data, (i * structLength) + 10);
offset = BitConverter.ToInt32(data, (i * structLength) + 12);
length = BitConverter.ToInt32(data, (i * structLength) + 16);
lumpFlags = BitConverter.ToUInt16(data, (i * structLength) + 8);
lumpVersion = BitConverter.ToUInt16(data, (i * structLength) + 10);
lumpOffset = BitConverter.ToInt32(data, (i * structLength) + 12);
lumpLength = BitConverter.ToInt32(data, (i * structLength) + 16);
}
LumpInfo info = new LumpInfo {
ident = ident,
flags = flags,
version = version,
offset = offset,
length = length,
ident = lumpIdent,
flags = lumpFlags,
version = lumpVersion,
offset = lumpOffset,
length = lumpLength,
};
this[(GameLumpType)info.ident] = info;
@@ -124,10 +125,11 @@ namespace LibBSP {
/// </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>A new <see cref="GameLump"/> object.</returns>
/// <remarks>This is only here for consistency with the other lump structures.</remarks>
public static GameLump LumpFactory(byte[] data, MapType type) {
return new GameLump(data, type);
public static GameLump LumpFactory(byte[] data, MapType type, int version = 0) {
return new GameLump(data, type, version);
}
/// <summary>

View File

@@ -16,7 +16,7 @@ namespace LibBSP {
/// <param name="type">Format identifier.</param>
/// <param name="version">Version of static prop lump this is.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
public StaticProps(byte[] data, MapType type, int version) {
public StaticProps(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}

View File

@@ -13,9 +13,10 @@ namespace LibBSP {
/// </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>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Textures(byte[] data, MapType type) {
public Textures(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -68,7 +69,7 @@ namespace LibBSP {
// They are null-terminated strings, of non-constant length (not padded)
bytes = new byte[i - offset];
Array.Copy(data, offset, bytes, 0, i - offset);
Add(new Texture(bytes, type));
Add(new Texture(bytes, type, version));
offset = i + 1;
}
}
@@ -80,7 +81,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < numElements; ++i) {
Array.Copy(data, BitConverter.ToInt32(data, (i + 1) * 4), bytes, 0, structLength);
Add(new Texture(bytes, type));
Add(new Texture(bytes, type, version));
}
return;
}
@@ -93,7 +94,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
Add(new Texture(bytes, type));
Add(new Texture(bytes, type, version));
}
}
}

View File

@@ -28,9 +28,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Model(byte[] data, MapType type) : this() {
public Model(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -110,10 +111,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Model"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Model> LumpFactory(byte[] data, MapType type) {
public static List<Model> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -169,7 +171,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Model(bytes, type));
lump.Add(new Model(bytes, type, version));
offset += structLength;
}
return lump;

View File

@@ -17,9 +17,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Node(byte[] data, MapType type) : this() {
public Node(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -69,10 +70,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="Node"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<Node> LumpFactory(byte[] data, MapType type) {
public static List<Node> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -126,7 +128,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new Node(bytes, type));
lump.Add(new Node(bytes, type, version));
offset += structLength;
}
return lump;

View File

@@ -36,7 +36,7 @@ namespace LibBSP {
/// <param name="version">The version of static prop lump this object is a member of.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public StaticProp(byte[] data, MapType type, int version) : this() {
public StaticProp(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -110,7 +110,7 @@ namespace LibBSP {
/// <param name="type">The map type.</param>
/// <param name="version">The version of the Static Prop lump.</param>
/// <returns>A <see cref="StaticProps"/> object.</returns>
public static StaticProps LumpFactory(byte[] data, MapType type, int version) {
public static StaticProps LumpFactory(byte[] data, MapType type, int version = 0) {
return new StaticProps(data, type, version);
}
}

View File

@@ -36,9 +36,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public Texture(byte[] data, MapType type) : this() {
public Texture(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -108,9 +109,10 @@ namespace LibBSP {
/// </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>A <see cref="Textures"/> object.</returns>
public static Textures LumpFactory(byte[] data, MapType type) {
return new Textures(data, type);
public static Textures LumpFactory(byte[] data, MapType type, int version = 0) {
return new Textures(data, type, version);
}
/// <summary>

View File

@@ -30,8 +30,9 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
public TextureData(byte[] data, MapType type) : this() {
public TextureData(byte[] data, MapType type, int version = 0) : this() {
if (data == null) {
throw new ArgumentNullException();
}
@@ -48,9 +49,10 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="TextureData"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
public static List<TextureData> LumpFactory(byte[] data, MapType type) {
public static List<TextureData> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -59,7 +61,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; i++) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new TextureData(bytes, type));
lump.Add(new TextureData(bytes, type, version));
}
return lump;
}

View File

@@ -44,9 +44,10 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>byte</c> array to parse.</param>
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
/// <exception cref="ArgumentNullException"><paramref name="data" /> was <c>null</c>.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public TextureInfo(byte[] data, MapType type) {
public TextureInfo(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -142,10 +143,11 @@ namespace LibBSP {
/// </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>A <c>List</c> of <see cref="TextureInfo"/> objects.</returns>
/// <exception cref="ArgumentNullException"><paramref name="data"/> was null.</exception>
/// <exception cref="ArgumentException">This structure is not implemented for the given maptype.</exception>
public static List<TextureInfo> LumpFactory(byte[] data, MapType type) {
public static List<TextureInfo> LumpFactory(byte[] data, MapType type, int version = 0) {
if (data == null) {
throw new ArgumentNullException();
}
@@ -184,7 +186,7 @@ namespace LibBSP {
byte[] bytes = new byte[structLength];
for (int i = 0; i < data.Length / structLength; ++i) {
Array.Copy(data, (i * structLength), bytes, 0, structLength);
lump.Add(new TextureInfo(bytes, type));
lump.Add(new TextureInfo(bytes, type, version));
}
return lump;
}

View File

@@ -156,7 +156,9 @@ namespace LibBSP {
/// Initializes a new instance of an <see cref="Entity"/>, parsing the given <c>byte</c> array into an <see cref="Entity"/> structure.
/// </summary>
/// <param name="data">Array to parse.</param>
public Entity(byte[] data, MapType type) : this(Encoding.ASCII.GetString(data).Split('\n')) { }
/// <param name="type">The map type.</param>
/// <param name="version">The version of this lump.</param>
public Entity(byte[] data, MapType type, int version = 0) : this(Encoding.ASCII.GetString(data).Split('\n')) { }
/// <summary>
/// Initializes a new instance of an <see cref="Entity"/> with the given classname.
@@ -521,8 +523,9 @@ namespace LibBSP {
/// </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, MapType type) {
public static Entities LumpFactory(byte[] data, MapType type, int version = 0) {
return new Entities(data, type);
}

View File

@@ -37,7 +37,8 @@ namespace LibBSP {
/// </summary>
/// <param name="data"><c>Byte</c>s read from a file.</param>
/// <param name="type">The <see cref="MapType"/> of the source map.</param>
public Entities(byte[] data, MapType type) : base() {
/// <param name="version">The version of this lump.</param>
public Entities(byte[] data, MapType type, int version = 0) : base() {
// Keep track of whether or not we're currently in a set of quotation marks.
// I came across a map where the idiot map maker used { and } within a value. This broke the code before.
bool inQuotes = false;

View File

@@ -9,9 +9,7 @@ namespace LibBSP {
/// </summary>
public class BSPReader {
private FileInfo bspFile;
private FileStream stream;
private BinaryReader binaryReader;
private Dictionary<int, FileInfo> lumpFiles = null;
private Dictionary<int, LumpInfo> lumpFiles = null;
private bool _bigEndian = false;
@@ -34,22 +32,25 @@ namespace LibBSP {
throw new FileNotFoundException("Unable to open BSP file; file " + file.FullName + " not found.");
} else {
this.bspFile = file;
this.stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read);
this.binaryReader = new BinaryReader(this.stream);
}
}
/// <summary>
/// Reads this lump in the map.
/// Gets the information for lump "<paramref name="index"/>" for this BSP file when reading it as "<paramref name="version"/>".
/// </summary>
/// <param name="index">The index of the lump to get.</param>
/// <param name="version">The version of BSP this is.</param>
/// <returns>Array of bytes read from the BSP file.</returns>
public byte[] ReadLumpNum(int index, MapType version) {
/// <param name="index">The numerical index of this lump.</param>
/// <param name="version">The type of BSP to interpret the file as.</param>
/// <returns>A <see cref="LumpInfo"/> object containing information about the lump.</returns>
/// <exception cref="IndexOutOfRandeException">"<paramref name="index"/>" is less than zero, or greater than the number of lumps allowed by "<paramref name="version"/>".</exception>
public LumpInfo GetLumpInfo(int index, MapType version) {
if (index < 0 || index >= BSP.GetNumLumps(version)) {
throw new IndexOutOfRangeException();
}
switch (version) {
case MapType.Quake:
case MapType.Nightfire: {
return ReadLumpFromOffsetLengthPairAtOffset(4 + (8 * index), version);
return GetLumpInfoAtOffset(4 + (8 * index), version);
}
case MapType.Quake2:
case MapType.Daikatana:
@@ -57,35 +58,15 @@ namespace LibBSP {
case MapType.SoF:
case MapType.Quake3:
case MapType.Raven:
case MapType.CoD: {
return ReadLumpFromOffsetLengthPairAtOffset(8 + (8 * index), version);
}
case MapType.CoD:
case MapType.CoD2: {
stream.Seek(8 + (8 * index), SeekOrigin.Begin);
int temp = binaryReader.ReadInt32();
return ReadLump(binaryReader.ReadInt32(), temp, version);
return GetLumpInfoAtOffset(8 + (8 * index), version);
}
case MapType.STEF2:
case MapType.STEF2Demo:
case MapType.MOHAA:
case MapType.FAKK: {
return ReadLumpFromOffsetLengthPairAtOffset(12 + (8 * index), version);
}
case MapType.CoD4: {
stream.Seek(8, SeekOrigin.Begin);
int numlumps = binaryReader.ReadInt32();
int offset = (numlumps * 8) + 12;
for (int i = 0; i < numlumps; i++) {
int id = binaryReader.ReadInt32();
int length = binaryReader.ReadInt32();
if (id == index) {
return ReadLump(offset, length, version);
} else {
offset += length;
while (offset % 4 != 0) { offset++; }
}
}
break;
return GetLumpInfoAtOffset(12 + (8 * index), version);
}
case MapType.Source17:
case MapType.Source18:
@@ -101,18 +82,147 @@ namespace LibBSP {
if (lumpFiles == null) {
LoadLumpFiles();
}
if (lumpFiles.ContainsKey(index)) { return ReadLumpFile(index); }
return ReadLumpFromOffsetLengthPairAtOffset(8 + (16 * index), version);
if (lumpFiles.ContainsKey(index)) { return lumpFiles[index]; }
return GetLumpInfoAtOffset(8 + (16 * index), version);
}
case MapType.CoD4: {
using (FileStream stream = new FileStream(bspFile.FullName, FileMode.Open, FileAccess.Read)) {
BinaryReader binaryReader = new BinaryReader(stream);
stream.Seek(8, SeekOrigin.Begin);
int numlumps = binaryReader.ReadInt32();
int offset = (numlumps * 8) + 12;
for (int i = 0; i < numlumps; i++) {
int id = binaryReader.ReadInt32();
int length = binaryReader.ReadInt32();
if (id == index) {
return new LumpInfo() {
offset = offset,
length = length
};
} else {
offset += length;
while (offset % 4 != 0) { offset++; }
}
}
binaryReader.Close();
}
return new LumpInfo();
}
default: {
return null;
}
}
return new byte[0];
}
/// <summary>
/// Gets the lump information at offset "<paramref name="offset"/>" for this BSP file when reading it as "<paramref name="version"/>".
/// </summary>
/// <param name="offset">The offset of the lump's information.</param>
/// <param name="version">The type of BSP to interpret the file as.</param>
/// <returns>A <see cref="LumpInfo"/> object containing information about the lump.</returns>
private LumpInfo GetLumpInfoAtOffset(int offset, MapType version) {
if (bspFile.Length < offset + 16) {
return new LumpInfo();
}
byte[] input;
using (FileStream stream = new FileStream(bspFile.FullName, FileMode.Open, FileAccess.Read)) {
BinaryReader binaryReader = new BinaryReader(stream);
stream.Seek(offset, SeekOrigin.Begin);
input = binaryReader.ReadBytes(16);
binaryReader.Close();
}
if (version == MapType.TacticalInterventionEncrypted) {
input = XorWithKeyStartingAtIndex(input, offset);
}
int lumpOffset = 0;
int lumpLength = 0;
int lumpVersion = 0;
int lumpIdent = 0;
if (version == MapType.L4D2) {
lumpVersion = BitConverter.ToInt32(input, 0);
lumpOffset = BitConverter.ToInt32(input, 4);
lumpLength = BitConverter.ToInt32(input, 8);
lumpIdent = BitConverter.ToInt32(input, 12);
// TODO: This is awful. Let's rework the enum to have internal ways to check engine forks.
} else if (version == MapType.Source17 ||
version == MapType.Source18 ||
version == MapType.Source19 ||
version == MapType.Source20 ||
version == MapType.Source21 ||
version == MapType.Source22 ||
version == MapType.Source23 ||
version == MapType.Source27 ||
version == MapType.Vindictus ||
version == MapType.DMoMaM ||
version == MapType.TacticalInterventionEncrypted) {
lumpOffset = BitConverter.ToInt32(input, 0);
lumpLength = BitConverter.ToInt32(input, 4);
lumpVersion = BitConverter.ToInt32(input, 8);
lumpIdent = BitConverter.ToInt32(input, 12);
} else if (version == MapType.CoD2) {
lumpLength = BitConverter.ToInt32(input, 0);
lumpOffset = BitConverter.ToInt32(input, 4);
} else {
lumpOffset = BitConverter.ToInt32(input, 0);
lumpLength = BitConverter.ToInt32(input, 4);
}
/*if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(lumpLength);
Array.Reverse(bytes);
lumpLength = BitConverter.ToInt32(bytes, 0);
bytes = BitConverter.GetBytes(lumpOffset);
Array.Reverse(bytes);
lumpOffset = BitConverter.ToInt32(bytes, 0);
}*/
return new LumpInfo() {
offset = lumpOffset,
length = lumpLength,
version = lumpVersion,
ident = lumpIdent
};
}
/// <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>
/// Loads any lump files associated with the BSP.
/// </summary>
private void LoadLumpFiles() {
lumpFiles = new Dictionary<int, FileInfo>();
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();
@@ -127,87 +237,27 @@ namespace LibBSP {
});
// Read the files in order. The last file in the list for a specific lump will replace that lump.
foreach (FileInfo file in files) {
FileStream fs = new FileStream(file.FullName, FileMode.Open, FileAccess.Read);
BinaryReader br = new BinaryReader(fs);
fs.Seek(4, SeekOrigin.Begin);
int lumpIndex = BitConverter.ToInt32(br.ReadBytes(4), 0);
lumpFiles[lumpIndex] = file;
br.Dispose();
fs.Dispose();
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>
/// Returns the data contained in the lump file for the lump <paramref name="index"/>.
/// </summary>
/// <param name="index">Index of the lump to get data from the lump file.</param>
/// <returns>The lump data in the lump file.</returns>
private byte[] ReadLumpFile(int index) {
FileInfo file = lumpFiles[index];
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 length = BitConverter.ToInt32(input, 12);
fs.Seek(offset, SeekOrigin.Begin);
byte[] output = br.ReadBytes(length);
br.Dispose();
fs.Dispose();
return output;
}
/// <summary>
/// Returns the lump referenced by the offset/length pair at the specified <paramref name="offset"/>,
/// read as two Int32.
/// </summary>
/// <param name="offset">The byte offset for the offset/length pair.</param>
/// <param name="version">The version of BSP this is.</param>
/// <returns>Array of bytes read from the BSP file.</returns>
private byte[] ReadLumpFromOffsetLengthPairAtOffset(int offset, MapType version) {
stream.Seek(offset, SeekOrigin.Begin);
byte[] input = binaryReader.ReadBytes(12);
if (version == MapType.TacticalInterventionEncrypted) {
input = XorWithKeyStartingAtIndex(input, offset);
}
int lumpOffset;
int lumpLength;
if (version == MapType.L4D2) {
lumpOffset = BitConverter.ToInt32(input, 4);
lumpLength = BitConverter.ToInt32(input, 8);
} else {
lumpOffset = BitConverter.ToInt32(input, 0);
lumpLength = BitConverter.ToInt32(input, 4);
}
/*if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(lumpLength);
Array.Reverse(bytes);
lumpLength = BitConverter.ToInt32(bytes, 0);
bytes = BitConverter.GetBytes(lumpOffset);
Array.Reverse(bytes);
lumpOffset = BitConverter.ToInt32(bytes, 0);
}*/
return ReadLump(lumpOffset, lumpLength, version);
}
/// <summary>
/// Reads the lump <paramref name="length"/> bytes long at <paramref name="offset"/> in the file.
/// </summary>
/// <param name="offset">Offset to start reading from.</param>
/// <param name="length">Length of the lump to read.</param>
/// <param name="version">The version of BSP this is.</param>
/// <returns>Array of bytes read from the BSP file.</returns>
public byte[] ReadLump(int offset, int length, MapType version) {
stream.Seek(offset, SeekOrigin.Begin);
byte[] input = binaryReader.ReadBytes(length);
if (version == MapType.TacticalInterventionEncrypted) {
input = XorWithKeyStartingAtIndex(input, offset);
}
return input;
}
/// <summary>
/// Xors the <paramref name="data"/> <c>byte</c> array with the localls stored key <c>byte</c> array, starting at a certain <paramref name="index"/> in the key.
/// Xors the <paramref name="data"/> <c>byte</c> array with the locally stored key <c>byte</c> array, starting at a certain <paramref name="index"/> in the key.
/// </summary>
/// <param name="data">The byte array to Xor.</param>
/// <param name="index">The index in the key byte array to start reading from.</param>
@@ -250,213 +300,216 @@ namespace LibBSP {
/// <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;
stream.Seek(0, SeekOrigin.Begin);
int data = binaryReader.ReadInt32();
if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(data);
Array.Reverse(bytes);
data = BitConverter.ToInt32(bytes, 0);
}
if (data == 1347633737) {
// 1347633737 reads in ASCII as "IBSP"
// Versions: CoD, CoD2, CoD4, Quake 2, Daikatana, Quake 3 (RtCW), Soldier of Fortune
data = binaryReader.ReadInt32();
using (FileStream stream = new FileStream(bspFile.FullName, FileMode.Open, FileAccess.Read)) {
BinaryReader binaryReader = new BinaryReader(stream);
stream.Seek(0, SeekOrigin.Begin);
int data = binaryReader.ReadInt32();
if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(data);
Array.Reverse(bytes);
data = BitConverter.ToInt32(bytes, 0);
}
switch (data) {
case 4: {
current = MapType.CoD2;
break;
if (data == 1347633737) {
// 1347633737 reads in ASCII as "IBSP"
// Versions: CoD, CoD2, CoD4, Quake 2, Daikatana, Quake 3 (RtCW), Soldier of Fortune
data = binaryReader.ReadInt32();
if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(data);
Array.Reverse(bytes);
data = BitConverter.ToInt32(bytes, 0);
}
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) {
switch (data) {
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 47: {
current = MapType.Quake3;
break;
}
case 59: {
current = MapType.CoD;
break;
}
}
} else {
if (data == 892416050) {
// 892416050 reads in ASCII as "2015," the game studio which developed MoHAA
current = MapType.MOHAA;
} else {
if (data == 1095516485) {
// 1095516485 reads in ASCII as "EALA," the ones who developed MoHAA Spearhead and Breakthrough
if (data == 892416050) {
// 892416050 reads in ASCII as "2015," the game studio which developed MoHAA
current = MapType.MOHAA;
} else {
if (data == 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
data = (int)binaryReader.ReadUInt16();
switch (data) {
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 {
// TODO: Vindictus? Before I was determining these by looking at the Game Lump data, is there a better way?
current = MapType.Source20;
}
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;
}
}
if (data == 1095516485) {
// 1095516485 reads in ASCII as "EALA," the ones who developed MoHAA Spearhead and Breakthrough
current = MapType.MOHAA;
} else {
if (data == 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;
if (data == 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
data = (int)binaryReader.ReadUInt16();
switch (data) {
case 17: {
current = MapType.Source17;
break;
} else {
if (temp == 152) {
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 {
// TODO: Vindictus? Before I was determining these by looking at the Game Lump data, is there a better way?
current = MapType.Source20;
}
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;
}
}
} else {
if (data == 556942917) {
// "EF2!"
current = MapType.STEF2;
} else {
if (data == 1263223110) {
// "FAKK"
// Formats: STEF2 demo, Heavy Metal FAKK2 (American McGee's Alice)
data = binaryReader.ReadInt32();
if (data == 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(data);
byte[] bytes = BitConverter.GetBytes(temp);
Array.Reverse(bytes);
data = BitConverter.ToInt32(bytes, 0);
temp = BitConverter.ToInt32(bytes, 0);
}
switch (data) {
case 19: {
current = MapType.STEF2Demo;
break;
}
case 12:
case 42: {// American McGee's Alice
current = MapType.FAKK;
if (temp == 168) {
current = MapType.SiN;
break;
} else {
if (temp == 152) {
break;
}
}
}
} else {
if (data == 556942917) {
// "EF2!"
current = MapType.STEF2;
} else {
switch (data) {
// 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;
if (data == 1263223110) {
// "FAKK"
// Formats: STEF2 demo, Heavy Metal FAKK2 (American McGee's Alice)
data = binaryReader.ReadInt32();
if (bigEndian) {
byte[] bytes = BitConverter.GetBytes(data);
Array.Reverse(bytes);
data = BitConverter.ToInt32(bytes, 0);
}
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);
data = BitConverter.ToInt32(XorWithKeyStartingAtIndex(binaryReader.ReadBytes(4)), 0);
if (data == 1347633750) {
current = MapType.TacticalInterventionEncrypted;
} else {
current = MapType.Undefined;
switch (data) {
case 19: {
current = MapType.STEF2Demo;
break;
}
case 12:
case 42: {// American McGee's Alice
current = MapType.FAKK;
break;
}
}
} else {
switch (data) {
// 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);
data = BitConverter.ToInt32(XorWithKeyStartingAtIndex(binaryReader.ReadBytes(4)), 0);
if (data == 1347633750) {
current = MapType.TacticalInterventionEncrypted;
} else {
current = MapType.Undefined;
}
break;
}
break;
}
}
}
@@ -465,15 +518,9 @@ namespace LibBSP {
}
}
}
binaryReader.Close();
}
return current;
}
/// <summary>
/// Disposes of the <c>FileStream</c> and releases the handle to the File.
/// </summary>
public void Close() {
stream.Dispose();
}
}
}