BSP (Source)/Game-Specific
This page documents some game-specific differences in the BSP file format, compared to the format used for the traditional engine branches.
Contents
Alien Swarm / Portal 2
Both games use a slightly different dbrushside_t
structure:
struct dbrushside_t
{
unsigned short planenum; // facing out of the leaf
short texinfo;
short dispinfo; // displacement info (BSPVERSION 7)
byte bevel; // is the side a bevel plane? (BSPVERSION 7)
byte thin; // is a thin side?
};
Dark Messiah of Might and Magic
Dark Messiah maps have a strange version number, it is possibly split into two shorts:
struct dheader_dm_t
{
int ident; // BSP file identifier
short version; // BSP file version (20)
short unknown; // always 4?
lump_t lumps[HEADER_LUMPS]; // lump directory array
int mapRevision; // the map's revision (iteration, version) number
};
The game lump header has an extra unknown integer:
struct dgamelumpheader_t
{
int lumpCount; // number of game lumps
int unknown;
dgamelump_t gamelump[lumpCount];
};
The dgamelump_t structure also adds an integer value for unknown purposes.
struct dgamelump_dm_t
{
int id; // gamelump ID
unsigned short flags; // flags
unsigned short version; // gamelump version
int fileofs; // offset to this gamelump
int filelen; // length
int unknown;
};
Static props use a modified StaticPropLumpV6_t
structure, which contains additional 72 bytes for unknown purposes at the end:
struct StaticPropLumpV6_dm_t
{
Vector m_Origin;
QAngle m_Angles;
unsigned short m_PropType;
unsigned short m_FirstLeaf;
unsigned short m_LeafCount;
unsigned char m_Solid;
unsigned char m_Flags;
int m_Skin;
float m_FadeMinDist;
float m_FadeMaxDist;
Vector m_LightingOrigin;
float m_flForcedFadeScale;
unsigned short m_nMinDXLevel;
unsigned short m_nMaxDXLevel;
byte m_unknown[72];
};
Furthermore, the DirectX levels seem to have different ranges.
Models using dmodel_t
have one additional integer field between origin
and headnode
:
struct dmodel_dm_t
{
Vector mins, maxs; // bounding box
Vector origin; // for sounds or lights
int unknown; // always 0?
int headnode; // index into node array
int firstface, numfaces; // index into face array
};
Texinfos using texinfo_t
have additional 24 bytes between textureVecs
and lightmapVecs
:
struct texinfo_dm_t
{
float textureVecs[2][4]; // [s/t][xyz offset]
byte unknown[24];
float lightmapVecs[2][4]; // [s/t][xyz offset] - length is in units of texels/area
int flags; // miptex flags overrides
int texdata; // Pointer to texture name, size, etc.
}
Left 4 Dead 2 / Contagion
The fields in lump_t have a different order:
struct lump_t
{
int version; // lump format version
int fileofs; // offset into file (bytes)
int filelen; // length of lump(bytes)
char fourCC[4]; // lump ident code
};
The Ship / Bloody Good Time
Static props use a modified StaticPropLumpV5_t
structure, which contains null-padded target name strings.
struct StaticPropLumpV5_ship_t
{
Vector m_Origin;
QAngle m_Angles;
unsigned short m_PropType;
unsigned short m_FirstLeaf;
unsigned short m_LeafCount;
unsigned char m_Solid;
unsigned char m_Flags;
int m_Skin;
float m_FadeMinDist;
float m_FadeMaxDist;
Vector m_LightingOrigin;
float m_flForcedFadeScale;
char m_TargetName[128];
};
Vampire The Masquerade - Bloodlines
Version 17 BSP files contain a substantially modified dface_t
structure. The known elements are:
// MAXLIGHTMAPS changed from 4 to 8
// 4 lightstyle for day + 4 for night = 8 ( nightime lightmapping system )
struct dface_bsp17_t
{
colorRGBExp32 m_AvgLightColor[MAXLIGHTMAPS]; // For computing lighting information
unsigned short planenum;
byte side; // faces opposite to the node's plane direction
byte onNode; // 1 of on node, 0 if in leaf
int firstedge; // we must support > 64k edges
short numedges;
short texinfo;
short dispinfo;
short surfaceFogVolumeID;
byte styles[MAXLIGHTMAPS]; // lighting info
byte day[MAXLIGHTMAPS]; // Nightime lightmapping system
byte night[MAXLIGHTMAPS]; // Nightime lightmapping system
int lightofs; // start of [numstyles*surfsize] samples
float area;
int m_LightmapTextureMinsInLuxels[2];
int m_LightmapTextureSizeInLuxels[2];
int origFace; // reference the original face this face was derived from
unsigned int smoothingGroups;
};
The extra data seems to be related to lighting of the face, and makes the length of the structure 104 bytes per face. Both the face lump and the original face lump in version 17 files use this structure.
Vindictus
Vindictus generally uses a lot more integers in place of shorts for its map data structures.
The game uses a different dgamelump_t format:
struct dgamelump_t
{
int id; // gamelump ID
int flags; // flags
int version; // gamelump version
int fileofs; // offset to this gamelump
int filelen; // length
};
Nodes and Leaves are also different, mainly changing to use ints instead of shorts:
struct dnode_t
{
int planenum; // index into plane array
int children[2]; // negative numbers are -(leafs + 1), not nodes
int mins[3]; // for frustum culling
int maxs[3];
int firstface; // index into face array
int numfaces; // counting both sides
int unknown; // seems to always be 0
};
The Leaves structure, among other things, does away with the area field, and flags has its own integer field.
struct dleaf_t
{
int contents; // OR of all brushes (not needed?)
int cluster; // cluster this leaf is in
int flags; // flags
int mins[3]; // for frustum culling
int maxs[3];
unsigned int firstleafface; // index into leaffaces
unsigned int numleaffaces;
unsigned int firstleafbrush; // index into leafbrushes
unsigned int numleafbrushes;
int leafWaterDataID; // -1 for not in water
};
Faces use one of two different structures, 72 bytes or 76 bytes, depending on the lump version in the file header. Once again, the biggest difference is changing from shorts to ints:
struct dface_t
{
unsigned int planenum; // the plane number
byte side; // faces opposite to the node's plane direction
byte onNode; // 1 of on node, 0 if in leaf
short unknown0; // always 0?
int firstedge; // index into surfedges
int numedges; // number of surfedges
int texinfo; // texture info
int dispinfo; // displacement info
int surfaceFogVolumeID; // ?
// Comment this unknown for v1
int unknown1; // v2 Faces only. Always negative?
byte styles[4]; // switchable lighting info
int lightofs; // offset into lightmap lump
float area; // face area in units^2
int LightmapTextureMinsInLuxels[2]; // texture lighting info
int LightmapTextureSizeInLuxels[2]; // texture lighting info
unsigned int origFace; // original face this was split from
unsigned int numPrims; // primitives
unsigned int firstPrimID;
unsigned int smoothingGroups; // lightmap smoothing group
};
Brush sides have an identical format, except using ints instead of shorts:
struct dbrushside_t
{
unsigned int planenum;
int texinfo;
int dispinfo;
int bevel;
};
Edges are also identical except for using ints:
struct dedge_t
{
unsigned int v[2];
};
With 232 bytes, the displacement info struct of Vindictus is notably bigger than the conventional 176 byte struct in other Source engine games. This is mostly because of CDispNeighbor and CDispCornerNeighbors using ints instead of shorts:
struct ddispinfo_t
{
Vector startPosition; // start position used for orientation
int DispVertStart; // Index into LUMP_DISP_VERTS.
int DispTriStart; // Index into LUMP_DISP_TRIS.
int power; // power - indicates size of surface (2^power 1)
float smoothingAngle; // lighting smoothing angle
int unknown0;
int contents; // surface contents
unsigned int MapFace; // Which map face this displacement comes from.
int LightmapAlphaStart; // Index into ddisplightmapalpha.
int LightmapSamplePositionStart; // Index into LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS.
CDispNeighbor EdgeNeighbors[4]; // Indexed by NEIGHBOREDGE_ defines.
CDispCornerNeighbors CornerNeighbors[4]; // Indexed by CORNER_ defines.
unsigned int AllowedVerts[10]; // active verticies
};
Vindictus' Static Props v5 is identical to vanilla Source engine. However, Vindictus maps may instead use a v6 Static Props lump that is different from the normal v6 Props. The StaticPropLump_t struct remains the same (like v5 Static Props) but there is an additional struct inserted between the StaticPropLeafLump_t array and the StaticPropLump_t array, perhaps providing additional prop scaling information. As with the other arrays, this struct begins with an int declaring how many elements there are. There seems to always be the same amount of this struct as there are StaticPropLump_t.
struct StaticPropScales_t
{
int staticProp; // Index into the StaticPropLump_t array
Vector scale; // Speculative, is this really scaling?
};
The overlay structure also follows the change from short to int:
struct doverlay_t
{
int Id;
int TexInfo;
unsigned int FaceCountAndRenderOrder;
int Ofaces[OVERLAY_BSP_FACE_COUNT];
float U[2];
float V[2];
Vector UVPoints[4];
Vector Origin;
Vector BasisNormal;
};
So does the areaportal structure:
struct dareaportal_t
{
unsigned int portalKey; // binds the area portal to a func_areaportal entity with the same portalnumber key
unsigned int otherarea; // The area this portal looks into.
unsigned int firstClipPortalVert; // Portal geometry.
unsigned int clipPortalVerts;
int planenum;
};
The LeafFace and LeafBrush lumps also use unsigned ints rather than unsigned shorts.
Titanfall
Titanfall uses a heavily modified Source engine, which also alters the file format for the maps.
The header uses rBSP
(Respawn BSP) instead of VBSP
as identifier and HEADER_LUMPS
was risen from 64 to 128, doubling the maximum amount of lumps. As of the closed beta, the maps use 29 as BSP version.
struct dheader_t
{
int ident; // BSP file identifier
int version; // BSP file version
int mapRevision; // the map's revision (iteration, version) number
int unknown; // always 127?
lump_t lumps[HEADER_LUMPS]; // lump directory array
};
Titanfall maps also make extensive use of the lump file system. Unlike to other Source games, Titanfall lump files are headerless and use their lump ID instead of a continuous number in the file name, which is formatted as following: <BSP file name>.<ID>.bsp_lump
.
Note that the lump ID is a four digit hex string with leading zeros. A lump file with the name mp_angel_city.bsp.0044.bsp_lump
therefore contains the data for lump 0x44 (68 decimal) for the map mp_angel_city.bsp
. These lump files probably also override the lumps defined inside the BSP file like in other Source games.
The entities are stored in different files: inside the BSP file, in the entity lump file and in five .ent files. The entity files start with the header ENTITIES01
and may otherwise be empty.
The lumps in Titanfall maps have changed drastically. Most lumps mandatory for other Source games are empty and unused, such as LUMP_NODES, LUMP_FACES, LUMP_EDGES, LUMP_BRUSHES and LUMP_BRUSHSIDES, so the geometry is now most likely stored in the new lumps above ID 64.