Gamestudio file formats

Gamestudio uses several proprietary file formats that are similar to popular formats used by other engines, such as the engines by id Software. There are five Gamestudio-specific file formats: WMP for a level, WAD for level textures, WMB for a compiled level with BSP tree and lightmaps, MDL for animated models, and HMP for terrain.

A WMP file is created by the level editor WED. It contains the following elements in a hierarchical tree structure that follows the group structure when designing a level:

WAD files can be created by WED or by third party editors. They contain textures in 8-bit, 16-bit, 24-bit, or compressed DDS format, plus their mipmaps.

WMB files are created by WED's Map Compiler. They contain the content of the WMP file with included textures, BSP tree data, and lightmaps.

MDL and HMP files are created by the model editor MED or by external tools. They come in two flavors. MDL5 and HMP5 is the old format used by all Gamestudio versions. MDL7 and HMP7 is the new format used by Gamestudio/A6 and A7. The old MDL5 and HMP5 formats are described below. The new MDL7 and HMP7 formats can be read and written through the MDL SDK available on the Gamestudio Download page.

The MDL5 model format

Despite the engine uses model files with .MDL extension, it's internal MDL5 format differs from the Quake MDL format. The MDL5 format is used by many Gamestudio Model exporters for popular editors like 3D Studio MAX, Maya, or Milkshape. It can be imported in the Acknex engines and the MED model editor, though MED saves its models in the newer MDL7 format that supports materials and bones. The MDL7 format is not publicly documented, but an easy-to-use SDK is freely available for filter and converter creation.

A wireframe mesh, made of triangles, gives the general shape of a model. 3D vertices define the position of triangles. For each triangle in the wireframe, there will be a corresponding triangle cut from the skin picture. Or, in other words, for each 3D vertex of a triangle that describes a XYZ position, there will be a corresponding 2D vertex positioned that describes a UV position on the skin picture.
It is not necessary that the triangle in 3D space and the triangle on the skin have the same shape (in fact, it is normally not possible for all triangles), but they should have shapes roughly similar, to limit distortion and aliasing. Several animation frames of a model are just several sets of 3D vertex positions. The 2D vertex positions always remain the same.

A MDL file contains
- A list of skin textures in 8-bit palettized, 16-bit 565 RGB or 16 bit 4444 ARGB format.
- A list of skin vertices, that are just the UV position of vertices on the skin texture.
- A list of triangles, which describe the general shape of the model.
- A list of animation frames. Each frame holds a list of 3D vertices.- A list of bone vertices, which are used for creating the animation frames.

MDL file header

Once the file header is read, all the other model parts can be found just by calculating their position in the file. Here is the format of the .MDL file header:

typedef float vec3[3];


typedef struct {
  char version[4]; // "MDL3", "MDL4", or "MDL5"
  long unused1;  // not used
  vec3 scale;    // 3D position scale factors.
  vec3 offset;   // 3D position offset.
  long unused2;  // not used
  vec3 unused3;  // not used
  long numskins; // number of skin textures
  long skinwidth; // width of skin texture, for MDL3 and MDL4;
  long skinheight; // height of skin texture, for MDL3 and MDL4;
  long numverts; // number of 3d wireframe vertices
  long numtris;  // number of triangles surfaces
  long numframes; // number of frames
  long numskinverts; // number of 2D skin vertices
  long flags;    // always 0
  long unused4;  // not used
} mdl_header;

The size of this header is 0x54 bytes (84).
The MDL3 format was used by the A4 engine, while the MDL4 and MDL5 formats were used by the A5 engine, the latter supporting mipmaps. After the file header follow the skins, the skin vertices, the triangles, the frames, and finally the bones (in future versions).

MDL skin format

The model skins are flat pictures that represent the texture that should be applied on the model. There can be more than one skin. You will find the first skin just after the model header, at offset baseskin = 0x54. There are numskins skins to read. Each of these model skins is either in 8-bit palettized (type == 0), in 16-bit 565 format (type == 2) or 16-bit 4444 format (type == 3). The skin structure in the MDL3 and MDL4 format is:

typedef byte unsigned char;

typedef struct {
  int skintype; // 0 for 8 bit (bpp == 1), 2 for 565 RGB, 3 for 4444 ARGB (bpp == 2)
  byte skin[skinwidth*skinheight*bpp]; // the skin picture
} mdl_skin_t;

In the MDL5 format the skin is a little different, because now mipmaps can be stored and the model skins have not necessarily the same size. If the skin contains mipmaps, 8 is added to the skintype. In that case the 3 additional mipmap images follow immediately after the skin image. The texture width and height must be divisible by 8. 8 bit skins are not possible anymore in combination with mipmaps.
typedef word unsigned short;

typedef struct {
  long skintype; // 2 for 565 RGB, 3 for 4444 ARGB, 10 for 565 mipmapped, 11 for 4444 mipmapped (bpp = 2),
                 // 12 for 888 RGB mipmapped (bpp = 3), 13 for 8888 ARGB mipmapped (bpp = 4)
  long width,height; // size of the texture
  byte skin[bpp*width*height]; // the texture image
  byte skin1[bpp*width/2*height/2]; // the 1st mipmap (if any)
  byte skin2[bpp*width/4*height/4]; // the 2nd mipmap (if any)
  byte skin3[bpp*width/8*height/8]; // the 3rd mipmap (if any)
} mdl5_skin_t;

8 bit skins are a table of bytes, which represent an index in the level palette. If the model is rendered in overlay mode, index 0x00 indicates transparency. 16 bit skins in 565 format are a table of unsigned shorts, which represent a true colour with the upper 5 bits for the red, the middle 6 bits for the green, and the lower 5 bits for the blue component. Green has one bit more because the human eye is more sensitive to green than to other colours. If the model is rendered in overlay mode, colour value 0x0000 indicates transparency. 16 bit alpha channel skins in 4444 format are represented as a table of unsigned shorts with 4 bits for each of the alpha, red, green, and blue component. 32 bit alpha channel skins in 8888 format are represented as a table of unsigned long integers with 4 bytes for each of the alpha, red, green, and blue component. Note that the byte order in that case is Intel order: blue, green, red, alpha.
The size width and heights of skins should be a multiple of 4, to ensure long word alignement. When using mipmaps, they must be a multiple of 8. The skin pictures are usually made of as many pieces as there are independent parts in the model. For instance, for a player, there may be several pieces that defines the body, the face, the hands, and the weapon.



Samurai skin

MDL skin vertices

The list of skin vertices indicates only the position on texture picture, not the 3D position. That's because for a given vertex, the position on skin is constant, while the position in 3D space varies with the animation. The list of skin vertices is made of these structures:

typedef struct
{
  short u; // position, horizontally in range 0..skinwidth-1
  short v; // position, vertically in range 0..skinheight-1
} mdl_uvvert_t;

mdl_uvvert_t skinverts[numskinverts];

u and v are the pixel position on the skin picture. The skin vertices are stored in a list, that is stored at offset basestverts = baseskin + skinsize. skinsize is the sum of the size of all skin pictures. If they are all 8-bit skins, then skinsize = (4 + skinwidth * skinheight) * numskins. If they are 16-bit skins without mipmaps, then skinsize = (4 + skinwidth * skinheight * 2) * numskins.


Samurai skin with uv mapping

MDL mesh triangles

The model wireframe mesh is made of a set of triangle facets, with vertices at the boundaries. Triangles should all be valid triangles, not degenerates (like points or lines). The triangle face must be pointing to the outside of the model. Only vertex indexes are stored in triangles. Here is the structure of triangles:

typedef struct {
  short index_xyz[3]; // Index of 3 3D vertices in range 0..numverts
  short index_uv[3]; // Index of 3 skin vertices in range 0..numskinverts
} mdl_triangle_t;

mdl_triangle_t triangles[numtris];
At offset basetri = baseverts + numskinverts * sizeof(uvvert_t) in the .MDL file you will find the triangle list.

MDL frames

A model contains a set of animation frames, which can be used in relation with the behavior of the modeled entity, so as to display it in various postures (walking, attacking, spreading its guts all over the place, etc). Basically the frame contains of vertex positions and normals. Because models can have ten thousands of vertices and hundreds of animation frames, vertex posistion are packed, and vertex normals are indicated by an index in a fixed table, to save disk and memory space.
Each frame vertex is defined by a 3D position and a normal for each of the 3D vertices in the model. In the MDL3 format, the vertices are always packed as bytes; in the MDL4 format that is used by the A5 engine they can also be packed as words (unsigned shorts). Therefore the MDL4 format allows more precise animation of huge models, and inbetweening with less distortion.

typedef struct {
	byte rawposition[3]; // X,Y,Z coordinate, packed on 0..255
	byte lightnormalindex; // index of the vertex normal
} mdl_trivertxb_t;


typedef struct {
	unsigned short rawposition[3]; // X,Y,Z coordinate, packed on 0..65536
	byte lightnormalindex; // index of the vertex normal
	byte unused;
} mdl_trivertxs_t;

To get the real X coordinate from the packed coordinates, multiply the X coordinate by the X scaling factor, and add the X offset. Both the scaling factor and the offset for all vertices can be found in the mdl_header struct. The formula for calculating the real vertex positions is:
float position[i] = (scale[i] * rawposition[i] ) + offset[i];
The lightnormalindex field is an index to the actual vertex normal vector. This vector is the average of the normal vectors of all the faces that contain this vertex. The normal is necessary to calculate the Gouraud shading of the faces, but actually a crude estimation of the actual vertex normal is sufficient. That's why, to save space and to reduce the number of computations needed, it has been chosen to approximate each vertex normal. The ordinary values of lightnormalindex are comprised between 0 and 161, and directly map into the index of one of the 162 precalculated normal vectors:
float lightnormals[162][3] = {
{-0.525725, 0.000000, 0.850650}, {-0.442863, 0.238856, 0.864188}, {-0.295242, 0.000000, 0.955423},
{-0.309017, 0.500000, 0.809017}, {-0.162460, 0.262866, 0.951056}, {0.000000, 0.000000, 1.000000},
{0.000000, 0.850651, 0.525731}, {-0.147621, 0.716567, 0.681718}, {0.147621, 0.716567, 0.681718},
{0.000000, 0.525731, 0.850651}, {0.309017, 0.500000, 0.809017}, {0.525731, 0.000000, 0.850651},
{0.295242, 0.000000, 0.955423}, {0.442863, 0.238856, 0.864188}, {0.162460, 0.262866, 0.951056},
{-0.681718, 0.147621, 0.716567}, {-0.809017, 0.309017, 0.500000}, {-0.587785, 0.425325, 0.688191},
{-0.850651, 0.525731, 0.000000}, {-0.864188, 0.442863, 0.238856}, {-0.716567, 0.681718, 0.147621},
{-0.688191, 0.587785, 0.425325}, {-0.500000, 0.809017, 0.309017}, {-0.238856, 0.864188, 0.442863},
{-0.425325, 0.688191, 0.587785}, {-0.716567, 0.681718, -0.147621}, {-0.500000, 0.809017, -0.309017},
{-0.525731, 0.850651, 0.000000}, {0.000000, 0.850651, -0.525731}, {-0.238856, 0.864188, -0.442863},
{0.000000, 0.955423, -0.295242}, {-0.262866, 0.951056, -0.162460}, {0.000000, 1.000000, 0.000000},
{0.000000, 0.955423, 0.295242}, {-0.262866, 0.951056, 0.162460}, {0.238856, 0.864188, 0.442863},
{0.262866, 0.951056, 0.162460}, {0.500000, 0.809017, 0.309017}, {0.238856, 0.864188, -0.442863},
{0.262866, 0.951056, -0.162460}, {0.500000, 0.809017, -0.309017}, {0.850651, 0.525731, 0.000000},
{0.716567, 0.681718, 0.147621}, {0.716567, 0.681718, -0.147621}, {0.525731, 0.850651, 0.000000},
{0.425325, 0.688191, 0.587785}, {0.864188, 0.442863, 0.238856}, {0.688191, 0.587785, 0.425325},
{0.809017, 0.309017, 0.500000}, {0.681718, 0.147621, 0.716567}, {0.587785, 0.425325, 0.688191},
{0.955423, 0.295242, 0.000000}, {1.000000, 0.000000, 0.000000}, {0.951056, 0.162460, 0.262866},
{0.850651, -0.525731, 0.000000}, {0.955423, -0.295242, 0.000000}, {0.864188, -0.442863, 0.238856},
{0.951056, -0.162460, 0.262866}, {0.809017, -0.309017, 0.500000}, {0.681718, -0.147621, 0.716567},
{0.850651, 0.000000, 0.525731}, {0.864188, 0.442863, -0.238856}, {0.809017, 0.309017, -0.500000},
{0.951056, 0.162460, -0.262866}, {0.525731, 0.000000, -0.850651}, {0.681718, 0.147621, -0.716567},
{0.681718, -0.147621, -0.716567}, {0.850651, 0.000000, -0.525731}, {0.809017, -0.309017, -0.500000},
{0.864188, -0.442863, -0.238856}, {0.951056, -0.162460, -0.262866}, {0.147621, 0.716567, -0.681718},
{0.309017, 0.500000, -0.809017}, {0.425325, 0.688191, -0.587785}, {0.442863, 0.238856, -0.864188},
{0.587785, 0.425325, -0.688191}, {0.688197, 0.587780, -0.425327}, {-0.147621, 0.716567, -0.681718},
{-0.309017, 0.500000, -0.809017}, {0.000000, 0.525731, -0.850651}, {-0.525731, 0.000000, -0.850651},
{-0.442863, 0.238856, -0.864188}, {-0.295242, 0.000000, -0.955423}, {-0.162460, 0.262866, -0.951056},
{0.000000, 0.000000, -1.000000}, {0.295242, 0.000000, -0.955423}, {0.162460, 0.262866, -0.951056},
{-0.442863,-0.238856, -0.864188}, {-0.309017,-0.500000, -0.809017}, {-0.162460, -0.262866, -0.951056},
{0.000000, -0.850651, -0.525731}, {-0.147621, -0.716567, -0.681718}, {0.147621, -0.716567, -0.681718},
{0.000000, -0.525731, -0.850651}, {0.309017, -0.500000, -0.809017}, {0.442863, -0.238856, -0.864188},
{0.162460, -0.262866, -0.951056}, {0.238856, -0.864188, -0.442863}, {0.500000, -0.809017, -0.309017},
{0.425325, -0.688191, -0.587785}, {0.716567, -0.681718, -0.147621}, {0.688191, -0.587785, -0.425325},
{0.587785, -0.425325, -0.688191}, {0.000000, -0.955423, -0.295242}, {0.000000, -1.000000, 0.000000},
{0.262866, -0.951056, -0.162460}, {0.000000, -0.850651, 0.525731}, {0.000000, -0.955423, 0.295242},
{0.238856, -0.864188, 0.442863}, {0.262866, -0.951056, 0.162460}, {0.500000, -0.809017, 0.309017},
{0.716567, -0.681718, 0.147621}, {0.525731, -0.850651, 0.000000}, {-0.238856, -0.864188, -0.442863},
{-0.500000, -0.809017, -0.309017}, {-0.262866, -0.951056, -0.162460}, {-0.850651, -0.525731, 0.000000},
{-0.716567, -0.681718, -0.147621}, {-0.716567, -0.681718, 0.147621}, {-0.525731, -0.850651, 0.000000},
{-0.500000, -0.809017, 0.309017}, {-0.238856, -0.864188, 0.442863}, {-0.262866, -0.951056, 0.162460},
{-0.864188, -0.442863, 0.238856}, {-0.809017, -0.309017, 0.500000}, {-0.688191, -0.587785, 0.425325},
{-0.681718, -0.147621, 0.716567}, {-0.442863, -0.238856, 0.864188}, {-0.587785, -0.425325, 0.688191},
{-0.309017, -0.500000, 0.809017}, {-0.147621, -0.716567, 0.681718}, {-0.425325, -0.688191, 0.587785},
{-0.162460, -0.262866, 0.951056}, {0.442863, -0.238856, 0.864188}, {0.162460, -0.262866, 0.951056},
{0.309017, -0.500000, 0.809017}, {0.147621, -0.716567, 0.681718}, {0.000000, -0.525731, 0.850651},
{0.425325, -0.688191, 0.587785}, {0.587785, -0.425325, 0.688191}, {0.688191, -0.587785, 0.425325},
{-0.955423, 0.295242, 0.000000}, {-0.951056, 0.162460, 0.262866}, {-1.000000, 0.000000, 0.000000},
{-0.850651, 0.000000, 0.525731}, {-0.955423, -0.295242, 0.000000}, {-0.951056, -0.162460, 0.262866},
{-0.864188, 0.442863, -0.238856}, {-0.951056, 0.162460, -0.262866}, {-0.809017, 0.309017, -0.500000},
{-0.864188,-0.442863, -0.238856}, {-0.951056,-0.162460, -0.262866}, {-0.809017, -0.309017, -0.500000},
{-0.681718, 0.147621, -0.716567}, {-0.681718, -0.147621, -0.716567}, {-0.850651, 0.000000, -0.525731},
{-0.688191, 0.587785, -0.425325}, {-0.587785, 0.425325, -0.688191}, {-0.425325, 0.688191, -0.587785},
{-0.425325,-0.688191, -0.587785}, {-0.587785,-0.425325, -0.688191}, {-0.688197,-0.587780, -0.425327}
};

A whole frame has the following structure:
typedef struct {
	long type; // 0 for byte-packed positions, and 2 for word-packed positions
	mdl_trivertx_t bboxmin,bboxmax; // bounding box of the frame
	char name[16]; // name of frame, used for animation
	mdl_trivertx_t vertex[numverts]; // array of vertices, either byte or short packed
} mdl_frame_t;


The size of each frame is sizeframe = 20 + (numverts+2) * sizeof(mdl_trivertx_t), while mdl_trivertx_t is either mdl_trivertxb_t or mdl_trivertxs_t, depending on whether the type is 0 or 2. In the MDL3 format the type is always 0. The beginning of the frames can be found in the .MDL file at offset baseframes = basetri + numtris * sizeof(mdl_triangle_t).

The HMP5 terrain format

A terrain is basically a rectangular mesh of height values with one or several surface textures. It is a variant of the Gamestudio MDL5 Model format, without all the data structures that are unnecessary for terrain.

HMP file header

Once the file header is read, all the other terrain parts can be found just by calculating their position in the file. Here is the format of the .HMP file header:

typedef float vec3[3];


typedef struct {
	char version[4]; // "HMP4" or "HMP5"; only the newer HMP5 format is described here
	long nu1; // always 2
	vec3 scale; // heightpoint scale factors
	vec3 offset; // heightpoint offset
	long nu6; // not used
	float ftrisize_x; // triangle X size
	float ftrisize_y; // triangle Y size
	float fnumverts_x; // number of mesh coordinates in X direction
	long numskins ; // number of skin textures
	long nu8,nu9; // not used
	long numverts; // total number of mesh coordinates
	long nu10; // not used
	long numframes; // number of frames
	long nu11; // not used
	long flags; // always 0
	long nu12; // not used
} hmp_header;

The size of this header is 0x54 bytes (84).
The "HMP4" format is used by the A5 engine prior to 5.230, while the new "HMP5" format is used by the A5 engine since version 5.230. The number of vertices in the rectangular mesh can be determined by
int numverts_x = (int) fnumverts_x;
int numverts_y = numverts/numverts_x;

After the file header follow the textures and then the array of height values.

HMP texture format

The terrain surface textures are flat pictures. There can be more than one texture. By default, the first texture is the terrain skin, and the second texture is the detail map if it has a different size. Further textures (up to 8) are used for material effects and shaders. You will find the first texture just after the model header, at offset baseskin = 0x54. There are numskins textures to read. The texture and pixel formats are the same as for MDL skins, and are described in detail in the MDL5 format description.

HMP height values

A terrain contains a set of animation frames, which each is a set of height values. Normally only the first frame is used, because terrain does not animate. Each mesh vertex is defined by a height value and a normal.

typedef byte unsigned char;
typedef word unsigned short;
typedef struct {
	word z; // height value, packed on 0..65536
	byte lightnormalindex; // index of the vertex normal
	byte unused; // not used
} hmp_trivertx_t;

To get the real Z coordinate from the packed coordinates, multiply it by the Z scaling factor, and add the Z offset. Both the scaling factor and the offset can be found in the mdl_header struct. Thus the formula for calculating the real height positions is:
float height = (scale[2] * z) + offset[2];

The x/y scale and offset vectors determine the terrains x and y boundaries, like this:

float xmin = offset[0];
float xmax = scale[0] * 65536 + offset[0];
float ymin = offset[1];
float ymax = scale[1] * 65536 + offset[1];

The X and Y position of the vertex results of the number of the vertex in the mesh, and thus must not be stored. The lightnormalindex field is an index to the actual vertex normal vector, just like in the MDL format description. A whole frame has the following structure:

typedef struct {
	long type; // always 2
	mdl_trivertxs_t bboxmin,bboxmax; // bounding box of the frame - see mdl description
	char name[16]; // name of the frame, used for animation
	hmp_trivertx_t height[numverts]; // array of height values
} hmp_frame_t;
A not animated terrain has only one frame, but can have several skin textures, like a MDL. By default, the first skin is used for the terrain texture and the second skin for the detail map. Depending on the terrain material or shader assigned in the Gamestudio script, the final terrain texture can be composed from up to 8 skin textures.
► latest version online