Skip to content

MSH Mesh Format

A .msh file holds the geometry of one model part: a gear piece, a weapon, a map object. It has no textures inside. Textures, shaders and the number of sub-meshes come from a material script (token MTL\0, version 3000) that the client loads first. The file extension of the material script is not confirmed yet.

This page follows the client loader (ioMeshManager::LoadFileInternal in io3DEngine) and the web tools’ reader. Anything marked not confirmed yet was not verified.

All 3D formats (.msh, .skl, .ani) share these rules:

  • Little-endian, no compression.
  • A file starts with a 4-byte token: three letters and a zero byte (MSH\0).
  • A string is an int32 length and then that many raw bytes. There is no terminator. The bytes are CP949 (Korean) text, so keep names plain ASCII.
  • float3 is three 32-bit floats (x, y, z).
Offset Type Field
0 char[4] MSH\0
4 int32 version: 2000, 2001 or 2002
8 int32 mesh type (list below)
12 uint32 vertex component mask (list below)
16 float3 bounding box minimum
28 float3 bounding box maximum
40 float bounding sphere radius
44 int32 sub-mesh count

Then, for each sub-mesh, 16 bytes: minIndex, vertexCount, indexStart, faceCount (four int32). After that comes int32 vertex count.

If the count of sub-meshes differs from the number of materials in the material script, the client writes a “Count Not Match” line to its log and reads only the sub-meshes it knows.

Version Meaning
2000 Normal mesh
2001 Static mesh with vertex colours (token defined in the sources; the loader treats it like 2000)
2002 Like 2000, plus a list of control points after the faces

The values are the MeshType enum order in io3DCommon.h:

Value Name
0 Static
1 Animated (skinned to a skeleton)
2 Lightmap
3 Billboard
4 Normal billboard
5 Animate effect
6 Static with vertex colour

Each block below is present only when its bit is set in the mask. They follow each other in exactly this order, each one holding one entry per vertex:

Order Mask bit Block Size per vertex
1 0x1 position float3 12 bytes
2 0x20 normal float3 12
3 0x1000 tangent float3 12
4 0x2000 binormal float3 12
5 0x40 colour 0 uint32 (D3D colour) 4
6 0x100 UV 0 float2 8
7 0x200 UV 1 (lightmap) float2 8
8 0x8 weights see below 32
9 0x2 position 2 (billboard centre) float3 12

Other bits exist in the enum (0x4 position W, 0x10 indices, 0x80 colour 1, UV 2 and 3) but the loader reads nothing for them. 0x10 always goes together with 0x8 and has no block of its own.

  1. int32 bone count, then that many strings: the bone names the vertices refer to.
  2. Per vertex: four weights (float) and four bone indices (float, holding whole numbers). The indices point into the bone name list above, not into the skeleton.

The client stops at the first weight that is zero or less, so put the biggest weight first and zeros last. A vertex can use up to four bones.

The names must exist in the skeleton (.skl) that the model config names. See SKL Skeleton Format.

After the vertex blocks: int32 face count, then faceCount × 3 indices as unsigned 16-bit numbers (WORD). So a mesh cannot have more than 65 536 vertices. Each sub-mesh uses faceCount faces starting at indexStart.

The web tools’ reader also accepts 32-bit indices when the file size only fits that width. The 2014, 2019 and 2021 client loaders read 16 bits only (m_pFace is an array of WORD).

If the version is 2002, the file continues after the faces with:

Type Field
int32 control point count

and for each control point:

Type Field
string type and index, for example Type_Index
string linked bone name
string extra info
float3 position

The client uses control points as attach points (weapon grips, effect spots). A type and index pair must be unique inside the mesh. A repeat is dropped and logged.

The mesh loader (ioMeshManager.cpp), ioFileTokenDefine.h and the format constants are byte-identical in the 2014, 2019 and 2021 sources. A mesh from one client loads in the others as far as the format goes. Material scripts and bone names may still differ per client.

Tool Use
Models Reads .msh: textures, wireframe, bones, control points, bounding box
Heroes, Costumes Read meshes for the hero and gear preview. Costumes builds a new .msh (version 2000, 16-bit faces, box and radius computed) from an import
Models (edit control points) Writes the geometry bytes back unchanged and rewrites only the control point list. A version 2000 or 2001 file becomes 2002
  • The exact use of mesh types 2 (lightmap) and 5 (animate effect) in the shipped files.
  • The material script (token MTL\0, version 3000) is only mentioned here; its layout has no page yet.
  • Whether any shipped file uses version 2001.