Difference between revisions of "GBX"

From Mania Tech Wiki
Jump to navigation Jump to search
m (:head_bang:)
(31 intermediate revisions by 3 users not shown)
Line 6: Line 6:
 
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.
 
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 serializable classes are organized into ''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 {{c|<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 <code>07 30 04 03</code> in the file, that would correspond to the integer 0x03043007, and be interpreted as follows:
 
The data in a .gbx file follows the pattern {{c|<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 <code>07 30 04 03</code> in the file, that would correspond to the integer 0x03043007, and be interpreted as follows:
Line 71: Line 71:
 
The file body contains further chunks of the main class instance, and may also contain auxiliary class instances. Reading the body 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:
 
The file body contains further chunks of the main class instance, and may also contain auxiliary class instances. Reading the body 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()
+
  ReadNode()<ref>[[ManiaPlanet_internals#Object_system|CMwNod::Archive]]</ref>
 
  {
 
  {
 
     while (true)
 
     while (true)
 
     {
 
     {
         chunkID = ReadUInt32();
+
         chunkID = ReadUInt32();<ref>[[ManiaPlanet_internals#CClassicArchive|CClassicArchive::ReadNat32]]</ref>
         if (chunkID == 0xFACADE01)
+
         if (chunkID == 0xFACADE01) // no more chunks
 +
        {
 +
            OnNodLoaded();<ref>[[ManiaPlanet_internals#Object_system|CMwNod::OnNodLoaded]]</ref>
 
             return;
 
             return;
 +
        }
 +
 +
        chunkFlags = GetChunkInfo(chunkID);<ref>[[ManiaPlanet_internals#Object_system|CMwNod::GetChunkInfo]]</ref>
 
   
 
   
         chunkFlags = GetChunkFlags(chunkID);
+
         if (chunkFlags == 0xFACADE01 || (chunkFlags & 0x11) == 0x10)
        if (chunkFlags & 0x10)
 
 
         {
 
         {
 
             skip = ReadUInt32();
 
             skip = ReadUInt32();
 +
            if (skip != 0x534B4950) // "SKIP"
 +
            {
 +
                OnNodLoaded();
 +
                return;
 +
            }
 +
 
             chunkDataSize = ReadUInt32();
 
             chunkDataSize = ReadUInt32();
 +
            SkipData(chunkDataSize);<ref>[[ManiaPlanet_internals#CClassicArchive|CClassicArchive::SkipData]]</ref> // skip unknown or obsolete chunk
 
         }
 
         }
 
   
 
   
         ReadChunk(chunkID);
+
         if (chunkFlags != 0xFACADE01 && (chunkFlags & 0x11) != 0x10)
 +
        {
 +
            if (chunkFlags & 0x10) // skippable
 +
            {
 +
                skip = ReadUInt32();          // unused
 +
                chunkDataSize = ReadUInt32(); // unused
 +
            }
 +
 +
            ReadChunk(chunkID);<ref>[[ManiaPlanet_internals#Object_system|CMwNod::Chunk]]</ref> // read the chunk
 +
        }
 
     }
 
     }
 
  }
 
  }
  
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 uint32 0x50494B53 ("SKIP", shows up as "PIKS" in the file due to little-endian ordering) and an uint32 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.
+
The code 0xFACADE01 is used here for two different purposes. As a dummy chunk ID (read from the file), it signifies the end of the chunk list for the current class. As a flag (returned by the function GetChunkInfo), it signifies that the passed chunk ID is unknown. GetChunkInfo() doesn't read anything from the file; it provides loading flags for the specified chunk ID. The flag 0x01 indicates that the chunk must be read. If this flag is not set, the chunk can be skipped (if possible). The second important flag is 0x10 and indicates whether the chunk is "skippable" or not. If it is, the chunk ID is followed by an uint32 0x50494B53 ("SKIP", shows up as "PIKS" in the file due to little-endian ordering) and an uint32 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.
+
The function OnNodLoaded() is used by some classes to prepare the read data (e.g., to make them compatible with newer versions of the engine).
  
 
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.
 
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.
Line 103: Line 123:
 
* '''byte''', '''uint16''', '''int32''', '''uint32''', '''uint64''', '''uint128''', '''float''': regular little-endian encoding.
 
* '''byte''', '''uint16''', '''int32''', '''uint32''', '''uint64''', '''uint128''', '''float''': regular little-endian encoding.
  
* '''vec2D''':
+
* '''vec2''':
 
** float x
 
** float x
 
** float y
 
** float y
  
* '''vec3D''':
+
* '''vec3''':
 
** float x
 
** float x
 
** float y
 
** float y
Line 117: Line 137:
 
** float b
 
** float b
  
* '''string''':
+
* '''string''':<ref>[[ManiaPlanet_internals#CClassicArchive|CClassicArchive::DoString]]</ref>
 
** uint32 length
 
** uint32 length
 
** byte chars[length] ({{wp|UTF-8}}, older files sometimes with {{wp|Byte order mark|BOM}}, not zero-terminated)
 
** byte chars[length] ({{wp|UTF-8}}, older files sometimes with {{wp|Byte order mark|BOM}}, not zero-terminated)
  
* '''lookbackstring''': a form of compression which allows to avoid repeating the same string multiple times. Every time 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.
+
* '''lookbackstring''':<ref>[[ManiaPlanet_internals#Id|CMwId::Archive]]</ref> a form of compression which allows to avoid repeating the same string multiple times. Every time 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:
 
** if this is the first lookback string encountered:
 
*** uint32 version (currently 3)
 
*** uint32 version (currently 3)
Line 130: Line 150:
 
''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. If index represents a number (bits 30 and 31 not set), it describes the position inside a ''global'' string table. In most cases it concerns the [[Collection ID|ID of a collection]].
 
''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. If index represents a number (bits 30 and 31 not set), it describes the position inside a ''global'' string table. In most cases it concerns the [[Collection ID|ID of a collection]].
  
* '''fileref''': path to an external file, e.g. a skin.
+
''Note'': Virtual Skipper 2 uses version 2 of the lookback strings. In this version, the string is always stored, the index always contains the position within the global name table, and the field with the version is also always present.
 +
 
 +
* '''meta''':<ref>[[ManiaPlanet_internals#Identifier|SGameCtnIdentifier::Archive]]</ref> contains [[ManiaPlanet_internals#Identifier|meta]] information like the track environment, time of day, and author.
 +
** lookbackstring id
 +
** lookbackstring collection
 +
** lookbackstring author
 +
 
 +
* '''fileref''':<ref>CSystemPackManager::ArchivePackDesc</ref> path to an external file, e.g. a skin.
 
** byte version (currently 3)
 
** byte version (currently 3)
 
** if version >= 3:
 
** if version >= 3:
Line 138: Line 165:
 
*** string locatorUrl
 
*** string locatorUrl
  
* '''meta''': contains [[ManiaPlanet_internals#Identifier|meta]] information like the track environment, time of day, and author.
+
* '''noderef''':<ref>[[ManiaPlanet_internals#CSystemArchiveNod|CSystemArchiveNod::DoNodPtr]]</ref> a reference to an auxiliary class instance.
** lookbackstring field1
 
** lookbackstring field2
 
** lookbackstring author
 
 
 
* '''noderef''': a reference to an auxiliary class instance.
 
 
** uint32 index. if this is -1, the node reference is empty (null).
 
** uint32 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:
 
** if the index is >= 0 and the node at the index has not been read yet:
Line 195: Line 217:
 
             5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
 
             5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
 
  if version >= 1:
 
  if version >= 1:
     bool locked (used by VirtualSkipper to lock the map parameter)
+
     bool locked (used by Virtual Skipper to lock the map parameters)
 
     string password (weak xor encryption, no longer used in newer track files; see 03043029)
 
     string password (weak xor encryption, no longer used in newer track files; see 03043029)
 
     if version >= 2:
 
     if version >= 2:
 
         meta decoration (timeOfDay, environment, envirAuthor) (decoration envir can be different than collection envir)
 
         meta decoration (timeOfDay, environment, envirAuthor) (decoration envir can be different than collection envir)
 
         if version >= 3:
 
         if version >= 3:
             vec2D mapOrigin
+
             vec2 mapOrigin
 
             if version >= 4:
 
             if version >= 4:
                 vec2D mapTarget
+
                 vec2 mapTarget
 
                 if version >= 5:
 
                 if version >= 5:
 
                     uint128
 
                     uint128
Line 253: Line 275:
 
             5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
 
             5: (old)Rounds, 6: InProgress, 7: Campaign, 8: Multi, 9: Solo, 10: Site, 11: SoloNadeo, 12: MultiNadeo)
 
 
 +
'''03043012'''
 +
string
 +
 +
'''03043013'''
 +
ReadChunk(0x0304301F)
 +
 
'''03043014''' (skippable)
 
'''03043014''' (skippable)
 
  uint32
 
  uint32
Line 267: Line 295:
 
 
 
'''03043018''' (skippable)
 
'''03043018''' (skippable)
  uint32
+
  bool
 
  uint32 numLaps
 
  uint32 numLaps
  
Line 283: Line 311:
 
  uint32 sizeY
 
  uint32 sizeY
 
  uint32 sizeZ
 
  uint32 sizeZ
  uint32 needUnlock
+
  bool needUnlock
  uint32 flagsAre32Bit
+
  if chunkId != 03043013:
 +
    uint32 version
 
   
 
   
 
  uint32 numBlocks
 
  uint32 numBlocks
Line 293: Line 322:
 
     byte y
 
     byte y
 
     byte z
 
     byte z
     uint16/uint32 flags
+
     if version == 0:
 +
        uint16 flags
 +
    if version > 0:
 +
        uint32 flags
 
     if (flags == 0xFFFFFFFF)
 
     if (flags == 0xFFFFFFFF)
 
         continue (read the next block)
 
         continue (read the next block)
Line 301: Line 333:
 
     if (flags & 0x100000)
 
     if (flags & 0x100000)
 
         noderef blockparameters
 
         noderef blockparameters
 +
 
''Note:'' blocks with flags 0xFFFFFFFF should be skipped, they aren't counted in the numBlocks.
 
''Note:'' blocks with flags 0xFFFFFFFF should be skipped, they aren't counted in the numBlocks.
  
Line 317: Line 350:
 
 
 
'''03043025'''
 
'''03043025'''
  vec2D mapCoordOrigin
+
  vec2 mapCoordOrigin
  vec2D mapCoordTarget
+
  vec2 mapCoordTarget
 
 
 
'''03043026'''
 
'''03043026'''
Line 324: Line 357:
 
 
 
'''03043027'''
 
'''03043027'''
  uint32 provided
+
  bool archiveGmCamVal
  if provided != 0:
+
  if archiveGmCamVal:
 
     byte
 
     byte
     vec3D x 3
+
     GmMat3 (vec3 x 3)
     vec3D
+
     vec3
 
     float
 
     float
 
     float
 
     float
Line 342: Line 375:
  
 
'''0304302A'''
 
'''0304302A'''
  uint32
+
  bool
  
 
'''0304303D''' (skippable)
 
'''0304303D''' (skippable)
Line 348: Line 381:
 
  uint32 version
 
  uint32 version
 
  if version >= 5:
 
  if version >= 5:
     uint32 frames
+
     uint32 frames   // If version < 5 then frames = 1
 
  if version >= 2:
 
  if version >= 2:
     if version < 5:
+
     for each frame:
      if version >= 4:
 
          uint32 size
 
          byte riff[size] // Avg lightmap webp file
 
 
       uint32 size
 
       uint32 size
       byte jfif[size]   // Intens/Avg lightmap jpeg file
+
       byte image[size]   // Image is JPEG/JFIF or WEBP/RIFF file format
       if version == 3:
+
       if version >= 3:
 
           uint32 size
 
           uint32 size
           byte jfif[size] // Intens lightmap jpeg file
+
           byte image[size]
    if version >= 5:
+
      if version >= 6:
      for each frame:
 
 
           uint32 size
 
           uint32 size
           byte riff[size] // Avg lightmap webp file
+
           byte image[size]
          uint32 size
 
          byte jfif[size] // Intens lightmap jpeg file
 
 
     if size != 0:
 
     if size != 0:
 
       uint32 uncompressedSize
 
       uint32 uncompressedSize
 
       uint32 compressedSize
 
       uint32 compressedSize
       byte data[compressedSize] // Lightmap cache zip file
+
       byte compressedData[compressedSize] // ZLIB compressed lightmap cache node
  
 
'''03043044''' (skippable)
 
'''03043044''' (skippable)
 
  uint32 unknown (0)
 
  uint32 unknown (0)
 
  uint32 size
 
  uint32 size
  uint32 flags
+
  uint32 classID
  uint32 unknown (1, 2)
+
  uint32 version
uint32 count (number of metadata records)
+
if version >= 2:
for each count:
+
  uint32 count (number of metadata records)
  string varName
+
  for each count:
  uint32 varType
+
    string varName
  switch varType:
+
    uint32 varType
    case EType_Boolean:
+
    switch varType:
      bool
+
      case EType_Boolean:
    case EType_Integer:
+
        bool
      int32
+
      case EType_Integer:
    case EType_Real:
+
        int32
      float
+
      case EType_Real:
    case EType_Text:
+
        float
      string
+
      case EType_Text:
    case EType_Int2:
+
        string
      int32
+
      case EType_Int2:
      int32
+
        int32
    case EType_Int3:
+
        int32
      int32
+
      case EType_Int3:
      int32
+
        int32
      int32
+
        int32
    case EType_Vec2:
+
        int32
      float
+
      case EType_Vec2:
      float
+
        float
    case EType_Vec3:
+
        float
      float
+
      case EType_Vec3:
      float
+
        float
      float
+
        float
    case EType_Array:
+
        float
      uint32 typeKey
+
      case EType_Array:
      uint32 typeValue
+
        uint32 typeKey
      uint32 arrayElements
+
        uint32 typeValue
      for each arrayElements
+
        uint32 arrayElements
        switch typeKey:
+
        for each arrayElements
          case EType_Boolean:
+
          switch typeKey:
            bool
+
            case EType_Boolean:
          case EType_Integer:
+
              bool
            int32
+
            case EType_Integer:
          case EType_Real:
+
              int32
            float
+
            case EType_Real:
          case EType_Text:
+
              float
            string
+
            case EType_Text:
        switch typeValue:
+
              string
          case EType_Boolean:
+
          switch typeValue:
            bool
+
            case EType_Boolean:
          case EType_Integer:
+
              bool
            int32
+
            case EType_Integer:
          case EType_Real:
+
              int32
            float
+
            case EType_Real:
          case EType_Text:
+
              float
            string
+
            case EType_Text:
          case EType_Int2:
+
              string
            int32
+
            case EType_Int2:
            int32
+
              int32
          case EType_Int3:
+
              int32
            int32
+
            case EType_Int3:
            int32
+
              int32
            int32
+
              int32
          case EType_Vec2:
+
              int32
            float
+
            case EType_Vec2:
            float
+
              float
          case EType_Vec3:
+
              float
            float
+
            case EType_Vec3:
            float
+
              float
            float
+
              float
          case EType_Array:
+
              float
            recursively read multidimensional arrays
+
            case EType_Array:
 +
              recursively read multidimensional arrays
  
<div class="mw-collapsible mw-collapsed">
 
 
The variable type is to be interpreted as follows:
 
The variable type is to be interpreted as follows:
<div class="mw-collapsible-content">
 
 
  enum eScriptType
 
  enum eScriptType
 
  {
 
  {
Line 461: Line 487:
 
   EType_Iso4,
 
   EType_Iso4,
 
   EType_Ident,
 
   EType_Ident,
   EType_Int2
+
   EType_Int2,
 +
  EType_Struct
 
  };
 
  };
</div>
+
Maniaplanet 4.1 has changed the way script types and values are written. The code now does the following:
</div>
+
 
 +
* First write the list of all types (all types appear only once).
 +
* For each value, write the index of the type in the list, and then write the value.
 +
 
 +
Writing the types and values has not changed except for the addition of structures which are a plain list of members.
 +
 
 +
Also note that the indices are written in the following form:
 +
 
 +
* If the index is < 128, it is written directly into one byte (because this covers 99% of the cases).
 +
* Otherwise, it is written in the first 7 bits of the first byte and in the following 2 bytes, a total of 23 bits (7+8+8). The 8th bit of the first byte is set to 1.
 +
 
 +
Note that this scheme is also used to write string lengths.
  
 
'''03043054''' (skippable)
 
'''03043054''' (skippable)
Line 470: Line 508:
 
  uint32
 
  uint32
 
  uint32 chunkSize
 
  uint32 chunkSize
  uint32 itemsCount (number of embedded items)
+
  uint32 itemCount (number of embedded items)
 
  uint32 zipSize (embedded items ZIP file size)
 
  uint32 zipSize (embedded items ZIP file size)
 
  byte zipFile[zipSize]
 
  byte zipFile[zipSize]
Line 532: Line 570:
  
 
'''0301B000'''
 
'''0301B000'''
  uint32 num
+
  uint32 archiveCount
  Item items[num]
+
  SCollectorStock archive[archiveCount]
     meta
+
     meta (blockName, collection, author)
 
     uint32
 
     uint32
  
Line 596: Line 634:
  
 
'''0305B005'''
 
'''0305B005'''
uint32 x 3 (ignored)
+
(all fields are ignored)
 +
uint32
 +
uint32
 +
 +
uint32
  
 
'''0305B006'''
 
'''0305B006'''
  uint32 num
+
  uint32 count
 
  uint32 items[count] (ignored)
 
  uint32 items[count] (ignored)
 +
 +
'''0305B007'''
 +
uint32 (ignored)
  
 
'''0305B008'''
 
'''0305B008'''
Line 641: Line 686:
  
 
===CGameWaypointSpecialProperty (03 13B 000) ===
 
===CGameWaypointSpecialProperty (03 13B 000) ===
 +
Mapped to the new GameData engine (2E) as class CGameWaypointSpecialProperty (2E009000).
 +
 
'''0313B000'''
 
'''0313B000'''
 
  uint32 version
 
  uint32 version
Line 725: Line 772:
  
 
A sample record looks as follows:
 
A sample record looks as follows:
  vec3D position
+
  vec3 position
 
  uint16 angle      (0..0xFFFF -> 0..pi)
 
  uint16 angle      (0..0xFFFF -> 0..pi)
 
  int16 axisHeading (-0x8000..0x7FFF -> -pi..pi)
 
  int16 axisHeading (-0x8000..0x7FFF -> -pi..pi)
Line 818: Line 865:
 
Base class for CGameCtnBlockInfo, CGameCtnMacroBlockInfo, CGameCtnObjectInfo and CGameCtnDecoration.
 
Base class for CGameCtnBlockInfo, CGameCtnMacroBlockInfo, CGameCtnObjectInfo and CGameCtnDecoration.
  
Deprecated. Moved to the new GameData engine (2E) as class CGameCtnCollector (2E001000).
+
Mapped to the new GameData engine (2E) as class CGameCtnCollector (2E001000).
  
 
'''0301A003''' ''"Desc"'' (header)
 
'''0301A003''' ''"Desc"'' (header)
 
  meta (name, collection, author)
 
  meta (name, collection, author)
 
  lookbackstring version
 
  lookbackstring version
  string pagePath (slash-separated folder path where the block appears in the editor)
+
  string pageName (slash-separated folder path where the block appears in the editor)
 
  if version == 5:
 
  if version == 5:
 
     lookbackstring
 
     lookbackstring
Line 829: Line 876:
 
     lookbackstring
 
     lookbackstring
 
  if version >= 3:
 
  if version >= 3:
     uint32 flags
+
     struct SCollectorDescFlags
     uint16 index
+
    {
 +
        uint32 __unused2__ : 1;
 +
        uint32 IsInternal  : 1;
 +
        uint32 IsAdvanced  : 1;
 +
        uint32 IconDesc    : 5;  // 0 = Unknown, 1 = NoIcon, 2 = BGRA_64x64, 3 = BGRA_128x128
 +
        uint32 __unused__  : 24;
 +
    };
 +
     uint16 catalogPosition (order of the blocks within pageName)
 
  if version >= 7:
 
  if version >= 7:
 
     string name
 
     string name
 
  if version >= 8:
 
  if version >= 8:
     byte
+
     byte prodState (0: Aborted, 1: GameBox, 2: DevBuild, 3: Release)
  
 
'''0301A004''' ''"Icon"'' (header)
 
'''0301A004''' ''"Icon"'' (header)
Line 876: Line 930:
  
 
===CGameCtnObjectInfo (03 01C 000)===
 
===CGameCtnObjectInfo (03 01C 000)===
Deprecated. Moved to the new GameData engine (2E) as class CGameItemModel (2E002000).
+
Mapped to the new GameData engine (2E) as class CGameItemModel (2E002000).
  
 
'''0301C000''' (header)
 
'''0301C000''' (header)
  uint32 objectInfoType (0: Undefined, 1: Ornament, 2: PickUp, 3: Character, 4: Vehicle)
+
  uint32 itemType (0: Undefined, 1: Ornament (formerly: StaticObject), 2: PickUp (formerly: DynaObject), 3: Character, 4: Vehicle, 5: Spot, 6: Cannon, 7: Group, 8: Decal, 9: Turret, 10: Wagon, 11: Block, 12: EntitySpawner
''Note:'' In class CGameItemModel the enumerators are: 0: Undefined, 1: StaticObject, 2: DynaObject, 3: Character, 4: Vehicle, 5: Spot, 6: Cannon
 
  
 
'''0301C001''' (header)
 
'''0301C001''' (header)
Line 911: Line 964:
  
 
'''0301C012'''
 
'''0301C012'''
  vec3D groundPoint
+
  vec3 groundPoint
 
  float painterGroundMargin
 
  float painterGroundMargin
 
  float orbitalCenterHeightFromGround
 
  float orbitalCenterHeightFromGround
Line 918: Line 971:
  
 
'''301C013'''
 
'''301C013'''
  nodref audioEnvironmentInCar (CPlugAudioEnvironment; used for cars)
+
  noderef audioEnvironmentInCar (CPlugAudioEnvironment; used for cars)
  
 
'''301C014'''
 
'''301C014'''
Line 939: Line 992:
 
'''0301C019'''
 
'''0301C019'''
 
  int version
 
  int version
  nodref phyModel                 // CPlugSurface
+
  noderef phyModel               // CPlugSurface
  nodref visModel                 // CPlugSurface
+
  noderef visModel               // CPlugSurface
 
  if version >= 1:
 
  if version >= 1:
     nodref visModelStatic       // CPlugSolid2Model
+
     noderef visModelStatic     // CPlugSolid2Model
  
 
===CGameCtnDecoration (03 038 000)===
 
===CGameCtnDecoration (03 038 000)===
Line 957: Line 1,010:
 
'''03033001''' ''"Desc"''
 
'''03033001''' ''"Desc"''
 
  byte version
 
  byte version
  lookbackstring environment
+
  lookbackstring collection
  bool
+
  bool needUnlock
 
  if version >= 1:
 
  if version >= 1:
 
     string iconEnv
 
     string iconEnv
 
     string iconCollection
 
     string iconCollection
 
     if version >= 2:
 
     if version >= 2:
         int32
+
         int32 sortIndex
 
         if version >= 3:
 
         if version >= 3:
             lookbackstring terrain
+
             lookbackstring defaultZone
 
             if version >= 4:
 
             if version >= 4:
 
                 meta (vehicle, collection, author)
 
                 meta (vehicle, collection, author)
 
                 if version >= 5:
 
                 if version >= 5:
                     string
+
                     string mapFid
                     float
+
                     vec2
                     float
+
                     vec2
                    float
+
                     if version == 5:
                    float
+
                         vec2 mapCoordElem
                     if version <= 5:
+
                     if version == 6 || version == 7:
                         float
+
                         vec2 mapCoordElem
                        float
+
                         vec2 mapCoordIcon
                     if version <= 7:
 
                         float
 
                         float
 
 
                     if version >= 7:
 
                     if version >= 7:
 
                         string loadscreen
 
                         string loadscreen
 
                         if version >= 8:
 
                         if version >= 8:
                             float
+
                             vec2 mapCoordElem
                             float
+
                             vec2 mapCoordIcon
                             float
+
                             vec2 mapCoordDesc
                            float
+
                             string longDesc
                            float
 
                            float
 
                             string
 
 
                             if version >= 9:
 
                             if version >= 9:
                                 string name
+
                                 string displayName
 
                                 if version >= 10:
 
                                 if version >= 10:
                                     bool
+
                                     bool isEditable
  
 
'''03033002''' ''"CollectorFolders"''
 
'''03033002''' ''"CollectorFolders"''
 
  byte version
 
  byte version
  string dirName
+
  string folderBlockInfo
  string dirName
+
  string folderItem
  string dirName
+
  string folderDecoration
 
  if version == 1 || version == 2:
 
  if version == 1 || version == 2:
     string dirName
+
     string folder
 
  if version >= 2:
 
  if version >= 2:
     string dirName
+
     string folderCardEventInfo
 
  if version >= 3:
 
  if version >= 3:
     string dirName
+
     string folderMacroBlockInfo
 
  if version >= 4:
 
  if version >= 4:
     string dirName
+
     string folderMacroDecals
  
 
'''03033003''' ''"MenuIconsFolders"''
 
'''03033003''' ''"MenuIconsFolders"''
 
  byte version
 
  byte version
  string dirName
+
  string folderMenusIcons
  
 
===CGameSkin (03 031 000)===
 
===CGameSkin (03 031 000)===
Line 1,028: Line 1,075:
 
     string file
 
     string file
 
     if version >= 2:
 
     if version >= 2:
         bool mipMap
+
         bool needMipMap
 
  if version >= 4:
 
  if version >= 4:
 
     string dirNameAlt
 
     string dirNameAlt
 
  if version >= 5:
 
  if version >= 5:
     bool unknown
+
     bool useDefaultSkin
  
 
===CGamePlayerProfile (03 08C 000)===
 
===CGamePlayerProfile (03 08C 000)===
 
'''0308C000''' ''"NetPlayerProfile"''
 
'''0308C000''' ''"NetPlayerProfile"''
  string login
+
  string onlineLogin
 
  string onlineSupportKey
 
  string onlineSupportKey
  
Line 1,044: Line 1,091:
 
  for each number:
 
  for each number:
 
     string dirName
 
     string dirName
 +
 +
== References to the actual functions ==
 +
<references />
  
 
== Applications and Libraries that can inspect/modify the file format ==
 
== Applications and Libraries that can inspect/modify the file format ==
Line 1,063: Line 1,113:
 
* [http://www.wolfgang-rolke.de/gbxdump/gbxlightmap.zip GbxLightMap download] - a Windows tool to extract the lightmaps from a given .Map.Gbx file (includes source code).
 
* [http://www.wolfgang-rolke.de/gbxdump/gbxlightmap.zip GbxLightMap download] - a Windows tool to extract the lightmaps from a given .Map.Gbx file (includes source code).
 
* [http://www.wolfgang-rolke.de/gbxdump/gbxmetadata.zip GbxMetadata download] - a Windows tool that indicates the persistent attributes of a given .Map.Gbx file (includes source code).
 
* [http://www.wolfgang-rolke.de/gbxdump/gbxmetadata.zip GbxMetadata download] - a Windows tool that indicates the persistent attributes of a given .Map.Gbx file (includes source code).
 +
* [[File:Krzychor-campaign-maker.zip]] - a Windows tool that allows to create custom campaigns for TMNF/TMUF. Sources not included.
  
 
[[Category:File formats]]
 
[[Category:File formats]]

Revision as of 18:02, 10 August 2019

TrackMania .gbx files (GameBox) are generic container files that can contain everything from configuration to textures to track definitions. They consist of a header, a reference table and the body.

In old versions of TrackMania they used to be text files – nowadays they are binary files. Integers are stored in little-endian order. The file body is often compressed (using LZO).

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 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 IDs there are also class IDs, which are just like chunk IDs except the chunk index part is ignored (and 0). For a complete overview of engines and classes, see Class IDs.

Header

The header contains things like compression information and the class ID of the main class instance. The current version of the header also provides a few chunks of the main class that serve as meta information (e.g. the thumbnail of a challenge).

  • byte[3] magic: "GBX"
  • uint16 version: currently 6
  • if version >= 3:
    • byte format: 'B' or 'T': Binary or Text (always B for version 6 gbx files)
    • byte compression: 'U' or 'C': Uncompressed or Compressed reference table (unused, always U)
    • byte compression: 'U' or 'C': Uncompressed or Compressed body (typically C for binary files)
    • if version >= 4:
      • byte unknown: 'R' or 'E': unknown purpose (typically R for binary files)
    • uint32 classID (class ID of main class instance)
    • if version >= 6:
      • uint32 userDataSize
      • byte[userDataSize]:
        • uint32 numHeaderChunks
        • HeaderEntry[numHeaderChunks]
          • uint32 chunkID
          • uint32 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
    • uint32 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.

Reference table

  • uint32 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:
    • uint32 ancestorLevel: how many folder levels to go up in the .pak folder hierarchy to reach the base folder from which files will be referenced.
    • uint32 numSubFolders
    • Folder folders[numSubFolders]
      • string name
      • uint32 numSubFolders
      • Folder folders[numSubFolders]
    • ExternalNode externals[numExternalNodes]
      • uint32 flags
      • if (flags & 4) == 0:
        • string fileName
      • else:
        • uint32 resourceIndex
      • uint32 nodeIndex (the index in the local node list where this external reference will be stored)
      • if version >= 5: (header version)
        • bool useFile (0 or 1, whether to use the file itself or the node that was loaded from the file)
      • if (flags & 4) == 0:
        • uint32 folderIndex: the depth-first index of the folder which the file is in. 0 means the base folder itself.

Body

  • if body is compressed:
    • uint32 uncompressedSize
    • uint32 compressedSize
    • byte data[compressedSize] (compressed with regular LZO)
  • else:
    • byte data[]

Reading the body

The file body contains further chunks of the main class instance, and may also contain auxiliary class instances. Reading the body 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()[1]
{
    while (true)
    {
        chunkID = ReadUInt32();[2]
        if (chunkID == 0xFACADE01) // no more chunks
        {
            OnNodLoaded();[3]
            return;
        }

        chunkFlags = GetChunkInfo(chunkID);[4]

        if (chunkFlags == 0xFACADE01 || (chunkFlags & 0x11) == 0x10)
        {
            skip = ReadUInt32();
            if (skip != 0x534B4950) // "SKIP"
            {
                OnNodLoaded();
                return;
            }

            chunkDataSize = ReadUInt32();
            SkipData(chunkDataSize);[5] // skip unknown or obsolete chunk
        }

        if (chunkFlags != 0xFACADE01 && (chunkFlags & 0x11) != 0x10)
        {
            if (chunkFlags & 0x10) // skippable
            {
                skip = ReadUInt32();          // unused
                chunkDataSize = ReadUInt32(); // unused
            }

            ReadChunk(chunkID);[6] // read the chunk
        }
    }
}

The code 0xFACADE01 is used here for two different purposes. As a dummy chunk ID (read from the file), it signifies the end of the chunk list for the current class. As a flag (returned by the function GetChunkInfo), it signifies that the passed chunk ID is unknown. GetChunkInfo() doesn't read anything from the file; it provides loading flags for the specified chunk ID. The flag 0x01 indicates that the chunk must be read. If this flag is not set, the chunk can be skipped (if possible). The second important flag is 0x10 and indicates whether the chunk is "skippable" or not. If it is, the chunk ID is followed by an uint32 0x50494B53 ("SKIP", shows up as "PIKS" in the file due to little-endian ordering) and an uint32 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.

The function OnNodLoaded() is used by some classes to prepare the read data (e.g., to make them compatible with newer versions of the engine).

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.

Primitives

  • bool: 32-bit little-endian integer that can be 0 or 1.
  • byte, uint16, int32, uint32, uint64, uint128, float: regular little-endian encoding.
  • vec2:
    • float x
    • float y
  • vec3:
    • float x
    • float y
    • float z
  • color:
    • float r
    • float g
    • float b
  • string:[7]
    • uint32 length
    • byte chars[length] (UTF-8, older files sometimes with BOM, not zero-terminated)
  • lookbackstring:[8] a form of compression which allows to avoid repeating the same string multiple times. Every time 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:
      • uint32 version (currently 3)
    • uint32 index: bit 31 and 30 define the string type. If both bits are 0, the index is a number. The actual index is represented by the bits 0-29. If this value 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]. If no data is provided (unassigned), the bits 30 and 31 indicate how this state is stored. If bit 31 is set, the string "Unassigned" is used, but if bit 30 is set, the value -1 is stored instead.
    • If the bits 0 through 29 are 0 and bit 30 or 31 is 1:
      • string newString. Append to the string list.

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. If index represents a number (bits 30 and 31 not set), it describes the position inside a global string table. In most cases it concerns the ID of a collection.

Note: Virtual Skipper 2 uses version 2 of the lookback strings. In this version, the string is always stored, the index always contains the position within the global name table, and the field with the version is also always present.

  • meta:[9] contains meta information like the track environment, time of day, and author.
    • lookbackstring id
    • lookbackstring collection
    • lookbackstring author
  • fileref:[10] path to an external file, e.g. a skin.
    • byte version (currently 3)
    • if version >= 3:
      • byte[32] (checksum)
    • string filePath (skin: if version < 2 relative to Skins folder else relative to user folder)
    • if filePath.length > 0 && version >= 1:
      • string locatorUrl
  • noderef:[11] a reference to an auxiliary class instance.
    • uint32 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:
      • uint32 classID: instantiate a new node for this class ID and store it in the node list at the specified index.
      • ReadNode()

Note: In case of a text format .gbx file (marked by a 'T' in the header) all numbers and strings are stored as one line of ASCII text in each case. Every line ends with a carriage return-linefeed (<CR><LF>) combination (0x0D and 0x0A). A single <LF> is part of a string.

Class descriptions

CGameCtnChallenge (03 043 000)

03043002 "TmDesc"

byte version
if version < 3:
    meta (trackUID, environment, mapAuthor)
    string trackName
bool 0
if version >= 1:
    uint32 bronzeTime (ms)
    uint32 silverTime (ms)
    uint32 goldTime (ms)
    uint32 authorTime (ms)
    if version == 2:
        byte
    if version >= 4:
        uint32 cost (Copper price; since version 12: Display cost)
        if version >= 5:
            bool multilap
            if version == 6:
                bool
            if version >= 7:
                uint32 trackType (0: Race, 1: Platform, 2: Puzzle, 3: Crazy, 4: Shortcut, 5: Stunts, 6: Script)
                if version >= 9:
                    uint32 0
                    if version >= 10:
                        uint32 authorScore
                        if version >= 11:
                            uint32 editorMode (bit 0: advanced/simple editor, bit 1: has ghost blocks)
                            if version >= 12:
                                bool 0
                                if version >= 13:
                                    uint32 nbCheckpoints
                                    uint32 nbLaps

03043003 "Common"

byte version
meta (trackUID, environment, mapAuthor)
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:
    bool locked (used by Virtual Skipper to lock the map parameters)
    string password (weak xor encryption, no longer used in newer track files; see 03043029)
    if version >= 2:
        meta decoration (timeOfDay, environment, envirAuthor) (decoration envir can be different than collection envir)
        if version >= 3:
            vec2 mapOrigin
            if version >= 4:
                vec2 mapTarget
                if version >= 5:
                    uint128
                    if version >= 6:
                        string mapType
                        string mapStyle
                        if version <= 8:
                            bool
                        if version >= 8:
                            uint64 lightmapCacheUID
                            if version >= 9:
                                byte lightmapVersion
                                if version >= 11:
                                    lookbackstring titleUID

03043004 "Version"

uint32 version

03043005 "Community"

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 "Thumbnail"

uint32 version
if version != 0:
    uint32 thumbSize
    "<Thumbnail.jpg>"
    byte thumb[thumbSize]
    "</Thumbnail.jpg>"
    "<Comments>"
    string comments
    "</Comments>"

03043008 "Author"

uint32 version
uint32 authorVersion
string authorLogin
string authorNick
string authorZone
string authorExtraInfo

0304300D

meta (vehicle, collection, author)

03043011

noderef collectorList
noderef challengeParameters
uint32 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)

03043012

string

03043013

ReadChunk(0x0304301F)

03043014 (skippable)

uint32
string password (old style password with weak xor encryption. This chunk is no longer used in newer track files; see 03043029)

03043017 (skippable)

uint32 numCheckpoints
Checkpoint[numCheckpoints]

Checkpoint:

uint32
uint32
uint32

03043018 (skippable)

bool
uint32 numLaps

03043019 (skippable)

fileref modPackDesc

0304301C (skippable)

uint32 playMode (0: Race, 1: Platform, 2: Puzzle, 3: Crazy, 4: Shortcut, 5: Stunts)

0304301F

meta (trackUID, environment, mapAuthor)
string trackName
meta decoration (timeOfDay, environment, envirAuthor)
uint32 sizeX
uint32 sizeY
uint32 sizeZ
bool needUnlock
if chunkId != 03043013:
    uint32 version

uint32 numBlocks
for each block:
    lookbackstring blockName
    byte rotation (0/1/2/3)
    byte x
    byte y
    byte z
    if version == 0:
        uint16 flags
    if version > 0:
        uint32 flags
    if (flags == 0xFFFFFFFF)
        continue (read the next block)
    if (flags & 0x8000) != 0: custom block
        lookbackstring author
        noderef skin
    if (flags & 0x100000)
        noderef blockparameters

Note: blocks with flags 0xFFFFFFFF should be skipped, they aren't counted in the numBlocks.

Note: It is possible that additional blocks with flags 0xFFFFFFFF (Unassigned) follow after all other blocks.

03043021

noderef clipIntro
noderef clipGroupInGame
noderef clipGroupEndRace

03043022

uint32

03043024

fileref customMusicPackDesc

03043025

vec2 mapCoordOrigin
vec2 mapCoordTarget

03043026

noderef clipGlobal

03043027

bool archiveGmCamVal
if archiveGmCamVal:
    byte
    GmMat3 (vec3 x 3)
    vec3
    float
    float
    float

03043028

ReadChunk(0x03043027)
string comments

03043029 (skippable)

uint128 passwordHash (salted MD5)
uint32 CRC32("0x" + hex(passwordHash) + "???" + trackUID)

0304302A

bool

0304303D (skippable)

bool unknown
uint32 version
if version >= 5:
   uint32 frames   // If version < 5 then frames = 1
if version >= 2:
   for each frame:
      uint32 size
      byte image[size]   // Image is JPEG/JFIF or WEBP/RIFF file format
      if version >= 3:
         uint32 size
         byte image[size]
      if version >= 6:
         uint32 size
         byte image[size]
   if size != 0:
      uint32 uncompressedSize
      uint32 compressedSize
      byte compressedData[compressedSize] // ZLIB compressed lightmap cache node

03043044 (skippable)

uint32 unknown (0)
uint32 size
uint32 classID
uint32 version
if version >= 2:
  uint32 count (number of metadata records)
  for each count:
    string varName
    uint32 varType
    switch varType:
      case EType_Boolean:
        bool
      case EType_Integer:
        int32
      case EType_Real:
        float
      case EType_Text:
        string
      case EType_Int2:
        int32
        int32
      case EType_Int3:
        int32
        int32
        int32
      case EType_Vec2:
        float
        float
      case EType_Vec3:
        float
        float
        float
      case EType_Array:
        uint32 typeKey
        uint32 typeValue
        uint32 arrayElements
        for each arrayElements
          switch typeKey:
            case EType_Boolean:
              bool
            case EType_Integer:
              int32
            case EType_Real:
              float
            case EType_Text:
              string
          switch typeValue:
            case EType_Boolean:
              bool
            case EType_Integer:
              int32
            case EType_Real:
              float
            case EType_Text:
              string
            case EType_Int2:
              int32
              int32
            case EType_Int3:
              int32
              int32
              int32
            case EType_Vec2:
              float
              float
            case EType_Vec3:
              float
              float
              float
            case EType_Array:
              recursively read multidimensional arrays

The variable type is to be interpreted as follows:

enum eScriptType
{
  EType_Void = 0,
  EType_Boolean,
  EType_Integer,
  EType_Real,
  EType_Class,
  EType_Text,
  EType_Enum,
  EType_Array,
  EType_ParamArray,
  EType_Vec2,
  EType_Vec3,
  EType_Int3,
  EType_Iso4,
  EType_Ident,
  EType_Int2,
  EType_Struct
};

Maniaplanet 4.1 has changed the way script types and values are written. The code now does the following:

  • First write the list of all types (all types appear only once).
  • For each value, write the index of the type in the list, and then write the value.

Writing the types and values has not changed except for the addition of structures which are a plain list of members.

Also note that the indices are written in the following form:

  • If the index is < 128, it is written directly into one byte (because this covers 99% of the cases).
  • Otherwise, it is written in the first 7 bits of the first byte and in the following 2 bytes, a total of 23 bits (7+8+8). The 8th bit of the first byte is set to 1.

Note that this scheme is also used to write string lengths.

03043054 (skippable)

uint32 version (1)
uint32
uint32 chunkSize
uint32 itemCount (number of embedded items)
uint32 zipSize (embedded items ZIP file size)
byte zipFile[zipSize]

21080001 "VskDesc"

byte version
if version < 1:
    meta (trackUID, environment, mapAuthor)
    string trackName
bool unknown
uin32 unknown
if version < 1:
    byte unknown
byte unknown
if version < 9:
    byte boatName (0: Acc, 1 : Multi, 2 : Melges, 3 : OffShore)
if version >= 9:
    lookbackstring boat (separated by line feed: Boat name, website, boat ID)
if version >= 12:
    lookbackstring boatAuthor
byte raceMode (0: Fleet Race, 1 : Match Race, 2 : Team Race)
byte unknown	
byte windDirection (0: North, 1: NE, 2: East, 3: SE, 4: South, 5: SW, 6: West, 7: NW)
byte windStrength (Force: windStrength + 3)
byte weather (0: Sunny, 1: Cloudy, 2: Rainy, 3: Stormy)
byte unknown
byte startDelay (0: Immediate, 1: 1 Min, 2: 3 Min, 3: 5 Min, 4: 8 Min)
uint32 startTime
if version >= 2:
    uint32 timeLimit
    bool noPenalty
    bool inflPenalty
    bool finishFirst
    if version >= 3:
        byte nbAIs
        if version >= 4:
            float courseLength
            if version == 4:
                byte unknown
            uint32 windShiftDuration
            if version >= 5:
               int32 windShiftAngle
               byte unknown
               if version == 6 || version == 7:
                   bool unknown
                   string unknown
               if version >= 7:
                   bool exactWind (Exact wind: !exactWind)
                   if version >= 10:
                       uint32 spawnPoints
                       if version >= 11:
                           byte aILevel (0: Easy, 1: Intermediate, 2: Expert, 3: Pro)
                           if version >= 13:
                               bool smallShifts
                               if version >= 14:
                                   bool noRules
                                   bool startSailUp

CGameCtnCollectorList (03 01B 000)

0301B000

uint32 archiveCount
SCollectorStock archive[archiveCount]
    meta (blockName, collection, author)
    uint32

CGameCtnChallengeParameters (03 05B 000)

0305B000 (all fields are ignored)

uint32
uint32
uint32
uint32

uint32
uint32
uint32

uint32

0305B001

string tip
string tip
string tip
string tip

0305B002 (all fields are ignored)

uint32
uint32
uint32
float
float
float
uint32
uint32
uint32
uint32
uint32
uint32
uint32
uint32
uint32
uint32

0305B003 (all fields are ignored)

uint32
float

uint32
uint32
uint32

uint32

0305B004

uint32 bronzeTime (ms)
uint32 silverTime (ms)
uint32 goldTime (ms)
uint32 authorTime (ms)
uint32 ignored

0305B005 (all fields are ignored)

uint32
uint32

uint32

0305B006

uint32 count
uint32 items[count] (ignored)

0305B007

uint32 (ignored)

0305B008

uint32 timeLimit (ms)
uint32 authorScore (ms)

0305B00A (skippable)

uint32 (0?)
uint32 bronzeTime (ms)
uint32 silverTime (ms)
uint32 goldTime (ms)
uint32 authorTime (ms)
uint32 timeLimit (ms)
uint32 authorScore (ms)

0305B00D

uint32 (-1?)

0305B00E (skippable)

uint32
uint32
uint32

CGameCtnBlockSkin (03 059 000)

03059000

string text
string ignored

03059001

string text
fileref packDesc

03059002

string text
fileref packDesc
fileref parentPackDesc

CGameWaypointSpecialProperty (03 13B 000)

Mapped to the new GameData engine (2E) as class CGameWaypointSpecialProperty (2E009000).

0313B000

uint32 version
if version == 1:
    uint32 spawn
    uint32 order
if version == 2:
    string tag
    uint32 order

CGameCtnReplayRecord (03 093 000)

03093000 "Version"

uint32 version
if version >= 2:
    meta (trackUID, environment, author)
    uint32 time (ms)
    string nickName
    if version >= 6:
        string driverLogin
        if version >= 8:
            byte 1
            lookbackstring titleUID

03093001 "Community"

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 "Author" (header)

uint32 version
uint32 authorVersion
string authorLogin
string authorNick
string authorZone
string authorExtraInfo

03093002 (body)

uint32 size
byte GBX[size] (the track the replay was recorded on)

03093007 (skippable)

uint32

03093014

uint32 ignored (0xA)
uint32 numGhosts
noderef ghosts[numGhosts]
uint32 ignored
uint32 num
uint64[numExtras]

03093015

noderef

CGameGhost (03 03F 005)

0303F005

uint32 uncompressedSize
uint32 compressedSize
byte compressedData[compressedSize]: (compressed with zlib deflate)
    uint32 classID
    bool bSkipList2
    uint32
    uint32 samplePeriod
    uint32

    uint32 size
    byte sampleData[size] (samples of position, rotation, speed... of the car during the race)

    uint32 numSamples
    if numSamples > 0:
        uint32 firstSampleOffset
        if numSamples > 1:
            uint32 sizePerSample
            if sizePerSample == -1:
                uint32 sampleSizes[numSamples - 1]

    if bSkipList2 == 0:
        uint32 num
        int32 sampleTimes[num]

0303F006

uint32 IsReplaying
ReadChunk(0x0303F005)

A sample record looks as follows:

vec3 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)).

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)

uint32 raceTime

03092008 (skippable)

uint32 numRespawns

03092009 (skippable)

color lightTrailColor

0309200A (skippable)

uint32 stuntsScore

0309200B (skippable)

uint32 num
uint64[num]

0309200C

uint32 ignored

0309200E

lookbackstring uid

0309200F

string ghostLogin

03092010

lookbackstring 

03092012

uint32 ignored
uint128

03092013 (skippable)

uint32
uint32

03092014 (skippable)

uint32 

03092015

lookbackstring playerMobilId

03092017 (skippable)

uint32 num
fileref skinPackDescs[num]
string ghostNickname
string ghostAvatarName

03092018

meta

03092019

uint32 eventsDuration
uint32 ignored
uint32 numControlNames
lookbackstring controlNames[numControlNames]

uint32 numControlEntries
uint32
ControlEntry[numControlEntries]
    uint32 time (ms + 100000)
    byte controlNameIndex
    uint32 onoff (1/0)

string gameVersion
uint32 exeChecksum
uint32 osKind
uint32 cpuKind
string raceSettingsXML
uint32

CGameCtnCollector (03 01A 000)

Base class for CGameCtnBlockInfo, CGameCtnMacroBlockInfo, CGameCtnObjectInfo and CGameCtnDecoration.

Mapped to the new GameData engine (2E) as class CGameCtnCollector (2E001000).

0301A003 "Desc" (header)

meta (name, collection, author)
lookbackstring version
string pageName (slash-separated folder path where the block appears in the editor)
if version == 5:
    lookbackstring
if version >= 4:
    lookbackstring
if version >= 3:
    struct SCollectorDescFlags
    {
        uint32 __unused2__ : 1;
        uint32 IsInternal  : 1;
        uint32 IsAdvanced  : 1;
        uint32 IconDesc    : 5;   // 0 = Unknown, 1 = NoIcon, 2 = BGRA_64x64, 3 = BGRA_128x128
        uint32 __unused__  : 24;
    };
    uint16 catalogPosition (order of the blocks within pageName)
if version >= 7:
    string name
if version >= 8:
    byte prodState (0: Aborted, 1: GameBox, 2: DevBuild, 3: Release)

0301A004 "Icon" (header)

uint16 iconWidth
uint16 iconHeight
byte iconData[4*iconWidth*iconHeight]   // one RGBA uint32 per pixel

0301A006 (header)

uint64 filetime   // lightmap cache timestamp

0301A007

bool isInternal
uint32
uint32 catalogPosition
uint32
uint32
uint32

0301A009

string pagePath (same as in chunk 0301A003)
bool hasIconFid
if hasIconFid:
    noderef iconFid
lookbackstring

0301A00B

meta

301A00C

string name

301A00D

string description

0301A00E

bool iconUseAutoRender
uint32 iconQuarterRotationY

0301A00F

string defaultSkinName

CGameCtnObjectInfo (03 01C 000)

Mapped to the new GameData engine (2E) as class CGameItemModel (2E002000).

0301C000 (header)

uint32 itemType (0: Undefined, 1: Ornament (formerly: StaticObject), 2: PickUp (formerly: DynaObject), 3: Character, 4: Vehicle, 5: Spot, 6: Cannon, 7: Group, 8: Decal, 9: Turret, 10: Wagon, 11: Block, 12: EntitySpawner

0301C001 (header)

uint32

0301C006

uint32 defaultCamIndex (-1 = none, 0 and above = camera index; used for cars)

0301C008

uint32 numNadeoSkinFids
noderef fids[numNadeoSkinFids] (file references)

0301C009

uint32 version = 10
uint32 numCameras
noderef cameras[numCameras] (references to CGameControlCamera; used for cars)

0301C00A

noderef decoratorSolid (CPlugDecoratorSolid)

0301C00B

noderef stemMaterial (CPlugMaterial)
noderef stemBumpMaterial (CPlugMaterial)

0301C00C

noderef raceInterfaceFid

0301C010

noderef bannerProfileFid (file reference to e.g. BannerProfileCanyon.Texture.gbx)

0301C012

vec3 groundPoint
float painterGroundMargin
float orbitalCenterHeightFromGround
float orbitalRadiusBase
float orbitalPreviewAngle

301C013

noderef audioEnvironmentInCar (CPlugAudioEnvironment; used for cars)

301C014

noderef baseAttributes (CGameAttributes; unused?)

301C015

uint32 objectInfoType (0: Undefined, 1: Ornament, 2: PickUp, 3: Character, 4: Vehicle)

0301C016

noderef defaultSkinFid

0301C017

uint32 version = 0
bool isFreelyAnchorable

0301C018

int version = 0
bool isUsable

0301C019

int version
noderef phyModel                // CPlugSurface
noderef visModel                // CPlugSurface
if version >= 1:
    noderef visModelStatic      // CPlugSolid2Model

CGameCtnDecoration (03 038 000)

03038000 "MoodRemaping"

byte version
string dirName
ReadChunk(0x03031000)

03038001

byte version
uint32 unknown

CGameCtnCollection (03 033 000)

03033001 "Desc"

byte version
lookbackstring collection
bool needUnlock
if version >= 1:
    string iconEnv
    string iconCollection
    if version >= 2:
        int32 sortIndex
        if version >= 3:
            lookbackstring defaultZone
            if version >= 4:
                meta (vehicle, collection, author)
                if version >= 5:
                    string mapFid
                    vec2
                    vec2
                    if version == 5:
                        vec2 mapCoordElem
                    if version == 6 || version == 7:
                        vec2 mapCoordElem
                        vec2 mapCoordIcon
                    if version >= 7:
                        string loadscreen
                        if version >= 8:
                            vec2 mapCoordElem
                            vec2 mapCoordIcon
                            vec2 mapCoordDesc
                            string longDesc
                            if version >= 9:
                                string displayName
                                if version >= 10:
                                    bool isEditable

03033002 "CollectorFolders"

byte version
string folderBlockInfo
string folderItem
string folderDecoration
if version == 1 || version == 2:
    string folder
if version >= 2:
    string folderCardEventInfo
if version >= 3:
    string folderMacroBlockInfo
if version >= 4:
    string folderMacroDecals

03033003 "MenuIconsFolders"

byte version
string folderMenusIcons

CGameSkin (03 031 000)

Moved to engine Plug (09) as class CPlugGameSkin (090F4000).

03031000 "Skin"

byte version
string dirName
if version >= 1:
    string textureName
    string sceneId
byte number
for each number:
    uint32 classId
    string name
    string file
    if version >= 2:
        bool needMipMap
if version >= 4:
    string dirNameAlt
if version >= 5:
    bool useDefaultSkin

CGamePlayerProfile (03 08C 000)

0308C000 "NetPlayerProfile"

string onlineLogin
string onlineSupportKey

CMwNod (01 001 000)

01001000 "FolderDep"

uint32 number
for each number:
    string dirName

References to the actual functions

Applications and Libraries that can inspect/modify the file format

  • GbxDump - a Windows tool to dump and analyze the header of all kinds of .Gbx files.
  • GBX Data Fetcher - a PHP module with classes to extract useful data from .Challenge|Map.Gbx files (including the thumbnail image), .Replay.Gbx files and .Pack.Gbx|.pak files, as well as parse their XML blocks.
  • Extract GBX data - a PHP script to format and print data from all file types supported by the GBX Data Fetcher module.
  • Tally GBX versions - a PHP script to tally some version data from all .Challenge|Map.Gbx files (includes sample challenges in all known versions).
  • Trackmania Disassembler - includes a library allowing you to write your own applications that can read the format.
  • TrackStudio (@archive.org) - a Windows TrackMania Forever track editor with 3D interface.
  • Blockmix tools (@archive.org) - Challenge editing tools (Recompressor, ChallengeEdit, GBX-Master, CELightRotate, TmfBlockMixEdition).
  • TMPakTool (@archive.org) - an open source tool which can open and edit .pak files in an Explorer-like interface. Comes with a C# library which you can use in your own applications to work with .pak files. Download & Source code
  • TMUnlimiter (@archive.org) - Patches the in-game track editor to remove the block placement restrictions. Source code
  • GBXedit - Command-line based blockmix editor for TM² Stadium maps.
  • MapEdit - GUI-based blockmix editor for all ManiaPlanet maps.
  • ReplayToChallenge download - a Windows tool to extract a Challenge from a .Replay.Gbx file (includes source code).
  • GbxDecompress download - a Windows tool that decompresses or compresses the file body of a given .Gbx file (includes source code).
  • EmbeddedItems download - a Windows tool that indicates all embedded items of a given .Map.Gbx file (includes source code).
  • GbxLightMap download - a Windows tool to extract the lightmaps from a given .Map.Gbx file (includes source code).
  • GbxMetadata download - a Windows tool that indicates the persistent attributes of a given .Map.Gbx file (includes source code).
  • File:Krzychor-campaign-maker.zip - a Windows tool that allows to create custom campaigns for TMNF/TMUF. Sources not included.