1. Units

The game consists of units, these units are connected to eachother using portals and contain all objects.

1.1. Unit list

Units are retrieved from TRAE\PC-W\objlist.dat (not unitlist.txt) and then stored in memory

struct UnitList

{

        uint32_t NumUnits;

        UnitInfo[] Units;

};

struct UnitInfo

{

        char Name[16];

        uint32_t Unk1;

};

1.2. Stream list

The game can hold up to 8 units in memory, it will automatically remove unused units while loading a new one.

StreamUnit StreamList[8]



struct StreamUnit

{

        uint32_t UnitId;

        uint8_t Unk1;

        uint8_t Unk2;

        uint16_t Flags;

        uint32_t Unk3;

        char Name[20];

        // TODO

};

2. Scripts

Every unit contains a script which control the level flow e.g. switches, doors, cinematics etc, these scripts are PE files exporting functions called by the game.

Scripts can be found extracting the unit DRM file using https://github.com/Gh0stBlade/cdcEngineTools and looking for a .gnc file with MS-DOS stub (This program cannot be run in DOS mode), next remove the .SECT header and drop the file in your favorite disassembler e.g. IDA Pro, Ghidra.

2.1. Exports

The following symbols are usually exported:

  • EventMain

  • EventRelocate

  • CallTrigger

  • IsTriggerActive

  • EventDebug

The last EventDebug is not a function but instead array of debug strings.

// from pu1

char* EventDebug[8] = { "doorsopened", "pusha",

                        "pushb", "introcine",

                        "tutorial_start", "doorreveala",

                        "level_title_start", "sherpasilent" };

                        // [...]

The EventMain function will be called from the game loop every loop which is where most of the logic is.

int EventMain(GameTracker* a1, StreamUnit* a2, int a3)

{

        (a3 + 1512)("02_CN_Hotel", 0); // StartBlockingCine

}

a3 in this function is a pointer to a table where functions and variables are stored for the scripts.

2.2. Example

It is possible to hook the EventMain function and implement your own logic, an example of this in the video below.

In this video EventMain is hooked to the function below

int HookedEventMain(int gametracker, int unit, int a3)

{

        auto instanceFind =

                ((int(__cdecl*)(int)) *(DWORD*)(a3 + 252));

        auto instanceQuery =

                ((int(__cdecl*)(int, int)) *(DWORD*)(a3 + 236));

        auto instancePost =

                ((void(__cdecl*)(DWORD, DWORD, int)) *(DWORD*)(a3 + 240));

        auto instanceKill =

                ((void(__cdecl*)(DWORD, int)) *(DWORD*)(a3 + 352));



        // call instanceFind to get an instance of level object by a fixed 'intro' id

        auto lever = instanceFind(3367);

        auto door = instanceFind(3349);



        // query the lever until it returns 1

        if (lever && instanceQuery(lever, 233) == 1)

        {

                // post to the door to open

                instancePost(door, 8388753, 0);

        }

}

Another example below, this turns the door security light to green after the player have shot the target behind the painting.

auto target = instanceFind(4568); // sw_ma_shootable_switch

auto light = instanceFind(4603); // ae_ma_security_light



if (target && instanceQuery(target, 245) == 1)

{

        instancePost(light, 8388752, 1);

}

2.3. Function table

The third parameter in EventMain will always be a pointer to a table of functions and other data which the game scripts will use. Below a table of known offsets.

Offset

Name

Description

224

Version

4103 on both PC and PS2, scripts return "wrong event version for pu1\n" if it doesn’t match

236

InstanceQuery(Instance* instance, int unk1)

Get a value from an instance e.g. to query a lever

if(instanceQuery(lever, 233) == 1)

{

        printf("open the door!\n");

}

240

void InstancePost(Instance* instance, int unk1, int data)

Post data to an instance

252

Instance* InstanceFind(int intro)

Find an instance by unique intro id

280

int unk(Instance* a1, Instance*, a2, int a3)

This is something related to anim data, in unit scripts this data is passed to InstanceSetEventAnimPlaying

464

void nullsub(char*)

nullsub, this is called by unit scripts to log debug messages. this video previews a playthrough with debug prints in St. Francis Folly.

716

void InstanceSetEventAnimPlaying(Instance* instance, int a2)

Plays the anim data on an instance

1016

void SwitchChapter(char* chapter)

Switches to a chapter by name (chapter0, chapter1, chapter2, chapter3, chapter4)

1308

char IsPs2()

Hardcoded to return 0 on PC

1340

void HideUnhideDrawGroup(Instance* instance, int drawGroup, int on)

Hide or unhide an draw group

1512

int EVENT_StartBlockingCine(char* cine, bool loop)

Starts a cinematic, example from cn2

(a3 + 1512))("02_CN_Hotel", 0); // EVENT_StartBlockingCine

1848

void EVENT_PlayerTurnGold()

Starts the player turn gold wet effect, calling this outside of gr18 (Midas Garden) crashes the game

2.4. PS2 version

On the PS2 versions scripts are still in PE format but MIPS instructions instead of x86.

The event version is the same as on PC 4103

loc_10002038:

lw      $t6, 0xE0($s2)

li      $at, 0x1007 ; 4103

beq     $t6, $at, loc_10002064 ; continue

nop

If you are not familiar with MIPS beg checks if ($t6 == $at) go to loc_10002064 where loc_10002064 continues

3. Instance

Every object, enemy or player is an instance, the game can hold up to 208 instances in memory. Examples of instances are:

  • Lara

  • Levers, pressure plates and doors

  • Ropes and swing poles

  • Inventory items and grappling hook

  • Enemies

  • Water

Below images are taken without any instances loaded (minus lara)

images/1.jpg Doors, levers and movable platforms missing

images/2.jpg

images/3.jpg

images/4.jpg Water missing

A picture with instances highlighted by their name draw at the position.

images/instances.png

3.1. Offsets

Offset

Description

16

Position

48

Rotation

80

Scale

96

Shadow position

148

Pointer to the object

177

Model id

236

Instance id

448

data

464

Intro id

572

extraData

data and extraData can for different instances have different data an example of data is for enemies offset 72 which is the health.

3.2. Query, post and process

While interacting with instances you often call InstancePost and InstanceQuery, underwater these actually call the queryFunc and messageFunc for the object for example for switches this is SWITCH_Query.

Each object also has a processFunc which is always called.

3.3. Instances Messages

3.3.1. Player

Message

Description

Data

262256

Give inventory item

The gear id of the item (see [_inventory_item_ids])

262261

Select gear item

The inventory item id

3.3.2. Switch

Message

Description

Data

8388753

Toggle the switch to a direction

The direction to rotate the switch to, 1 = down, 2 = up

3.4. Creation

An instance can be spawned by calling one of the game’s 'birth' functions, the game will check if there is space in the instance pool (else return 0) and get the next instance id.

// generated by IDA decompiler

result = (_DWORD *)gInstances;

if ( !gInstances )

        return 0;

gInstances = *(_DWORD *)(gInstances + 8); // next instance

v1 = dword_817D64;

v2 = dword_817D64 == 0;

dword_817D64 = result;

result[2] = v1;

if ( !v2 )

v1[3] = result;

result[3] = 0;

result[59] = gNextInstanceId++; // assign next instance id to instance

++gNumInstances;

return result; // return instance

4. Inventory

4.1. Inventory item ids

Id

Description

0

Pistols (handgun_rbweapon)

1

50 Caliber Pistols (magnum_rbweapon)

2

Mini SMGs (uzi_rbweapon)

3

Shotgun (shotgun_rbweapon)

4

Grapple hook (grapple_hook)

5

Large healthpack (g_healthpack)

6

Small healthpack (g_healthpack_small)

7

8

Cog (ip_pu_cog)

9

Eye of Horus (ip_eg_eyeofhorus)

10

Scarab of Osiris (ip_eg_scarab)

11

Seal of Anubis (ip_eg_sealofanubis)

12

Ankh of Isis (ip_eg_ankhwall)

13

Ankh key (ip_eg_ankh)

14

Key of Atlas (ip_gr_key_a)

15

Key of Damocles (ip_gr_key_d)

16

Key of Hephaestus (ip_gr_key_h)

17

Key of Poseidon (ip_gr_key_p)

18

Balcony key (ip_gr_key)

19

Scion of Qualopec (ip_scion_q)

20

Scion of Natla (ip_gr_n)

21

Scion of Tihocan (ip_gr_t)

22

Lead bar (ip_gr_bar_lead)

23

Gold bar (ip_gr_bar_gold)

24

Red Fuse (ip_lc_fuse_r)

25

Green Fuse (ip_lc_fuse_g)

26

Blue Fude (ip_lc_fuse_b)

27

Village key (ip_pu_trextooth)

28

Decorative Bow (ip_ma_box)

29

Decorative Arrow (ip_ma_arrow)

30

Sculpture Gear (ip_ma_gear)

31

Empty bucket (ip_ma_bucket)

32

Bucket of Water (ip_ma_bucketw)

33

Music Box Cylinder (ip_ma_pianospool)

34

Sundial Gnomon (ip_ma_gnomon)

35

Wrench (ip_ma_wrench)

36

Grapple (ip_ma_grapple)

37

Journal (ip_ma_journal)

38

Maze map (ip_ma_maze_map)

39

ip_lc_drillkey, does not exist in the game files