When loading a save game, classic TR games read the name and number and then load everything after that directly into the game RAM, without checking anything. That's why savegames from original versions don't work in multipatched versions (and make the game crash). It also makes the “struct-ification” of the format very hard, as it would need decompiling the whole game (which is already being done for TR1 by some awesome people). The way the game works also makes the savegames extremely level-dependent, at least for the entity info part (which is yet to be documented here) as when creating a new game, the game simply loads the level data in RAM, and when saving it writes that down in a file. Understanding a savegame file completely therefore requires having the original level file the savegame is for.
struct TR1Savegame // 10675 bytes
{
uint8_t saveName[75]; // ASCII C string; null-terminated (there is always random data after \0, it is ignored); for accents see String Array section of TOMBPC.DAT page
uint32_t saveNumber; // game clips it to 16 bits
uint8_t unknown2[15];
// First level use block 0, second use block 1... so that at the end of the game,
// it sums up everything and/or checks if you found all secrets, etc
struct
{
uint16_t ammoPistols; // Always 1000
uint16_t ammoMagnums; // 65,535 means unlimited
uint16_t ammoUzis;
uint16_t ammoShotgun;
uint8_t smallMedipacks; // 255 means unlimited
uint8_t largeMedipacks;
uint8_t numScionPieces;
/* 0 None
* 1 Climbing
* 2 Draw weapon
* 3 Holster weapon
* 4 Combat (i.e. holding a weapon)
*/
uint8_t handStatus;
/* 0 None
* 1 Pistols
* 2 Magnums
* 3 Uzis
* 4 Shotgun
*/
uint8_t weapon; // current held weapon
/* 00000001 Level unlocked
* 00000010 Pistols
* 00000100 Magnums
* 00001000 Uzis
* 00010000 Shotgun
* 00100000 Midas Hand
*/
uint8_t flags;
uint8_t _padding;
} levelInitData[21]; // for the 21 levels (315 bytes)
uint32_t elapsedTime; // in game ticks (1/30th of a second), so divide by 30 for time in seconds
uint32_t kills;
uint16_t secretsFound; // bitmask of the discovered secrets of the current level
uint16_t levelNumber; // First level = 1
uint8_t numPickups;
uint8_t unlimitedAmmo; // Enabled = 1
uint8_t hasItem141; // From decompiled game source.
uint8_t hasItem142; // Strangely, those (unknown) items aren't present in any official TR1 level
uint8_t puzzles[4];
uint8_t keys[4];
uint8_t leadBar;
uint8_t levelInitDataCRC; // not implemented, always 0
// 10240 bytes starting from now
uint8_t roomsAreSwapped;
uint8_t flipFlags[10]; // = (FlipFlag & 0xFF00)>> 8
uint16_t cameraFlagsZoneIndices[cameraCount];
// items block here todo
#pragma pack(push, 8)
struct
{
int16_t itemID;
int16_t handStatus;
WeaponID currentWeapon;
WeaponID requestedWeapon;
int16_t climbFallSpeedOverride;
int16_t underwaterState;
int16_t unknown1;
int16_t collisionFrame;
int16_t collisionAxis;
int16_t air; // game ticks of air left
int16_t swimToDiveKeypressDuration; // game ticks
int16_t deadTime; // game ticks
int16_t underwaterCurrentStrength;
int16_t spasmEffectCounter; // e.g. when Lara is hit by lightning
Vertex4 *spasmSource; // from which direction Lara is hit
#pragma pack(push, 1)
struct MeshTree
{
int32_t replacedMeshesBits;
MESH_HEADER *meshes[15];
} laraMeshTree;
#pragma pack(pop)
ITEM *enemy;
Vertex2YX weaponTargetVector;
int16_t yRotationSpeed;
int16_t movementAngle;
Vertex2YXZ headRotation;
Vertex2YXZ torsoRotation;
AimInfo aimInfoLeft;
AimInfo aimInfoRight;
Ammo pistolAmmo;
Ammo magnumAmmo;
Ammo uziAmmo;
Ammo shotgunAmmo;
struct RoutePlanner
{
BoxNode *node;
uint16_t head;
uint16_t tail;
uint16_t searchNumber;
int16_t blockMask;
int16_t stepHeight;
int16_t dropHeight;
int16_t flyHeight;
int16_t zoneCount;
int16_t destinationBox;
uint16_t searchOverride;
Vertex4 searchTarget;
} aiInfo;
} lara; // 236 bytes
#pragma pack(pop)
int32_t postFxFunc; // this is usually -1 (and the last occurence of that in the file) so you can align it to that (replace the items blocks above by a filler block) so that Lara is aligned to postFxFunc
int32_t animFxTime;
};
enum WeaponID : int16_t
{
None = 0,
Pistols = 1,
Autopistols = 2,
Uzis = 3,
Shotgun = 4,
};
struct Vertex2
{
int16_t x;
int16_t y;
int16_t z;
};
struct Vertex2YX
{
int16_t y;
int16_t x;
};
struct Vertex2YXZ
{
int16_t y;
int16_t x;
int16_t z;
};
struct Vertex4
{
int32_t x;
int32_t y;
int32_t z;
};
struct Ammo
{
int32_t ammo;
int32_t hits;
int32_t misses;
};
#pragma pack(push, 8)
struct AimInfo
{
ANIM_FRAME *weaponAnimData;
int16_t frame;
int16_t aiming;
Vertex2YXZ aimRotation;
int16_t shootTimeout;
};
#pragma pack(pop)
The xxxxCount
values are available in the table below.
ID | Level | Filename | Camera count | Items | Secrets | Puzzle | Key |
| | | | | | 1 | 2 | 3 | 4 | 1 | 2 | 3 | 4 |
0 | Lara's Home | GYM | 1 | 1 | 0 | | | | | | | | |
1 | Caves | LEVEL1 | 2 | 60 | 3 | | | | | | | | |
2 | City of Vilcabamba | LEVEL2 | 6 | 95 | 3 | Gold Idol | | | | Silver Key | | | |
3 | Lost Valley | LEVEL3A | 6 | 64 | 5 | Machine Cog | | | | | | | |
4 | Tomb of Qualopec | LEVEL3B | 10 | 77 | 3 | | | | | | | | |
5 | St. Francis' Folly | LEVEL4 | 14 | 107 | 4 | | | | | Neptune Key | Atlas Key | Damocles Key | Thor Key |
6 | Colosseum | LEVEL5 | 10 | 86 | 3 | | | | | Rusty Key | | | |
7 | Palace Midas | LEVEL6 | 7 | 136 | 3 | Gold Bar | | | | | | | |
8 | The Cistern | LEVEL7A | 7 | 112 | 3 | | | | | Gold Key | Silver Key | Rusty Key | |
9 | Tomb of Tihocan | LEVEL7B | 15 | 88 | 2 | | | | | Gold Key | Rusty Key | Rusty Key | |
10 | City of Khamoon | LEVEL8A | 3 | 93 | 3 | | | | | Saphire Key | | | |
11 | Obelisk of Khamoon | LEVEL8B | 8 | 103 | 3 | Eye of Horus | Scarab | Seal of Anubis | Ankh | Saphire Key | | | |
12 | Sanctuary of the Scion | LEVEL8C | 12 | 74 | 1 | Ankh | Scarab | | | Gold Key | | | |
13 | Natla's Mines | LEVEL10A | 15 | 101 | 3 | Fuse | Pyramid Key | | | | | | |
14 | Atlantis | LEVEL10B | 6 | 192 | 3 | | | | | | | | |
15 | The Great Pyramid | LEVEL10C | 2 | 129 | 3 | | | | | | | | |
16 | Cut Scene 1 | CUT1 | 1 | 3 | N/A |
17 | Cut Scene 2 | CUT2 | 0 | 1 |
18 | Cut Scene 3 | CUT3 | 0 | 4 |
19 | Cut Scene 4 | CUT4 | 0 | 6 |
20 | Title | TITLE | 0 | 1 |
21 | Current Position | CURRENT | | |
The level “Current Position” (ID 21) seems to have been used internally during the development of the game, and its purposes are unknown. Its file “CURRENT.PHD
” isn't present in any of the official releases. TODO: Check in the betas.