GBX
Contents |
[edit] General information
TrackMania gamebox files (*.gbx) are generic container files that can contain everything from configuration to textures to track definitions. They consist of a header section and a data section.
In old versions of TrackMania they used to be text files - nowadays they are binary files. Integers are stored in little endian order. The data section is often compressed (using LZO).
[edit] Engines, classes, chunks
A .gbx file more specifically stores the serialization of one or more class instances. There is one main instance, and optionally a number of auxiliary instances.
The serializable classes are organized into 16 engines. Each class is also subdivided into chunks. A class is then not serialized in one go, but rather as a series of chunks. This allows Nadeo to easily extend classes in new TrackMania versions: instead of having to define a new class they can simply add more chunks to an existing one, and have older versions ignore these new chunk types.
The data in a gbx file follows the pattern <chunk ID> <chunk data>. A chunk ID is a 32-bit number that identifies the engine, the class, and the chunk in that class. If you for example see the bytes 07 30 04 03 in the file, that would correspond to the integer 0x03043007, and be interpreted as follows:
engine class chunk 03 043 007
All engines and classes are named; in this case, engine 3 is the Game engine, and class 043 in that engine is CGameCtnChallenge. Chunks do not have names.
Apart from chunk ID's there are also class ID's, which are just like chunk ID's except the chunk index part is ignored (and 0). For a complete overview of engines and classes, see Class ID's.
[edit] Header
The header contains things like compression information, the class ID of the main class instance, and a few chunks of the main class that serve as meta information (e.g. the thumbnail of a challenge).
- byte[3] magic: "GBX"
- int16 version: currently 6
- byte storageSettings[4]:
- 'B' or 'T': Binary or Text (always B, text is for backwards compatibility)
- 'U' or 'C': header Uncompressed or Compressed (typically U)
- 'U' or 'C': data Uncompressed or Compressed (typically C)
- 'R' or 'E': unknown purpose (typically R)
- int32 classID (class ID of main class instance)
- int32 headerSize
- byte[headerSize]:
- int32 numHeaderChunks
- HeaderEntry[numHeaderChunks]
- int32 chunkID
- int32 chunkSize (may have bit 31 set. This indicates a "heavy" header chunk which is skipped while scanning gbx files on game startup)
- concatenated data of header chunks
[edit] Data
- int32 numNodes: the total number of class instances related to this gbx file. This includes the main instance, any local auxiliary instances, and any referenced external nodes/files. An internal list will be allocated with this number of entries; the main instance is at index 0.
- int32 numExternalNodes: the number of external nodes and files that this .gbx references. These come from other files located in the .gbx's .pak file. The references to these will be placed in the same list as the local nodes (see above). Both raw files (e.g. textures) and .gbx main instances can be referenced.
- if numExternalNodes > 0:
- int32 ancestorLevel: how many folder levels to go up in the .pak folder hierarchy to reach the base folder from which files will be referenced.
- int32 numSubFolders
- Folder folders[numSubFolders]
- string name
- int32 numSubFolders
- Folder folders[numSubFolders]
- ExternalNode externals[numExternalNodes]
- int32 flags
- if (flags & 4) == 0:
- string fileName
- else:
- int32 fileIndex
- int32 nodeIndex (the index in the local node list where this external reference will be stored)
- int32 useFile (0 or 1, whether to use the file itself or the node that was loaded from the file)
- if (flags & 4) == 0:
- int32 folderIndex: the depth-first index of the folder which the file is in. 0 means the base folder itself.
- if data is compressed:
- int32 uncompressedSize
- int32 compressedSize
- byte data[compressedSize] (compressed with regular LZO)
- else:
- byte data[]
[edit] Reading the data
The data section contains further chunks of the main class instance, and may also contain auxiliary class instances. Reading the data section is started by creating an in-memory instance of the class corresponding to the main class ID (instances are called nodes internally), and calling ReadNode on it:
ReadNode()
{
while(true)
{
chunkID = ReadUInt32();
if(chunkID == 0xFACADE01)
return;
chunkFlags = GetChunkFlags(chunkID);
if(chunkFlags & 0x10)
{
skip = ReadUInt32();
chunkDataSize = ReadUInt32();
}
ReadChunk(chunkID);
}
}
GetChunkFlags() doesn't read anything from the file; it provides loading flags for the specified chunk ID. The only important flag is whether or not the chunk is "skippable". If it is, the chunk ID is followed by an int32 0x50494B53 ("SKIP", shows up as "PIKS" in the file due to little endian ordering) and an int32 specifying the size of the chunk data. This allows older versions of TrackMania that don't know how to parse this chunk ID to skip over the chunk data and go to the next chunk. If the chunk is not skippable, the chunk data follows immediately after the chunk ID.
A dummy chunk ID of 0xFACADE01 signifies the end of the chunk list for the current class.
Chunk data is not self-describing; the program itself has to know how to read each one. In fact, if your program doesn't know a specific chunk ID and the chunk is not skippable, you can't even tell how long the chunk is.
We will first describe a number of "primitives" that are used when reading and writing chunks. Then we will describe the contents of a number of common chunks.
[edit] Primitives
- int8, int16, int32, int64, int128, float: regular little endian encoding
- vec2D:
- float x
- float y
- vec3D:
- float x
- float y
- float z
- color:
- float r
- float g
- float b
- string:
- int32 length
- byte chars[length] (ascii, not zero-terminated)
- lookbackstring: a form of compression which allows to avoid repeating the same string multiple times. Everytime a new string is encountered, it is added to a string list, and from then on this list entry is referenced instead of repeating the string another time.
- if this is the first lookback string encountered:
- int32 version (currently 3).
- int32 index: bit 31 and 30 define the string type. If both bits are 0, the index is a number (external reference). The actual index is bits 0-29. If it is 0, a new string follows (and will be added to the string list). If it is greater than one, use the string at stringlist [index - 1]. A value of -1 means there is no data provided (unassigned).
- if (index & 0x3FFFFFFF) is 0:
- string newString. Add to the string list.
- if this is the first lookback string encountered:
Note: the lookback string state is reset after each header chunk. The string list is cleared completely, and the next lookback string will again trigger the version number.
- fileref: path to an external file, e.g. a skin.
- byte version (currently 2)
- string filePath
- if file.length > 0 && version >= 1:
- string unknown
- meta: contains meta information like the track environment, time of day, and author.
- lookbackstring field1
- lookbackstring field2
- lookbackstring author
- noderef: a reference to an auxiliary class instance.
- int32 index. if this is -1, the node reference is empty (null).
- if the index is >= 0 and the node at the index has not been read yet:
- int32 classID: instantiate a new node for this class ID and store it in the node list at the specified index
- ReadNode()
[edit] Class descriptions
[edit] CGameCtnChallenge (03 043 000)
03043002
byte version
int32 0
int32 bronzeTime (ms)
int32 silverTime (ms)
int32 goldTime (ms)
int32 authorTime (ms)
if version >= 4:
int32 price (coppers)
if version >= 6:
int32 multilap (0/1)
int32 trackType (0: Race, 1: Platform, 2: Puzzle, 3: Crazy, 4: Shortcut, 5: Stunts, 6: Script)
if version >= 9:
int32 0
if version >= 10:
int32 authorScore
if version >= 11:
int32 createdWithEditorSimple
if version >= 12:
int32 0
if version >= 13:
int32 nbCheckpoints
int32 nbLaps
03043003
byte version
meta (trackUID, environment, author)
string trackName
byte kind (0: (internal)EndMarker, 1: (old)Campaign, 2: (old)Puzzle, 3: (old)Retro, 4: (old)TimeAttack,
5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
if version >= 1:
int32
string password (weak xor encryption, no longer used in newer track files; see 03043029)
if version >= 2:
meta background (timeOfDay, environment, author)
if version >= 3:
vec2D mapOrigin
if version >= 4:
vec2D mapTarget
if version >= 5:
int128
if version >= 9:
int32 0
string mapStyle
int64
byte lightmap
03043004
int32 version
03043005
string xml
The XML block contains everything the 003 header chunk does, except for the password. Unlike the 003 chunk, however, it also contains the list of dependencies for the track (images, mods, music, etc.), as well as the version number of the software that created the track, the actual number of laps, and an optional Mod name.
03043007
int32 haveThumbnail
if haveThumbnail != 0:
int32 thumbSize
"<Thumbnail.jpg>"
byte thumb[thumbSize]
"</Thumbnail.jpg>"
"<Comments>"
string comments
"</Comments>"
03043008
int32 version int32 authorVersion string authorLogin string authorNick string authorZone string authorExtraInfo
0304300D
meta empty
03043011
noderef collectorList
noderef challengeParameters
int32 kind (0: (internal)EndMarker, 1: (old)Campaign, 2: (old)Puzzle, 3: (old)Retro, 4: (old)TimeAttack,
5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
03043014 (skippable)
int32 string password (old style password with weak xor encryption. this chunk is no longer used in newer challenge files, see 03043029)
03043017 (skippable)
int32 numCheckpoints Checkpoint[numCheckpoints]
Checkpoint:
int32 int32 int32
03043018 (skippable)
int32 int32 numLaps
03043019 (skippable)
fileref modPackDesc
0304301C (skippable)
int32 playMode (0: Race, 1: Platform, 2: Puzzle, 3: Crazy, 4: Shortcut, 5: Stunts)
0304301F
meta (trackUID, environment, author)
string trackName
meta (timeOfDay, environment, author)
int32 sizeX
int32 sizeY
int32 sizeZ
int32 needUnlock
int32 flagsAre32Bit
int32 numBlocks
for each block:
lookbackstring blockName
byte rotation (0/1/2/3)
byte x
byte y
byte z
int16/int32 flags
if (flags & 0x8000) != 0: custom block
lookbackstring author
noderef skin
03043021
noderef clipIntro noderef clipGroupInGame noderef clipGroupEndRace
03043022
int32
03043024
fileref customMusicPackDesc
03043025
vec2D mapCoordOrigin vec2D mapCoordTarget
03043026
noderef clipGlobal
03043027
int32 provided
if provided != 0:
byte
vec3D x 3
vec3D
float
float
float
03043028
ReadChunk(0x03043027) string comments
03043029 (skippable)
int128 passwordHash (salted MD5)
int32 CRC32("0x" + hex(passwordHash) + "???" + trackUID)
0304302A
int32
[edit] CGameCtnCollectorList (03 01B 000)
0301B000
int32 num
Item items[num]
meta
int32
[edit] CGameCtnChallengeParameters (03 05B 000)
0305B000 (all fields are ignored)
int32 int32 int32 int32 int32 int32 int32 int32
0305B001
string tip string tip string tip string tip
0305B002 (all fields are ignored)
int32 int32 int32 float float float int32 int32 int32 int32 int32 int32 int32 int32 int32 int32
0305B003 (all fields are ignored)
int32 float int32 int32 int32 int32
0305B004
int32 bronzeTime (ms) int32 silverTime (ms) int32 goldTime (ms) int32 authorTime (ms) int32 ignored
0305B005
int32 x 3 (ignored)
0305B006
int32 num int32 items[count] (ignored)
0305B008
int32 timeLimit (ms) int32 authorScore (ms)
0305B00A (skippable)
int32 (0?) int32 bronzeTime (ms) int32 silverTime (ms) int32 goldTime (ms) int32 authorTime (ms) int32 timeLimit (ms) int32 authorScore (ms)
0305B00D
int32 (-1?)
0305B00E (skippable)
int32 int32 int32
[edit] CGameCtnBlockSkin (03 059 000)
03059000
string text string ignored
03059001
string text fileref packDesc
03059002
string text fileref packDesc fileref parentPackDesc
[edit] CGameCtnReplayRecord (03 093 000)
03093000
int32 version
meta (trackUID, environment, author)
int32 time (ms)
string nickName
if version > 5:
string driverLogin
03093001
string xml
The XML block contains the UID and replay ("best") time like in the header. It also contains the version number of the software that created the replay; optionally the respawns count (can be -1 or larger), the Stunts score, and a validable flag; and in recent versions occasionally two checkpoints fields.
03093002 (header)
int32 version int32 authorVersion string authorLogin string authorNick string authorZone string authorExtraInfo
03093002 (data)
int32 size byte GBX[size], the track the replay was recorded on
03093007 (skippable)
int32
03093014
int32 ignored (0xA) int32 numGhosts noderef ghosts[numGhosts] int32 ignored int32 num int64[numExtras]
03093015
noderef
[edit] CGameGhost (03 03F 005)
0303F005
int32 uncompressedSize
int32 compressedSize
byte compressedData[compressedSize]: (compressed with zlib deflate)
int32 classID
int32 bSkipList2
int32
int32 samplePeriod
int32
int32 size
byte sampleData[size] (samples of position, rotation, speed... of the car during the race)
int32 numSamples
if numSamples > 0:
int32 firstSampleOffset
if numSamples > 1:
int32 sizePerSample
if sizePerSample == -1:
int32 sampleSizes[numSamples - 1]
if bSkipList2 == 0:
int32 num
int32 data[num]
A sample record looks as follows:
vec3D position uint16 angle (0..0xFFFF -> 0..pi) int16 axisHeading (-0x8000..0x7FFF -> -pi..pi) int16 axisPitch (-0x8000..0x7FFF -> -pi/2..pi/2) int16 speed (-> exp(speed/1000); 0x8000 means 0) int8 velocityHeading (-0x80..0x7F -> -pi..pi) int8 velocityPitch (-0x80..0x7F -> -pi/2..pi/2) ... (more unknown data)
The rotation of the car is calculated as a quaternion.
- The real part of the quaternion is calculated as cos(angle) which corresponds to a rotation of 2*angle around the rotation axis.
- The imaginary part of the quaternion (the rotation axis) is calculated as the vector (sin(angle)*cos(axisPitch)*cos(axisHeading), sin(angle)*cos(axisPitch)*sin(axisHeading), sin(angle)*sin(axisPitch)).
You can convert this quaternion to a transform matrix.
The velocity vector (direction and speed of movement) is calculated in a similar way: (speed*cos(velocityPitch)*cos(velocityHeading), speed*cos(velocityPitch)*sin(velocityHeading), speed*sin(velocityPitch)).
[edit] CGameCtnGhost (03 092 000)
CGameCtnGhost is a subclass of CGameGhost. If you encounter an unknown chunk ID while reading a CGameCtnGhost instance, delegate it to CGameGhost.
03092005 (skippable)
int32 raceTime
03092008 (skippable)
int32 numRespawns
03092009 (skippable)
color lightTrailColor
0309200A (skippable)
int32 stuntsScore
0309200B (skippable)
int32 num int64[num]
0309200C
int32 ignored
0309200E
lookbackstring uid
0309200F
string ghostLogin
03092010
lookbackstring
03092012
int32 ignored int128
03092013 (skippable)
int32 int32
03092014 (skippable)
int32
03092015
lookbackstring playerMobilId
03092017 (skippable)
int32 num fileref skinPackDescs[num] string ghostNickname string ghostAvatarName
03092018
meta
03092019
int32 eventsDuration
int32 ignored
int32 numControlNames
lookbackstring controlNames[numControlNames]
int32 numControlEntries
int32
ControlEntry[numControlEntries]
int32 time (ms + 100000)
byte controlNameIndex
int32 onoff (1/0)
string gameVersion
int32 exeChecksum
int32 osKind
int32 cpuKind
string raceSettingsXML
int32
[edit] Historical information
While the above description of *.gbx files is far more complete and precise than before, it is also entirely different from – and more abstract than – the original description, which formed the basis of the tools below. That historical description can still be viewed at this revision link.
[edit] Applications and Libraries that can inspect the file format
- Extract GBX data - a PHP script to extract useful data from all .Challenge.Gbx files (including the thumbnail image), and from .Replay.Gbx files.
- Tally GBX versions - a PHP script to tally version data from all .Challenge.Gbx files (including sample challenges in all known versions).
- GBX Data Fetcher - two PHP classes to extract useful data from all .Challenge.Gbx files (including the thumbnail image) and from .Replay.Gbx files, and parse their XML blocks.
- Replay Parser - a PHP class to extract useful data from Replay data strings (from the GetValidationReplay method), and parse their XML blocks.
- TrackStudio - a Windows TrackMania Forever track editor with 3D interface.
- Blockmix tools - challenge editing tools (Recompressor, ChallengeEdit, GBX-Master, CELightRotate, TmfBlockMixEdition).
- TMPakTool - Open and edit .pak files.
- TMUnlimiter - Patches the in-game track editor to remove the block placement restrictions.
- GbxDump - a Windows tool to dump and analyze the headers of .Challenge.Gbx and .Replay.Gbx files.
- ReplayToChallenge - a Windows tool to extract a Challenge from a .Replay.Gbx file (includes source code).
- Easy TM
- Trackmania Disassembler - includes a library allowing you to write your own applications that can read the format.
To do ... translate...