{{indexmenu_n>11}}
====== Scripting in TR4 and TR5 ======
In this chapter we will describe full gameflow script specification for TR4/TR5 //script file// (usually called ''%%SCRIPT.DAT%%'') and //language file//, which contains all the strings used in game for specific language (e.g., ''%%ENGLISH.DAT%%'', ''%%FRENCH.DAT%%'', and so on).
===== The Script File =====
The script is divided into several blocks (or //headers//), some of them are global (applicable to whole game instance), and some are per-level only.
==== Global Header ====
This header contains general information not specific to particular level.
{{anchor:tr4_script_header}}
struct tr4_script_header // 9 bytes
{
uint8_t Options;
uint8_t Filler[3]; // Unused
uint32_t InputTimeout;
uint8_t SecurityTag;
}
''%%Options%%'' is a set of bit flags with several global game settings (name of the settings directly borrowed from original text scripts distributed with TRLE):
^ Hex ^ Bit ^ Description ^
^ ''0x0001'' ^ 0 | ''CheatEnabled'' -- Enables debug fly mode activated by typing ''%%DOZY%%'' ingame. |
^ ''0x0002'' ^ 1 | ''LoadSaveEnabled'' -- When this bit is not set, load and save features are disabled. This option was used for demo versions. |
^ ''0x0004'' ^ 2 | ''TitleEnabled'' -- Specifies if title screen should be displayed or not. If not set, game will start right away after user has launched an application. |
^ ''0x0008'' ^ 3 | ''PlayAnyLevel'' -- Gives an access to any level from the title screen. |
^ ''0x0070'' ^ 4 | ''Language'' -- Chooses which loading picture (US, GR, FR or UK) is showed |
^ ::: ^ 5 | ::: |
^ ::: ^ 6 | ::: |
^ ''0x0080'' ^ 7 | ''DemoDisc'' -- Unknown feature, probably related to game versions deployed on promotional CDs. |
''%%InputTimeout%%'': in early TR4 demos (for example, version dated September 15, 1999) this parameter specified time interval, after which game will engage pre-recorded rolling demo, in case there was no user input. This feature became useless in final version.
''%%SecurityTag%%'' parameter meant to be a special “key” value used to encrypt script data. Encryption is done with simple XOR operation against the data. However, this value was never used, and instead, hardcoded one was specified. This matter will be discussed later.
==== Level Header ====
This section defines platform-specific information, such as //file extensions// used in PC an PlayStation versions of the game. All the mentioned strings are null-terminated.
{{anchor:tr4_script_levelheader}}
struct tr4_script_levelheader
{
uint8_t NumTotalLevels;
uint8_t NumFilenames;
uint8_t Filler;
uint16_t LevelpathStringLen;
uint16_t LevelBlockLen;
uint8_t PSXLevelString [5]; // typically ".PSX"
uint8_t PSXFMVString [5]; // typically ".FMV"
uint8_t PSXCutString [5]; // typically ".CUT"
uint8_t Filler [5]; // Possibly for some additional extension type?
uint8_t PCLevelString [5]; // typically ".TR4"
uint8_t PCFMVString [5]; // typically ".BIK"
uint8_t PCCutString [5]; // typically ".TR4"
uint8_t Filler [5];
}
''%%NumTotalLevels%%'' is an amount of levels included in script. Title flyby is also counted.
''%%LevelpathStringLen%%'' is a sum of lengths of all //level path strings//, including ''%%0x00%%''s (empty ones).
''%%LevelBlockLen%%'' is a sum of lengths of each level script data length.
==== Level Listing Block ====
{{anchor:tr4_script_levellisting}}
struct tr4_script_levellisting
{
uint16_t OffsetsToLevelpathString[NumTotalLevels];
uint8_t LevelpathStringBlock [LevelpathStringLen];
uint16_t OffsetsToLevelData [NumTotalLevels];
}
Note that the offsets in the offset table themselves **are not** relative to the file address ''%%0%%''. The level-path offsets are relative to the first path string’s starting byte address ''%%(56 + NumTotalLevels * 2)%%'', while the level-data offsets are relative to the first level data’s starting byte address ''%%(56 +
NumTotalLevels * 2 + LevelpathStringLen + NumTotalLevels * 2)%%''.
It is also worth noting that the level-path strings in ''%%SCRIPT.DAT%%'' are ordered the same way they were ordered in corresponding ''%%[Level]%%'' blocks in uncompiled //SCRIPT.TXT//. For example, if the first //[Level]// in ''%%SCRIPT.TXT%%'' defines ''%%Level=DATA\TEST1,101%%'' and the second ''%%Level=DATA\TEST2,101%%'' — then there will be 2 level-paths in ''%%SCRIPT.DAT%%'', in the order such as this: ''%%DATA\\TEST1.DATA\\TEST2;%%'' where ''%%.%%'' is the null-terminator (''%%0x00%%'') byte.
To get to a certain level’s path within ''%%SCRIPT.DAT%%'' knowing only its number, just look-up at ''%%OffsetsToLevelpathString[LevelNum]%%'' and go to that offset (remember, it is //not relative// to file address ''%%0%%''!).
==== Level Block ====
Inside the level block, each level stores its own data describing certain parameters, such as level name, puzzle item names, load camera position, default background ambience soundtrack, and so on (the title level is no exception!).
While in ''%%SCRIPT.TXT%%'' each parameter was given its own line and position within the file itself, in ''%%SCRIPT.DAT%%'' this is not the case. Rather, bitfields are used for bool options (enabled/disabled; such as //Lightning// option) and the rest of the usually multi-byte data uses an //opcode data structure//.
That is, preceding a certain type of data you usually find a byte. That is the //opcode byte// — depending on its value, it can be determined what kind and how many arguments follow that need parsing. For example, chunk ''%%0x81%%'' indicates the level description opcode; with that info, the parser knows that 4 arguments follows: the string index, etc. This structure is somewhat akin to the //AnimCommands// structure of level files (see description above). The chunk order //does// matter; the original ''%%tomb4.exe%%'' binary seems to crash if something is not ordered the way it should be.
The title screen is special in that it uses the ''%%0x82%%'' opcode the indicate the level-name and audio track information and it, naturally, lacks the string index integer as the title level has no name associated with it.
{{anchor:tr4_script_leveldata}}
struct tr4_script_leveldata
{
uint8_t LevelData [LevelDataLen];
}
''%%LevelData%%'' is all of the level’s data continuously stored in memory. Number of level data sections is equal to overall amount of levels in game, and overall size of all level data sections comprise //Level Block//.
To get to a certain level’s data section, follow that particular level’s offset from inside the offset table you loaded (described above). The data sections themselves are ordered the very same way levels were ordered in ''%%SCRIPT.TXT%%''. For more info on the types of all available TR4 chunks and how to parse them, see the [[scripting_tr4_tr5#tr4_script_opcodes|Script Opcodes]] section.
==== Language File Listing Block ====
After the level block follows a simple array of ASCII strings which define //all the language files the game can choose from//. There are, however, no offset tables for this one, so one must simply read until a null-byte is reached, and then take that as the string and repeat onwards until EOF. Therefore, the last byte of ''%%SCRIPT.DAT%%'' must always be the null-terminator (''%%0x00%%'').
This setup is valid only for standard TR4 scripts generated by original TRLE script utility. //TRNG// scripts have their own special footer and data block appended to the bottom of the file, which contain all the extra information it needs.
{{anchor:tr4_script_opcodes}}
==== Script Opcodes ====
Here is a list of all available TR4 opcodes, their meaning and their corresponding arguments (order of arguments matters!):
It is important to note that the ''LoadCamera'' opcode has been removed in TR5, which means that all the other opcodes (>= 0x92) are shifted by 1).
^ TR4 ^ TR5 ^ Opcode ^ Arguments ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| ''0x80''|| ''FMV'' | ''uint8_t: 4 least significant bits represent the FMV index; 4 most significant bits (y) represent the FMV trigger bitfield as in y=1<->bit 8 set'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x81''|| ''Level'' | ''uint8_t stringIndex'' ||||||||||||||||||||| ''uint16_t levelOptions'' ||||||||||||||||||||| ''uint8_t pathIndex'' ||||||||||||||||||||| ''uint8_t audio'' |||||||||||||||||||||
| ''0x82''|| ''[Title] Level'' | ''uint8_t pathIndex'' |||||||||||||||||||||||||||| ''uint16_t titleOptions'' |||||||||||||||||||||||||||| ''uint8_t audio'' ||||||||||||||||||||||||||||
| ''0x83''|| ''LEVEL_DATA_END'' | None -- this opcode appears at the end of every level (incl. title) block ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x84''|| ''Cut'' | ''uint8_t cutIndex'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x85''|| ''ResidentCut1'' | ''uint8_t cutIndex'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x86''|| ''ResidentCut2'' |:::||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x87''|| ''ResidentCut3'' |:::||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x88''|| ''ResidentCut4'' |:::||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x89''|| ''Layer1'' | ''uint8_t red'' ||||||||||||||||||||| ''uint8_t green'' ||||||||||||||||||||| ''uint8_t blue'' ||||||||||||||||||||| ''int8_t speed'' |||||||||||||||||||||
| ''0x8A''|| ''Layer2'' |:::|||||||||||||||||||||:::|||||||||||||||||||||:::|||||||||||||||||||||:::|||||||||||||||||||||
| ''0x8B''|| ''UVrotate'' | ''int8_t speed'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x8C''|| ''Legend'' | ''uint8_t stringIndex'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x8D''|| ''LensFlare'' | ''uint16_t yClicks'' |||||||||||||| ''bit16 zClicks'' |||||||||||||| ''uint16_t xClicks'' |||||||||||||| ''uint8_t red'' |||||||||||||| ''uint8_t green'' |||||||||||||| ''uint8_t blue'' ||||||||||||||
| ''0x8E''|| ''Mirror'' | ''uint8_t room'' |||||||||||||||||||||||||||||||||||||||||| ''int32_t xAxis'' ||||||||||||||||||||||||||||||||||||||||||
| ''0x8F''|| ''Fog'' | ''uint8_t red'' |||||||||||||||||||||||||||| ''uint8_t green'' |||||||||||||||||||||||||||| ''uint8_t blue'' ||||||||||||||||||||||||||||
| ''0x90''|| ''AnimatingMIP'' | ''uint8_t: 4 least significant bits represent animatingObjectIndex - 1; 4 most significant bits represent the distance'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x91''| **XXX** | ''LoadCamera'' | ''int32_t srcX'' |||||||||||| ''int32_t srcY'' |||||||||||| ''int32_t srcZ'' |||||||||||| ''int32_t targX'' |||||||||||| ''int32_t targY'' |||||||||||| ''int32_t targZ'' |||||||||||| ''uint8_t room'' ||||||||||||
| ''0x92''| ''0x91''| ''ResetHUB'' | ''uint8_t levelIndex'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ''0x93''| ''0x92''| ''KEY_ITEM1'' | ''uint16_t stringIndex'' |||||||||||| ''uint16_t height'' |||||||||||| ''uint16_t size'' |||||||||||| ''uint16_t yAngle'' |||||||||||| ''uint16_t zAngle'' |||||||||||| ''uint16_t xAngle'' |||||||||||| ''uint16_t flags'' ||||||||||||
| ''0x94''| ''0x93''| ''KEY_ITEM2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x95''| ''0x94''| ''KEY_ITEM3'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x96''| ''0x95''| ''KEY_ITEM4'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x97''| ''0x96''| ''KEY_ITEM5'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x98''| ''0x97''| ''KEY_ITEM6'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x99''| ''0x98''| ''KEY_ITEM7'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9A''| ''0x99''| ''KEY_ITEM8'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9B''| ''0x9A''| ''KEY_ITEM9'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9C''| ''0x9B''| ''KEY_ITEM10'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9D''| ''0x9C''| ''KEY_ITEM11'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9E''| ''0x9D''| ''KEY_ITEM12'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0x9F''| ''0x9E''| ''PUZZLE_ITEM1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA0''| ''0x9F''| ''PUZZLE_ITEM2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA1''| ''0xA0''| ''PUZZLE_ITEM3'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA2''| ''0xA1''| ''PUZZLE_ITEM4'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA3''| ''0xA2''| ''PUZZLE_ITEM5'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA4''| ''0xA3''| ''PUZZLE_ITEM6'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA5''| ''0xA4''| ''PUZZLE_ITEM7'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA6''| ''0xA5''| ''PUZZLE_ITEM8'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA7''| ''0xA6''| ''PUZZLE_ITEM9'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA8''| ''0xA7''| ''PUZZLE_ITEM10'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xA9''| ''0xA8''| ''PUZZLE_ITEM11'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAA''| ''0xA9''| ''PUZZLE_ITEM12'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAB''| ''0xAA''| ''PICKUP_ITEM1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAC''| ''0xAB''| ''PICKUP_ITEM2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAD''| ''0xAC''| ''PICKUP_ITEM3'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAE''| ''0xAD''| ''PICKUP_ITEM4'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xAF''| ''0xAE''| ''EXAMINE1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB0''| ''0xAF''| ''EXAMINE2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB1''| ''0xB0''| ''EXAMINE3'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB2''| ''0xB1''| ''KEY_ITEM1_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB3''| ''0xB2''| ''KEY_ITEM1_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB4''| ''0xB3''| ''KEY_ITEM2_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB5''| ''0xB4''| ''KEY_ITEM2_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB6''| ''0xB5''| ''KEY_ITEM3_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB7''| ''0xB6''| ''KEY_ITEM3_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB8''| ''0xB7''| ''KEY_ITEM4_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xB9''| ''0xB8''| ''KEY_ITEM4_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBA''| ''0xB9''| ''KEY_ITEM5_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBB''| ''0xBA''| ''KEY_ITEM5_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBC''| ''0xBB''| ''KEY_ITEM6_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBD''| ''0xBC''| ''KEY_ITEM6_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBE''| ''0xBD''| ''KEY_ITEM7_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xBF''| ''0xBE''| ''KEY_ITEM7_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC0''| ''0xBF''| ''KEY_ITEM8_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC1''| ''0xC0''| ''KEY_ITEM8_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC2''| ''0xC1''| ''PUZZLE_ITEM1_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC3''| ''0xC2''| ''PUZZLE_ITEM1_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC4''| ''0xC3''| ''PUZZLE_ITEM2_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC5''| ''0xC4''| ''PUZZLE_ITEM2_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC6''| ''0xC5''| ''PUZZLE_ITEM3_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC7''| ''0xC6''| ''PUZZLE_ITEM3_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC8''| ''0xC7''| ''PUZZLE_ITEM4_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xC9''| ''0xC8''| ''PUZZLE_ITEM4_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCA''| ''0xC9''| ''PUZZLE_ITEM5_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCB''| ''0xCA''| ''PUZZLE_ITEM5_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCC''| ''0xCB''| ''PUZZLE_ITEM6_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCD''| ''0xCC''| ''PUZZLE_ITEM6_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCE''| ''0xCD''| ''PUZZLE_ITEM7_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xCF''| ''0xCE''| ''PUZZLE_ITEM7_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD0''| ''0xCF''| ''PUZZLE_ITEM8_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD1''| ''0xD0''| ''PUZZLE_ITEM8_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD2''| ''0xD1''| ''PICKUP_ITEM1_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD3''| ''0xD2''| ''PICKUP_ITEM1_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD4''| ''0xD3''| ''PICKUP_ITEM2_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD5''| ''0xD4''| ''PICKUP_ITEM2_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD6''| ''0xD5''| ''PICKUP_ITEM3_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD7''| ''0xD6''| ''PICKUP_ITEM3_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD8''| ''0xD7''| ''PICKUP_ITEM4_COMBO1'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| ''0xD9''| ''0xD8''| ''PICKUP_ITEM4_COMBO2'' |:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||:::||||||||||||
| **XXX** | ''0xD9''| ''GiveItemAtStartup'' | ''uint16_t itemNumber'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|:::| ''0xDA''| ''LoseItemAtStartup'' | ''uint16_t itemNumber'' ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
The ''%%uint16_t%%'' values ''%%levelOptions%%'' and ''%%titleOptions%%'' are actually //bit fields// containing several boolean options, and are laid out as follows (per-bit description):
* //Bit 0// (''%%0x0001%%'') — YoungLara
* //Bit 1// (''%%0x0002%%'') — Weather
* //Bit 2// (''%%0x0004%%'') — Horizon
* //Bit 3// (''%%0x0008%%'') — Layer1 used
* //Bit 4// (''%%0x0010%%'') — Layer2 used
* //Bit 5// (''%%0x0020%%'') — Starfield
* //Bit 6// (''%%0x0040%%'') — Lightning
* //Bit 7// (''%%0x0080%%'') — Train
* //Bit 8// (''%%0x0100%%'') — Pulse
* //Bit 9// (''%%0x0200%%'') — ColAddHorizon
* //Bit 10// (''%%0x0400%%'') — ResetHUB used
* //Bit 11// (''%%0x0800%%'') — LensFlare used
* //Bit 12// (''%%0x1000%%'') — Timer
* //Bit 13// (''%%0x2000%%'') — Mirror used
* //Bit 14// (''%%0x4000%%'') — Remove Horus amulet from inventory
* //Bit 15// (''%%0x8000%%'') — NoLevel
===== The Language File =====
In contrary to TR2 and TR3, TR4 uses a more sophisticated language-handling scheme. Instead of storing the strings in ''%%SCRIPT.DAT%%'' for every different language, TR4 splits the string definition (''%%{LANGUAGE}.DAT%%'') and script definition (''%%SCRIPT.DAT%%'') data into the two mentioned files. This allows for smaller files, finer grain of selectivity and easy localization.
This means that, within ''%%SCRIPT.DAT%%'', strings are always given as string indices, i.e. numbers that correspond to the array positions of the corresponding strings within ''%%{LANGUAGE}.DAT%%'', where ''%%{LANGUAGE}%%'' can be any supported language filename.
From these files, the game will choose the first one that is available and use that as the //string resource//. See below for details on string selection.
==== Language File Priority ====
The number of supported language files depends on what was defined in ''%%SCRIPT.TXT%%'', in the //[Language]// section. Also, the priority of loading is specified there (the first number before the comma). For example, if we have defined:
[Language]
File= 0,ENGLISH.TXT
File= 1,FRENCH.TXT
File= 2,GERMAN.TXT
File= 3,ITALIAN.TXT
File= 4,SPANISH.TXT
File= 5,US.TXT
…that would mean that the game will first look for ''%%ENGLISH.DAT%%'' for loading. If that’s not present, it will look for ''%%FRENCH.DAT%%''. If not, it’ll look for ''%%GERMAN.DAT%%'', and so on. If none of the files are present, the game will crash. In ''%%SCRIPT.DAT%%'', these numbers reflect on the order of file name strings: in the above situation, the //language file listing block// at the end of ''%%SCRIPT.DAT%%'' would look like this (highest→lowest priority):
ENGLISH.DAT FRENCH.DAT GERMAN.DAT ITALIAN DAT SPANISH.DAT US.DAT.
…where the splitting space between filenames specifies the null-terminator (0x00) byte.
==== Language File Structure ====
=== The Header ===
The header of the language file follows this structure:
{{anchor:tr4_lang_header}}
struct tr4_lang_header
{
uint16_t NumGenericStrings;
uint16_t NumPSXStrings;
uint16_t NumPCStrings;
uint16_t GenericStringsLen; // including the null-terminator bytes
uint16_t PSXStringsLen; // including the null-terminator bytes
uint16_t PCStringsLen; // including the null-terminator bytes
uint16_t StringOffsetTable[];
}
''%%StringOffsetTable%%'' is a table holding offsets which point to corresponding strings. Therefore, its size is ''%%NumGenericStrings + NumPSXStrings + NumPCStrings%%''.
Offsets in the offset table themselves //are not// relative to the file address 0! They are actually relative to the first string’s starting byte address!
In order to get an absolute offset of a string whose relative offset you retrieved from the offset table, do the following:
''%%absoluteOffset = relativeOffset + sizeof(tr4_lang_header)%%''
''%%sizeof(tr4_lang_header)%%'' depends, of course, on the number of strings in each group. Therefore, the header size is ''%%sizeof(uint16_t) * 6 + sizeof(OffsetTable)%%''.
=== String Data ===
In the usual TR4 situation, there are typically //359 strings// (that is, usually //NumTotalStrings = NumGenericStrings + NumPSXStrings + NumPCStrings = 359//) defined. This, however, is //not// a limit nor rule of any kind.
All the strings defined within ''%%{LANGUAGE}.DAT%%'' files are ASCII null-terminated strings. Every character (byte) contained in such a string is XOR-ed with byte ''%%0xA5%%'' (as mentioned above, it is done regardless of what byte was specified in ''%%SCRIPT.TXT%%'' under the //Security// option).
The null-termination byte is //not// being XOR-ed!
After the above defined header section goes an array of strings, in a predefined order: //Generic → PSX → PC//.
The length of this array of total //(NumTotalStrings)// strings is therefore //TotalStringsLen = GenericStringsLen + PSXStringsLen + PCStringsLen//.
Hence the string array has the following format:
{{anchor:tr4_lang_stringdata}}
struct tr4_lang_stringdata
{
string_entry Strings[NumTotalStrings];
}
where ''%%string_entry%%'' is simply a ''%%char%%'' array, whose length depends on the corresponding string’s length. That can be calculated by subtracting the next string’s by the current string’s offset.