The GSkinMesh (GSM) file format
Last updated: 2010-01-10, current with gsmhacking 3.1.50.
Character and weapon models in SWAT3 are stored in GSkinMesh format, a proprietary format created by Sierra. No documentation or source code for the .gsm file format is availble.
I have been reverse engineering the .gsm file format. Barring any mistakes in my work, I believe I have deciphered all of it.
The GSkinAnimation file format is also documented.
Sections of the .gsm file
The .gsm file is composed of fourteen sections:
- The .gsm file header
- Skeleton and bones information
- Vertex to bone assignments
- Vertex positions
- Blended positions
- Texture co-ordinates
- Effective vertices
- Triangle groups
- Shared triangle lists
- Textures information
- Materials information
- Grips information
- Grippers information
A note on co-ordinates
When exporting weapon models, the GSM exporter normalises the model's co-ordinate system relative to the position of the first bone in the first skeleton. Thus that bone is always at the origin. It does not do this when exporting character meshes.
Each bone's "position" is a vector from its parent in the co-ordinate system defined by the parent's rotation.
It is important to make the distinction between what you think a bone is and what 3D Studio (and hence the GSM) thinks a bone is. When you make a bone in 3D Studio you click the mouse at the location of the first endpoint of the bone, move to where you want the other end to be and click again. 3D Studio then draws a line between the two points.
You probably think that line is a bone. Wrong. The two endpoints are bones (or nodes) in 3D Studio's eyes. That's why you've got "bones" called Rifle_Root and Rifle_Bone01 in your weapon GSM.
This means that every GSM must have at least two bones. And it means using the root bone as the origin for the co-ordinate system is meaningful.
The sections in detail
Throughout this document, sizeof(int) and sizeof(float) are assumed to be 4, sizeof(short) is assumed to be 2 and all hex values are little-endian. Strings are not \0 terminated unless specified, since their lengths are always given. A float is a single precision signed floating point real.
If the above is meaningless to you then you are probably going to get bored very quickly anyway...
The .gsm file header
|Description||Size||Comments or example|
|Magic||16 octets||GSM magic is 0x72 0xF6 0x63 0x1E 0x5E 0x47 0xD2 0x11 0x87 0x99 0x00 0x20 0xAF 0xE6 0x36 0xEE|
|GSM exporter version||char (minor) char (major)||Always 0x05 0x04: version 4.5|
First we have a definition of the skeletons in the .gsm file (or Physiques, if you are a 3D Studio MAX modeller), then the bone data following that. Since the exporter will only let you export one model at a time, and since it crashes if you try to physique a model to more than one skeleton, it is tempting to speculate that all GSM files must have one skeleton, no more, no fewer. Indeed, making that assumption allows us to do some cool stuff very easily. Makes you wonder why they put the skeleton data in there, though.
|Number of skeletons||short||Usually (always?) 0x01 0x00|
|Skeleton ID||short||First one is 0x00 0x00|
|Length of skeleton name||short||Example: 0x07 0x00 for SwatGuy|
|Skeleton name||see above||Example: SwatGuy|
|Number of bones in skeleton||short||0x34 0x00 for SWT, 0x25 0x00 for TAC|
Each bone is defined like this:
|Bone ID||short||First one is 0x00 0x00|
|Bone name length||short||Example: 0x0C 0x00 for Biped Pelvis|
|Bone name||See above||Example: Biped Pelvis|
|Bone link||short||Number of the bone this bone is connected to. 0xFF 0xFF for the root bone|
|Rotation quaternion X component||float||Bone's orientation determined by this quaternion|
|Rotation quaternion Y component||float||Bone's orientation determined by this quaternion|
|Rotation quaternion Z component||float||Bone's orientation determined by this quaternion|
|Rotation quaternion scalar component||float||Bone's orientation determined by this quaternion|
Vertex to bone assignments section
Physiquing a mesh to a skeleton sets up links between vertices and bones. The game knows to move the vertices in a character's leg when his leg bones move because those vertices are mapped to that bone.
|Number of assignments||short|
The GSM format classifies the assignments by groups of vertices, so that for example, "n vertices starting from vertex v map to bone b."
All vertices must be mapped to bones. Not all bones need have vertices mapped to them.
|Bone ID||short||Must match an entry from the bones section|
|First vertex in assignment||short||Must be valid in the range of vertex positions|
|Number of vertices in assignment||short||This number of vertices are mapped to the bone|
Sometimes (with character models) the number of assignments that are actually present is less than the number advertised in the assignment header. In this case, the magic marker 0xEE 0xEE is substituted for the missing assignment(s).
It seems this happens if and only if there are blended positions. In that case, the following information is present.
|Index of first blended position||short|
|Number of blended positions||int||May actually be a short with null padding|
The vertex positions section is simply a list of co-ordinates, with a vertex in the mesh present at each co-ordinate.
|Number of positions||short|
This section describes vertex blending. It is present even if there was no blended position header earlier, though in this case there will be no data stream.
|Number of blended positions||short|
|Length of blended position stream||short|
|Blended position||Only present if number of positions is nonzero (See below)|
Each blended position lists 2 or more vertices (positions) and a weight.
|Number of positions||short|
|Weight||float||Normalised; sum of weights must be 1.0 or 0.0|
These are UVW maps but with no W. The V part is inverted from the value in the 3D Studio material editor.
|Number of co-ordinates||short|
|V||float||-1 x 3D Studio value|
|Number of effective vertices||short|
|Texture co-ordinate ID||short|
|Position ID||short||If greater than number of positions, this refers to a blended position|
|Shared triangle list ID||short|
|Hit detection||short||Hit detection colour (as described in ModHQ FAQ) in RGB15 format.|
A position ID is valid when it is less than the sum of the numbers of positions and blended positions in the mesh. If it is greater than the number of positions, subtract that number to get the blended position index.
To calculate a blended position, scale each the co-ordinate vector for position listed in the blend list by its associated weight and sum the resultant vectors.
A triangle is defined as a triple (v1,v2,v3), where v1, v2 and v3 are entries from the effective vertices section. If you had only one triangle in the mesh, for example, you would have three effective vertices and your one triangle entry would be (0,1,2).
|Number of triangles||short|
|First vertex||short||ID <= number of effective vertices|
|Second vertex||short||ID <= number of effective vertices|
|Third vertex||short||ID <= number of effective vertices|
Triangle groups relate to material groups in 3D Studio. When you set a face to a specific ID you will create a corresponding triangle group entry in the GSM. The material IDs start from 0, though, and so are 1 less than the numbers given in MAX.
Similarly to the vertex to bone assignments section, the triangle groups section gives a triangle ID and a number. The triangle with the given ID and the next <number> triangles all belong to the specified group.
|Number of triangle groups||short|
|Triangle group||See below|
|Material ID||short||1 less than ID given in MAX|
|First triangle||short||From triangles section|
|Number of triangles||short|
Shared triangle lists
|Shared triangle list data stream length||int||Actually a number of shorts, not number of octets (doh!)|
|Number of shared triangle lists||short|
|Shared triangle list||See below|
|Shared triangle list|
Each entry in the shared triangle list is exactly that, a list of triangle numbers:
|Number of triangles||short|
First a count of the textures in the file:
|Number of textures||short||Example: 0x13 0x00 for SWT_ELEM_HIGH (19 textures)|
The textures themselves are defined viz:
|Length of texture name||short||Length of full path name to texture|
|Texture name||See above||Name is terminated with TWO \0 characters (eg <name> 0x00 0x00)|
When you hex edit .gsm files, you are messing with these. Note that there is no texture ID field.
This is where the fun really is. You can alter opacity and stuff to get cool effects like in the Ghost Recon mod.
|Number of materials||short||Usually (though not necessarily) the same as the number of textures.|
The materials names are as defined in the 3D Studio MAX material editor...
|Material name length||short|
|Material name||See above|
|Number of maps||short||Number of textures mapped to this material|
|Map||See below (if number of maps is zero this is of course not present)|
|Map type?||short||Not sure about this|
|Diffuse map red value||float||This is the material editor value|
|Diffuse map green value||float||This is the material editor value|
|Diffuse map blue value||float||This is the material editor value|
|Ambient map red value||float||This is the material editor value|
|Ambient map green value||float||This is the material editor value|
|Ambient map blue value||float||This is the material editor value|
|Specularity map red value||float||This is the material editor value|
|Specularity map green value||float||This is the material editor value|
|Specularity map blue value||float||This is the material editor value|
|Opacity falloff||float||This is the material editor value divided by 100 - in/out irrelevant|
|Transparency||float||This is the material editor value divided by 100|
|Two-sided||char||0x01 if material is 2-sided, 0x00 otherwise|
|Self-illumination||float||This is the material editor value divided by 100|
|Shininess||float||This is the material editor value divided by 100|
|Shine strength||float||This is the material editor value divided by 100|
Material maps look like this:
|Map type||short||0x01 = diffuse, 0x09 = transparency etc|
|Texture ID||short||From textures section|
When characters "hold" weapons the game works out where to place the object relative to the character by aligning grips with grippers. Grips are the helper objects attached to objects. Grippers are the helpers attached to characters (eg at the hands). Grips and grippers are matched by name.
Weapons must have grips. Characters must have grippers.
|Number of grips||short|
Each grip follows:
|Grip name length||short||Example 0x05 0x00 for Grip1|
|Grip name||See above||Example: Grip1|
|Transformation matrix element (0,0)||float||Defines grip's orientation in space|
|Transformation matrix element (0,1)||float||Defines grip's orientation in space|
|Transformation matrix element (0,2)||float||Defines grip's orientation in space|
|Transformation matrix element (1,0)||float||Defines grip's orientation in space|
|Transformation matrix element (1,1)||float||Defines grip's orientation in space|
|Transformation matrix element (1,2)||float||Defines grip's orientation in space|
|Transformation matrix element (2,0)||float||Defines grip's orientation in space|
|Transformation matrix element (2,1)||float||Defines grip's orientation in space|
|Transformation matrix element (2,2)||float||Defines grip's orientation in space|
|Bone ID that grip is attached to||short||Example: 0x1C 0x00 is Biped R Hand for the SWT mesh, so this gripper is the trigger hand gripper|
Note that there is no grip ID, just the ID of the bone it is attached to. You must crosscheck with the bones section to figure out the correct value.
The actual gripper data stream is identical to that of grips. Only the context is different.
Send any comments to firstname.lastname@example.org.