Piero V.

A note about Call Of Duty 1/UO BSP maps

Call Of Duty 1 and its expansion, United Offensive, are based on the Quake III engine, therefore they share some of its features, like a Binary Space Partitioning to describe maps, even though the format isn’t compatible.

An article copyrighted 2000 describes very well the BSP format of Quake 3. It’s interesting, because it’s the very base to understand CoD, too, at least its devision in lumps and some of them.

An article on “Mappers United Wiki” depicts the lumps of CoD 1 maps and the size of single lump, but they aren’t explained. Luckily in the same site there’s an article on CoD 2, and most of the structs are equal.

So I’ve tried to render them, but I’ve only managed to build the geometry of maps.

First of all - they use a Z-Up coordinate system, like Blender, but contrary to many rendering systems.

Second thing is that units are very big, they are something like decimeters.

Thirs - everything is in little endian.

That said, let’t analyze some important structs.

Header and lumps directory

The header of CoD is equal to Quake: first 4 bytes are the “IBSP” word, uppercase.

Then there’s an unsigned 32 bit integer, which is the format version. CoD 1/UO is 59, whereas CoD 2 is 4 and Quake is 46.

After those 8 bytes, there is the lump directory, which is an array of these structs:

struct LumpDirentry {
	unsigned int size;
	unsigned int offset;
};

The length of the array should depend on only on the game. If a lump isn’t on the map, it’s reported on the directory with size 0, since the lump id which a struct refers to is its index on the array. However the array always ends with 8 zero bytes.

The offset is relative to the beginning of the file, and size is the full size of the lumps in bytes, which is not the number of that kind of lump.

Note that Quake 3 used to have offset before size.

DrawVerts or Vertices (lump #7)

struct DrawVerts {
	float position[3];
	float normal[3];

	unsigned char rgba[4];

	float texcoords[2];
	float lightmap[2];
};

This is very similar to lump #10 of Quake, but it differs in order. In fact this is Direct3D order.

TriangleSoups (lump #6)

struct TriangleSoups {
	unsigned short material_id;

	unsigned short draw_order;

	unsigned int vertex_offset;		// Referred to DrawVerts
	unsigned short vertex_length;

	unsigned short triangle_length;
	unsigned int triangle_offset;	// Referred to MeshVerts
};

This struct in part reminds face struct of Quake, but it lacks some fields, like type, or effects. This is due to the fact that CoD uses Direct3D for patches, again, and that it doesn’t use some features, like effects.

CoD 2 has a very similar struct, but it adds 6 floats whose meaning is still unknown.

MeshVerts (lump #8)

Actually this isn’t a struct, but just an array of unsigned 16 bit integers.

They are the indices to create triangles for faces. The indices are relative to each face and reset for each one, which means that, being face a TriangleSoups struct and x an unsigned integer, a x index is vertices[face.vertex_offset + x].

A simple code

Therefore a C++(11) pseudo-code to create the map geometry is something like that:

// load data to RAM
loadVertices();
loadFaces();

// Iterate faces
for (TriangleSoups &f in faces) {
	beginSection(); // It will reset index counter, like Ogre::ManualObject::begin
	setMaterial(f.material_id);

	unsigned int vertStop = f.vertex_offset + f.vertex_length;
	for (unsigned int i = f.vertex_offset; i < vertStop; i++) {
		uploadVert(vertices[i]); // Upload vertex to GPU
	}

	for (int i = 0; i < f.triangle_length; i++) {
		pushIndex(meshverts[f.triangle_offset + i]); // push back a new index
	}

	endSection();
}

That’s it. No binary trees are needed, since modern render engines don’t need them anymore. So we don’t care of planes, nodes and leaves.

That works for CoD 2, too, just change lump indices.

Materials

This code can render a geometry and there’s a hint to materials, which is the real problem with those games.

CoD 1 is very simple: materials are always a texture with the same name, you only have to guess the extension. Texture coordinates are the problem: they aren’t standard UV coordinates, but they are something else, which the game can decode and create UV from.

On the other hand CoD 2 uses UV coords, but materials aren’t just a texture file name. They are binary files. I’ve tried to reverse them. There are many offset to null-ended strings. They are the type of texture (colorMap for the main texture, other maps for effects…). Images are in IWI format, which is just another formats with a different header (usually DDS with DXT1, DXT3 or DXT5).

Lighting and entities

There are at least some other useful lumps.

One is ligthmaps: without it, the map is pretty flat.

struct Lightmaps {
	unsigned char map[128][128][3]; // 128x128 RGB image
};

However the link between these lumps and map is not clear yet. LightIndices (lump #19) should do the work.

Another important lumps is entities, which is an embedded ASCII text that contains serveral properties.

It contains other models to be loaded and some other information, like swpawn points or lamps.

Models are in XMODEL proprietary format. It contains bones, too! Some converters exist, but their developers haven’t released any information of the format.

Physics and collisions

Remaining lumps are needed by the engine to compute collisions.

Their structure is unknown, but I think convex hulls can do the work, but I haven’t tested it yet.