This article explains the Tomb Raider 6 formats. However this engine is not released open-source I have some thoughts what the Level Editing tools would do and more specifically I know what files would be out there and also their formats a little.
So first let's see a few pictures of WorldEdit - the program used to transform the pure materials (3D models and animations) into a playable level:
As you can clearly see the *.WPJ holds the level project information, possibly used to build up the playable *.GMX binary file - loaded by the engine. The scene tab in the Editor show us a list of the rooms in the current zone (actually some of these names, transformed into hashes can be tracked in the present binary level files in the data\maps directory). They have no separate file format and are possibly stored all in the *.TRX file format showed in the Meshes tab.
*Extension Name *FormatD *description
//Primary files
RMX - 0x40666666 //Room Map data
EVX - 0x00000008 //Behavior data
CLN - 0xFFFFFFFF //Collision data
SCX - 0x00000102 //Script data
Z%2.2d - 0x00000020 //Zone data - the decimal number is the zone index
CBH - 0x00000002 //Camera Behavior (possibly)
//Secondary files
//1st Cutscene files
CAM - 0X52454448 ("HDER") //Camera data
POS - 0X534F5043 ("CPOS") //Character position data
XXX - 0X534F5050 ("PPOS") //Object position data
TXT - **Text data format** //Describes the scene
//2nd Animations and objects
CAL - **Not constant** //Animations
CHR - **Not constant** //Characters (objects)
SHA - 0x44414853 ("SHAD") //Shading
//Others
AMX - **Not constant** //Game dependant code
The hash of the primary files are calculated with adding the format extension at the level name (for example: PARIS1 level RMX hash is calculated with the string: PARIS1.RMX). NOTE the upper case (The hashes of the files archives are always calculated in upper case)!
The cutscene file names are depend of the cutscene* name. For example if the cutscene we describe is "IG_1_16" the CAM file will be "IG_1_16.CAM", the POS will be "IG_1_16.POS" and etc. The cutscenes are called by triggers in the Room Map Data.
Here is the function, which gets the hash value for this files with the string of them, source:
Hash_Function_Source.cpp
NOTE: This function is used to convert almost all string you found in the .exe file.
* - The cutscene can be another gmx levels ("CS") and ingame movie ("IG").
NOTE: The format I used to describe the structures are defined by the c++ standard but I use some my defined forms like:
dword - unsigned long (32-bit unsigned integer)
short - unsigned short (16-bit unsigned integer)
xFloatVector - float [x] (float array as size of x)
struct GMX {
dword FormatD; // 0x40666666
short AmountFiles;
short Unknown;
GMX_File Files[ AmountFiles ]; // GMX_File structure as amount of AmountFiles
};
GMX_File {
unsigned long Hash; //The hashed file name
unsigned long Pointer; // add to it 0x800 to get the file pointer (pointer is relative to the start of the GMX file)
unsigned long Size; // Size of the file
};
1. RMX file format (Room Map data)
The Room Map data file is used to map the rooms (from Zone data file), transforming them, and limit them visibility. You can connect the rooms to be visible from other rooms with some special triggers. This file contain an array of map structures, each of it have objects.
struct RMX
{
dword FormatD; // 0x40666666
dword Unknown[3];
dword AmountMaps;
dword PointerToMap[ AmountMaps ]; // Pointer to the Mapped room (Map struct) as amount of AmountMaps
dword PointerToEOF; //Pointer to the end of file
};
NOTE: The EOF pointer isn't required if you make it 0 you won't see a ingame result.
struct Map
{
dword pad[25]; //zeros to fit the file size
dword RoomHash ; //Unused
dword Pointer; //Unused
dword pad1;
3FloatVector RoomTransform; //The room XZY transform
dword pad2;
//Down are positions of two points which are the room visibility limits
3FloatVector RoomLimitPoint0; //The position of the first point
dword pad3;
3FloatVector RoomLimitPoint1; //The position of the second point
dword pad4[3];
dword RoomHash1; //This is the hashed room name mapped in this structure (the hash is searched by the TR 6 engine in the current loaded zone for room resources)
dword pad5[3];
dword NL_OBJ[2]; //An object
dword NL_LIGHT[2]; //Lightning
dword NL_CHARLOC[2]; //Playable characters, animating and enemies
dword NL_TRIGGER[2]; //Trigger
dword NL_PORTAL[2];
dword NL_WAYPOINT[2];
dword NL_ACTOR[2];
dword NL_WATER[2]; //Water
dword NL_EMITTER[2];
dword NL_CAMERA[2]; //Camera set
dword NL_SCENERY[2];
//Each of upper dwords includes 2 pointers. The first pointer point to the begin of the first object and the second - points to the begin of the last object(the pointers are relative to the PointerToMap ).
dword Pointers[7][2]; //This pointers points to some data for room connections but I don't know much about them
dword Unknown[10];
};
struct ObjStart { //every type of object has the same start structure
dword PtrPrev; //Pointer to previous object begin
dword PtrNext; //Pointer to next object begin
dword Unknown[6];
3FloatVector ObjectTransform; //XZY object transformation
float point; //Indicates the position is point or vertex - the default value is point (0x3F80=1)
3FloatVector ObjectRotation; //XZY object rotation in degrees (clockwise)
dword Unused[5];
float X; //Object x position (used only in NL_TRIGGER)
float Z; //Object z position (used only in NL_TRIGGER)
float Y; //Object y position (used only in NL_TRIGGER) Unknown yet...
dword Unused[2];
dword ObjHash; //Object identify hash (used to call this object)
dword dwObjectType; //What type is the current object (follows the numerical way of pointer to such are structured in the room header)
dword Flag2; //Obj flag2
};
Some objects have callback function which can receive messages (like for example you can send a message to a door to open). They are:
NL_OBJ - _mapObjNodeReceiveMsg
NL_CHARLOC - _mapActorCallback
NL_TRIGGER - _mapTrigNodeReceiveMsg
Common structure in those objects is some fancy way of invoking EVX function which is:
struct FancyFuncInvokation {
dword dwEventIdToInvoke; //When this id is received into the callback function - this Func is invoked
dword dwFuncNum; //Number of function invoked from EVX file
};
The Obj type:
In it case Flag2 have the following possible values:
enum nlobjFlag2 : dword
{
EN_SKIP_AMX_SCRIPT = 4
} ;
ScxAmx scripts overrides default functions for the object.
struct NL_OBJ {
ObjStart Begin;
char Unknown[0xC];
dword dwSCXAMXhash; //Hash from SCX file to drive this object
char Unknown[0xE8];
enum : dword {
EN_PICKABLE = 0x200 //The object is a pickable one
} Flag1; //flag
dword Unknown;
short MeshObjIndx; //Indicates mesh-obj index from the current zone-file which is loaded (every NL_OBJ points to the current loaded zone objects NOT to the RoomObj::RoomHash origin map!)
short BehavIndx; //Indicates Behavior struct index from the script-file
short AmountFuncsInvoked; //Indicates Amount of the functions invoked at the end of the object
char BytesAnotherFlags[0x26]; //??
dword dwTimesMemAllocated; //how many blocks of 0x40 bytes will be allocated to be used by function 'colInsertObjectIntoList'
//If copying objects over watch out for this value otherwise game engine could overflow and thus cause bugs
char Bytes[0x70]; //Some positions linked with object lightning??
FancyFuncInvokation[AmountFuncsInvoked];
};
struct NL_TRIGGER {
ObjStart Begin;
char Unknown[0xF0];
4FloatVector pos0;
4FloatVector pos1;
dword dUnkn;
dword dNumFunctsExecuted; //Number of direct function calls
dword dSomeFlag;
dword dUnkn1[2];
dword dNumFancyScripts; //Number of fancy function calls
dword dUnkn2[2];
Functs[dNumFunctsExecuted];
FancyFuncInvokation[dNumFancyScripts];
};
2. EVX format (Behavior data)
The EVX format describes functions and behaviors used from the RMX file. Here's the structure of it:
struct SHead {
dword FormatID; //The Id of the file 8
dword PointerToObjs; //Pointer to Sobject structure
dword AmountScriptsPtrs; // Amount Script pointers
Scriptptrs ptrs[ AmountScriptsPtrs ];
};
struct Scriptptrs {
dword PointerToScript; //Pointer to Script structure
dword Hash; //Some kind of Hash meaning Unknown
};
struct Script {
dword AmountFuncts; //Amount of functions in this script
Functs Data[ AmountFuncts ];
};
struct Functs {
dword Flag1; //Some kind of ID connecting with FuncHash
dword FuncHash; //Function hash (from exe)
dword Flag2; //Flag used as an argument in the function - can be changed without fatal effect
};
Sobject {
dword AmountBehavs; // Amount of behaviors
dword Amount Anims; // Amount of animations used from the behaviors
dword pad[2];
Behavior* Dat;
Anim* Dat;
};
Behavior {
dword ItemToGive; //Index of item behavior in the inventory (if pickup item)
dword Unknown;
short Flag1; //flag - unknown
short Flag2; //flag - unknown
//the upper flags must be the same as those in the NL_OBJ structure
dword Unknown[3];
dword AnimIndx; //Anim struct index
dword Unknown[9];
char NodeStr[32]; //Name of the object resources (add extension *.CAL and *.CHR to find the animation and character for it)
};
Anim {
dword ObjAnimHashes; //Object animation hashes from the NODE animation file
dword CharAnimHashes; //Character use the object animation hashes
char Byte[];
// ............... size of 0xA0
};
3. Object CHR file format
Object character (NODE) file format prepare objects to use an animations. It have the same extension like the other character files (but it have different structure and usage). Here's the structure of it:
struct node {
dword FormatId; //'NODE'
dword PtrEOF; //Pointer to end of the file
dword AmountObj; //Amount of Structures
AnimHead Data[ AmountObj ];
};
struct AnimHead {
dword StrLen; //String lenght
char* string; //Name of the animated object (from current zone ObjHead::ObjHash)
float X; //Object x animation toehold position
float Z; //Object z animation toehold position
float Y; //Object y animation toehold position
dword Transform[]; //Some scales and transformations
};
4. ZONE data format
Zone data includes rooms, textures, mesh-objects and special static object collision. The zone data extension indicates it number *.Z and the number of it for example: .Z00 for zone 0. You can change it with a script function. Actually the zone files are bin from the Room data file. Here's the structure of it:
struct Map {
dword FormatId; //32
dword TextPtr; //Texture struct pointer
dword ObjsPtr; //Objs struct pointer
dword MshPtr; //Meshes struct pointer
dword StatColPtr; //StaticCol struct ponter
dword pad[3];
dword AmountShadR; // Amount of room shaders Headers
RoomShadH ShadDat[ AmountShadR ];
};
struct RoomShadH {
dword RHash; //Room Hash
dword SPtr; //Shader pointer (struct ShaderZ)
};
struct ShaderZ {
dword Unknown[2];
dword PtrShad; //Points to object Shader (struct ShadDat - file format)
};
struct Texture {
dword AmountTextInfo;
dword Unknown1;
dword AmountTextHead;
dword Unknown2;
TextInfo DatInf[ AmountTextInfo ];
TextHead DatHead[ AmountTextHead ];
char TextData[][];
};
The TextInfo struct is used while rendering the texture while the TextHead is used for creating the textures (it need to be the header of the TextDat).
struct TextInfo {
dword Flag1;
dword Flag2;
dword PrimText; //Texture(TextHead or created texture) index used in this TextInfo as primary texture to render
dword Flag3;
dword SecondText; //Texture(TextHead or created texture) index used in this TextInfo as secondary texture to blend
};
struct TextDatHead {
char Format[4]; // 'DXT1', 'DXT3' or 0x15000000. Last one is uncompressed A8R8G8B8 format.
dword Unknown1; // 2 or 4
dword Unknown2; // Always 1
dword Unknown3; // Always 30
dword Levels; // Number of Mip levels in this texture. 7 for 256x256 textures, 2 for 8x8 textures.
dword XSize;
dword YSize;
dword DataSize; // Size of this texture. ( TextData [The object index at the TextData array][ DataSize ]=TextData [The object index at the TextData array+1][0] )
dword Unknown4; // Always 0?
dword Unknown5; // Always 0?
};
struct Objs {
dword AmountObjHead;
dword AmountObjInfoPtrs;
dword ObjInfoPtr[AmountObjInfoPtrs]; //Object Header Pointers relative to the start of the map file (if ==0xffffffff the objectInfo is empty*) as Number of AmountObjInfoPtrs
* this is because the objects can duplicates in other area (think about the RMX NL_OBJ - for example if 0x12 object index means health pack in the first area(map) and in the second will mean a big house which will looks strange in the place of the small health pack and CORE programmers don't want this) and for skip this problem are the empty objects.
ObjHead ObjectHead[ Amount ObjHead ]; //Object header struct as amount of Amount ObjHead . They're index increase and points to a mesh-object from the Meshes struct
};
Just like the Texture struct here the ObjectHead data is used to create the mesh-objects and the ObjInfo is used while rendering them. The difference is that the data are stored into the Meshes struct.
struct ObjHead {
float Positions[7]; //Some object Transforms
dword PtrShad; //Points to object Shader (struct Shader - file format)
dword ObjHash; //Hashed object name
dword Unknown[11]; //in every object is same
};
struct ObjInfo {
float Pos1Ang[3]; //Those two vector creating
dword Unknown;
float Pos2Ang[3]; //an cube which describes the object parameters in 3d space (used only in pushable objects to secure the collision)
dword* ObjectCodeBin; //This is a binary code which describes the used objects in this info
here is what the binary code means:
0xFFFFFFFE - begin scope
0xFFFFFFFC - begin float values 0x18 (first 3 floats creates a position vector and the second-rotation vector-translating the objects in the current scope)
0xFFFFFFFD - end scope
0xFFFFFFFF - end object_node
in every scope you can include object meshes by index like this:
DWORD MeshObj[3] like the first dword value is the index of the mesh-object
};
struct Meshes {
dword AmountRooms;
Room MeshRooms[ AmountRooms ];
dword AmountObjects;
Mesh Objects[ AmountObjects ]; //Each object have a header in Objs::ObjectHead
};
struct Room {
dword RoomHash; //Room Hash used and translated from RMX::RoomObj::roomHash
Mesh Room
};
struct Mesh {
dword StrSize; //Structure size
dword Unknown[2];
dword AmountVerices; // Amount of vertices
dword Unknown1;
dword AmountIndx; // Amount of indexes
dword Unknown2;
dword AmountGroups; // Amount of groups
dword Unknown[5];
Vertex vertDat[ AmountVerices ]; //vertex data of number as AmountVerices
short Indexes[ AmountIndx ]; //indexes of number as AmountIndx
Group groups[ AmountGroups ]; //groups of number as AmountGroups };
struct Vertex {
float X;
float Z;
float Y;
//texture coordinates:
float U;
float V;
float U1;
float V1;
dword Unknown;
dword SpecularColor;
dword DiffuseColor;
};
struct Group {
unsigned long NumFaces;
unsigned long NumVertices;
unsigned long StartIndex;
unsigned long MaterIndx;
unsigned long Unknown;
unsigned long BaseVertexIndex;
unsigned long LastVertx;
unsigned long PrimitiveType;
float PosX1;
float PosZ1;
float PosY1;
float IsPoint1;
float PosX2;
float PosZ2;
float PosY2;
float IsPoint2;
};
5. SHAD file format (Shaders)
This file format is used to shader the Actor CHR (not the object ones). The structure itself is used in the Zone data format for shading objects and rooms. I'm still searching for this file and extend its structure. That I know for the moment is that this file creates effects like flames (in the torches of tomb levels), rain (in the first level) .
struct ShadHead {
dword VerId; //"SHAD"
ShadDat Data;
};
struct ShadDat {
dword Ptr; //
dword Ptr1; //
dword Dat[];
};
6. CutScene Text file (TXT)
This text file describes the scene options. The options starts with "#". Then we write number of things to enable or disable. For example if we want to hide the current zone doors while the cuscene we will write this:
#MAP WORLD OFF
2
a_block__door_a_block
a_block__door_a_block14
If we want to enable it we logically put on:
#MAP WORLD ON
2
a_block__door_a_block
a_block__door_a_block14
Where "a_block__door_a_block" is the name of NL_OBJ inside the gmx. The engine hash this string and search it in RMX::ObjStart::Hash.
Other is the char visibility items. For example if we want to hide laras shotgun here is what we'll write:
#CHAR LARA_1 OFF
1
SHOTGUN_BACKPACK
The "SHOTGUN_BACKPACK" is lara model equipment.
Actually there are many more commands which I will document here when I find them.
7. SCX format
The SCX file is an archive of AMX files which appear to be some game managed code generated by CORE and used as a behavior for some objects.
The structure of the file can be described by the following structures:
struct SCXHead
{
DWORD FormatSignature; //0x00000102
DWORD nFiles; //Number of AMX files held by the archive
} ;
struct AMXFileDescription
{
DWORD dwHash; //Hashed file name
DWORD szFile; //File Size
} ;
struct Object
{
DWORD dwHashToReference; //Hashed Object name to reference from RMX
DWORD dwAMXFileHash; //Hashed AMX file name attached to dwHashToReference
} ;
struct ObjecstDefinitions
{
DWORD nDefs; //Number Definitions
Object[nDefs];
} ;
These are the only structures that can be defined in this format because it's 4 and 16 byte aligned. The file starts with SCXHead structure which defines it's format (FormatSignature) and the number of files hold by it(nFiles). Then the actual data follows in array as amount of nFiles where each of it's elements is defined in this way:
AMXFileDescription structure aligned in 4 bytes
char Data[] (The AMX file itself) - the first part of it is 16 byte aligned
After this array follows the ObjecstDefinitions structure (aligned in 4 bytes) which maps object Hashes to the files. This is the format description served by my observations which can't be understood by everyone (because I mess terms and etc.) and that's way it's a good idea to have a look at this format by yourself. It's really cool messing with this file because you can add some odd character behaviors (for example see this video : http://youtu.be/xq0gw_AcNvo)
8. AMX format
*Not Finished*
I don't know much about this format. It seems the first part of it import some functions from the main executable and the second consist of machine instructions. Anywhere some functions from TRAOD.exe caught my eye:
_amx_Align16 004CC690
_amx_Align32 004CC6F0
_amx_Callback 004CC740
_amx_Debug 004CC780
_amx_Init 004CC790
_amx_NumPublics 004CCC30
_amx_GetPublic 004CCC50
_amx_FindPublic 004CCC90
_amx_Register 004CCD40
_amx_Exec 004CCE20
_amx_SetCallback 004CE010
_amx_RaiseError 004CE020
_amx_GetAddr 004CE030
_amx_StrLen 004CE050
_amx_SetString 004CE0C0
_amx_GetString 004CE150
_amx_StrPack 004CE900
_amx_StrUnpack 004CE9A0
_mapRegisterAMXMethods 004E8BE0
_amx_GetAddrVectRead 00503EF0
_amx_GetAddrVectWrite 00503F80
_amx_GetAddrMatrixWrite 00504020
_amx_CallbackVect 00504030
This really confirm my thesis that this is game dependant code.