diff --git a/LibBSP/Source/Extensions/PlaneExtensions.cs b/LibBSP/Source/Extensions/PlaneExtensions.cs index 8076273..6bd47b9 100644 --- a/LibBSP/Source/Extensions/PlaneExtensions.cs +++ b/LibBSP/Source/Extensions/PlaneExtensions.cs @@ -232,12 +232,13 @@ namespace LibBSP { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. /// 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. - public static List LumpFactory(byte[] data, MapType type) { + public static List LumpFactory(byte[] data, MapType type, int version = 0) { if (data == null) { throw new ArgumentNullException(); } diff --git a/LibBSP/Source/Extensions/UIVertexExtensions.cs b/LibBSP/Source/Extensions/UIVertexExtensions.cs index 14f401a..f8459d4 100644 --- a/LibBSP/Source/Extensions/UIVertexExtensions.cs +++ b/LibBSP/Source/Extensions/UIVertexExtensions.cs @@ -71,12 +71,13 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// The resulting object. /// was null. /// This structure is not implemented for the given maptype. /// 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. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. /// This function goes here since I can't put it into Unity's UIVertex class, and so I can't /// depend on having a constructor taking a byte array. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/BSP.cs b/LibBSP/Source/Structs/BSP/BSP.cs index 721dfa9..b9c4dcf 100644 --- a/LibBSP/Source/Structs/BSP/BSP.cs +++ b/LibBSP/Source/Structs/BSP/BSP.cs @@ -54,19 +54,20 @@ namespace LibBSP { /// /// Struct containing basic information for a lump in a BSP file. /// - public struct LumpInfo { + 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 BSP formats. Any unused lumps in a given format /// will be left as null. /// - public class BSP { + public class BSP : Dictionary { 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 { } } + /// + /// Gets the object associated with the lump with index "". + /// + /// Index of the lump to get information for. + /// A object containing information about lump "". + public 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) { + public BSP(string filePath) : base(16) { reader = new BSPReader(new FileInfo(filePath)); this.filePath = filePath; } @@ -599,16 +614,71 @@ namespace LibBSP { /// Lists in this class will be read and populated when accessed through their properties. /// /// A reference to the .BSP file. - public BSP(FileInfo file) { + public BSP(FileInfo file) : base(16) { reader = new BSPReader(file); this.filePath = file.FullName; } /// - /// Tells the object to release file handles for the BSP file. + /// Gets the number of lumps in a given BSP version. /// - public void Close() { - reader.Close(); + /// 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; + } + } } /// diff --git a/LibBSP/Source/Structs/BSP/Brush.cs b/LibBSP/Source/Structs/BSP/Brush.cs index 0914ed3..c43f946 100644 --- a/LibBSP/Source/Structs/BSP/Brush.cs +++ b/LibBSP/Source/Structs/BSP/Brush.cs @@ -17,9 +17,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/BrushSide.cs b/LibBSP/Source/Structs/BSP/BrushSide.cs index 1f8eca5..77909d0 100644 --- a/LibBSP/Source/Structs/BSP/BrushSide.cs +++ b/LibBSP/Source/Structs/BSP/BrushSide.cs @@ -19,9 +19,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/Cubemap.cs b/LibBSP/Source/Structs/BSP/Cubemap.cs index 6d882b8..2191081 100644 --- a/LibBSP/Source/Structs/BSP/Cubemap.cs +++ b/LibBSP/Source/Structs/BSP/Cubemap.cs @@ -25,9 +25,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; diff --git a/LibBSP/Source/Structs/BSP/DisplacementInfo.cs b/LibBSP/Source/Structs/BSP/DisplacementInfo.cs index d60c05d..958c9a6 100644 --- a/LibBSP/Source/Structs/BSP/DisplacementInfo.cs +++ b/LibBSP/Source/Structs/BSP/DisplacementInfo.cs @@ -28,9 +28,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; diff --git a/LibBSP/Source/Structs/BSP/DisplacementVertex.cs b/LibBSP/Source/Structs/BSP/DisplacementVertex.cs index 3ccdd3d..c12d4f6 100644 --- a/LibBSP/Source/Structs/BSP/DisplacementVertex.cs +++ b/LibBSP/Source/Structs/BSP/DisplacementVertex.cs @@ -26,8 +26,9 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A object. /// was null. - 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); } /// diff --git a/LibBSP/Source/Structs/BSP/Edge.cs b/LibBSP/Source/Structs/BSP/Edge.cs index d40ab44..93a0b94 100644 --- a/LibBSP/Source/Structs/BSP/Edge.cs +++ b/LibBSP/Source/Structs/BSP/Edge.cs @@ -15,9 +15,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/Face.cs b/LibBSP/Source/Structs/BSP/Face.cs index c6f6927..7515526 100644 --- a/LibBSP/Source/Structs/BSP/Face.cs +++ b/LibBSP/Source/Structs/BSP/Face.cs @@ -48,9 +48,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/Leaf.cs b/LibBSP/Source/Structs/BSP/Leaf.cs index 0138366..11f0a75 100644 --- a/LibBSP/Source/Structs/BSP/Leaf.cs +++ b/LibBSP/Source/Structs/BSP/Leaf.cs @@ -19,9 +19,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/Lumps/DisplacementVertices.cs b/LibBSP/Source/Structs/BSP/Lumps/DisplacementVertices.cs index 47c02bc..5926091 100644 --- a/LibBSP/Source/Structs/BSP/Lumps/DisplacementVertices.cs +++ b/LibBSP/Source/Structs/BSP/Lumps/DisplacementVertices.cs @@ -12,8 +12,9 @@ namespace LibBSP { /// /// Array of bytes to parse. /// Format identifier. + /// The version of this lump. /// was null. - 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)); } } diff --git a/LibBSP/Source/Structs/BSP/Lumps/GameLump.cs b/LibBSP/Source/Structs/BSP/Lumps/GameLump.cs index d7ca61c..71a2fcf 100644 --- a/LibBSP/Source/Structs/BSP/Lumps/GameLump.cs +++ b/LibBSP/Source/Structs/BSP/Lumps/GameLump.cs @@ -46,9 +46,10 @@ namespace LibBSP { /// /// The data to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A new object. /// This is only here for consistency with the other lump structures. - 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); } /// diff --git a/LibBSP/Source/Structs/BSP/Lumps/StaticProps.cs b/LibBSP/Source/Structs/BSP/Lumps/StaticProps.cs index ba8c1b2..220208e 100644 --- a/LibBSP/Source/Structs/BSP/Lumps/StaticProps.cs +++ b/LibBSP/Source/Structs/BSP/Lumps/StaticProps.cs @@ -16,7 +16,7 @@ namespace LibBSP { /// Format identifier. /// Version of static prop lump this is. /// was null. - public StaticProps(byte[] data, MapType type, int version) { + public StaticProps(byte[] data, MapType type, int version = 0) { if (data == null) { throw new ArgumentNullException(); } diff --git a/LibBSP/Source/Structs/BSP/Lumps/Textures.cs b/LibBSP/Source/Structs/BSP/Lumps/Textures.cs index 4eb1e2b..1609f4c 100644 --- a/LibBSP/Source/Structs/BSP/Lumps/Textures.cs +++ b/LibBSP/Source/Structs/BSP/Lumps/Textures.cs @@ -13,9 +13,10 @@ namespace LibBSP { /// /// The data to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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)); } } } diff --git a/LibBSP/Source/Structs/BSP/Model.cs b/LibBSP/Source/Structs/BSP/Model.cs index afe29b0..87dbf00 100644 --- a/LibBSP/Source/Structs/BSP/Model.cs +++ b/LibBSP/Source/Structs/BSP/Model.cs @@ -28,9 +28,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; diff --git a/LibBSP/Source/Structs/BSP/Node.cs b/LibBSP/Source/Structs/BSP/Node.cs index 3842982..52ae8a1 100644 --- a/LibBSP/Source/Structs/BSP/Node.cs +++ b/LibBSP/Source/Structs/BSP/Node.cs @@ -17,9 +17,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; diff --git a/LibBSP/Source/Structs/BSP/StaticProp.cs b/LibBSP/Source/Structs/BSP/StaticProp.cs index 2339365..bda4768 100644 --- a/LibBSP/Source/Structs/BSP/StaticProp.cs +++ b/LibBSP/Source/Structs/BSP/StaticProp.cs @@ -36,7 +36,7 @@ namespace LibBSP { /// The version of static prop lump this object is a member of. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// The map type. /// The version of the Static Prop lump. /// A object. - 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); } } diff --git a/LibBSP/Source/Structs/BSP/Texture.cs b/LibBSP/Source/Structs/BSP/Texture.cs index 7aa0687..5aea978 100644 --- a/LibBSP/Source/Structs/BSP/Texture.cs +++ b/LibBSP/Source/Structs/BSP/Texture.cs @@ -36,9 +36,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A object. - 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); } /// diff --git a/LibBSP/Source/Structs/BSP/TextureData.cs b/LibBSP/Source/Structs/BSP/TextureData.cs index b3e950c..b51102c 100644 --- a/LibBSP/Source/Structs/BSP/TextureData.cs +++ b/LibBSP/Source/Structs/BSP/TextureData.cs @@ -30,8 +30,9 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/BSP/TextureInfo.cs b/LibBSP/Source/Structs/BSP/TextureInfo.cs index 3b4692b..213c391 100644 --- a/LibBSP/Source/Structs/BSP/TextureInfo.cs +++ b/LibBSP/Source/Structs/BSP/TextureInfo.cs @@ -44,9 +44,10 @@ namespace LibBSP { /// /// byte array to parse. /// The map type. + /// The version of this lump. /// was null. /// This structure is not implemented for the given maptype. - 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 { /// /// The data to parse. /// The map type. + /// The version of this lump. /// A List of objects. /// was null. /// This structure is not implemented for the given maptype. - public static List LumpFactory(byte[] data, MapType type) { + public static List 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; } diff --git a/LibBSP/Source/Structs/Common/Entity.cs b/LibBSP/Source/Structs/Common/Entity.cs index c18a6b7..7951370 100644 --- a/LibBSP/Source/Structs/Common/Entity.cs +++ b/LibBSP/Source/Structs/Common/Entity.cs @@ -156,7 +156,9 @@ namespace LibBSP { /// Initializes a new instance of an , parsing the given byte array into an structure. /// /// Array to parse. - public Entity(byte[] data, MapType type) : this(Encoding.ASCII.GetString(data).Split('\n')) { } + /// The map type. + /// The version of this lump. + public Entity(byte[] data, MapType type, int version = 0) : this(Encoding.ASCII.GetString(data).Split('\n')) { } /// /// Initializes a new instance of an with the given classname. @@ -521,8 +523,9 @@ namespace LibBSP { /// /// The data to parse. /// The map type. + /// The version of this lump. /// An object, which is a List of s. - public static Entities LumpFactory(byte[] data, MapType type) { + public static Entities LumpFactory(byte[] data, MapType type, int version = 0) { return new Entities(data, type); } diff --git a/LibBSP/Source/Structs/Common/Lumps/Entities.cs b/LibBSP/Source/Structs/Common/Lumps/Entities.cs index cb2818b..e03ffee 100644 --- a/LibBSP/Source/Structs/Common/Lumps/Entities.cs +++ b/LibBSP/Source/Structs/Common/Lumps/Entities.cs @@ -37,7 +37,8 @@ namespace LibBSP { /// /// Bytes read from a file. /// The of the source map. - public Entities(byte[] data, MapType type) : base() { + /// The version of this lump. + 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; diff --git a/LibBSP/Source/Util/BSPReader.cs b/LibBSP/Source/Util/BSPReader.cs index 45e2448..8221dbf 100644 --- a/LibBSP/Source/Util/BSPReader.cs +++ b/LibBSP/Source/Util/BSPReader.cs @@ -9,9 +9,7 @@ namespace LibBSP { /// public class BSPReader { private FileInfo bspFile; - private FileStream stream; - private BinaryReader binaryReader; - private Dictionary lumpFiles = null; + private Dictionary 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); } } /// - /// Reads this lump in the map. + /// Gets the information for lump "" for this BSP file when reading it as "". /// - /// The index of the lump to get. - /// The version of BSP this is. - /// Array of bytes read from the BSP file. - public byte[] ReadLumpNum(int index, MapType version) { + /// The numerical index of this lump. + /// The type of BSP to interpret the file as. + /// A object containing information about the lump. + /// "" is less than zero, or greater than the number of lumps allowed by "". + 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]; + } + + /// + /// Gets the lump information at offset "" for this BSP file when reading it as "". + /// + /// The offset of the lump's information. + /// The type of BSP to interpret the file as. + /// A object containing information about the lump. + 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 + }; + } + + /// + /// Reads the lump in the BSP file using the information in "". + /// + /// The object representing the lump's information. + /// A byte array containing the data from the file for the lump at the offset with the length from "". + 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; } /// /// Loads any lump files associated with the BSP. /// private void LoadLumpFiles() { - lumpFiles = new Dictionary(); + lumpFiles = new Dictionary(); // Scan the BSP's directory for lump files DirectoryInfo dir = bspFile.Directory; List 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(); + } } } /// - /// Returns the data contained in the lump file for the lump . - /// - /// Index of the lump to get data from the lump file. - /// The lump data in the lump file. - 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; - } - - /// - /// Returns the lump referenced by the offset/length pair at the specified , - /// read as two Int32. - /// - /// The byte offset for the offset/length pair. - /// The version of BSP this is. - /// Array of bytes read from the BSP file. - 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); - } - - /// - /// Reads the lump bytes long at in the file. - /// - /// Offset to start reading from. - /// Length of the lump to read. - /// The version of BSP this is. - /// Array of bytes read from the BSP file. - 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; - } - - /// - /// Xors the byte array with the localls stored key byte array, starting at a certain in the key. + /// Xors the byte array with the locally stored key byte array, starting at a certain in the key. /// /// The byte array to Xor. /// The index in the key byte array to start reading from. @@ -250,213 +300,216 @@ namespace LibBSP { /// The of this BSP, if it could not be determined. 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; } - - /// - /// Disposes of the FileStream and releases the handle to the File. - /// - public void Close() { - stream.Dispose(); - } } }