You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a standalone savegame editor for Tomb Raider III. It has been rigorously tested to work with the Steam version of the game, but it should work on
the original and multi-patched versions as well. Be sure to back up your save game files as a precaution.
This editor can enable any weapon on any level, including the bonus level. No setup is necessary, simply download and run.
For a more robust and well-maintained savegame editor that supports the entire Tomb Raider classic series, check out TR-SaveMaster.
Installation and use
To download and use this savegame editor, simply navigate to the Release folder, then download TRIII-SaveEdit.exe.
You can store the .exe file anywhere on your computer. Once downloaded, launch the editor and click "Browse" to locate your game directory. Your game directory location depends on whether you did a Steam install
or a CD install.
Steam install directory: C:\Program Files (x86)\Steam\steamapps\common\TombRaider (III)
CD install directory: C:\Program Files (x86)\Core Design\Tomb Raider III
Once your savegame is selected, you can then change weapons, ammo, and health. Click "Save" to apply your changes, and enjoy. Do note that ammunition for weapons not currently equipped may appear differently than in-game.
Harpoons are grouped in bundles of 2, Desert Eagle clips equate to 5 bullets, MP5 clips equate to 30 bullets, Uzi clips equate to 20 bullets, and a single box of grenades is equivalent to 2 rounds of Grenade Launcher ammunition.
Reading weapons information
Unlike Tomb Raider: Chronicles, the save file offsets in Tomb Raider III are stored differently on each level. Another interesting difference is that
instead of storing weapons on individual offsets, all weapons information is stored on a single offset, which I call weaponsConfigNum.
The only exception is the Harpoon Gun, which is stored on its own offset, and is of boolean type. The weapons configuration variable has a
base number of 1, which indicates no weapons present in inventory. Each weapon adds a unique number to the variable.
weaponsConfigNum
Weapon
Unique number
Pistols
2
Desert Eagle
4
Uzis
8
Shotgun
16
MP5
32
Rocket Launcher
64
Grenade Launcher
128
When the game loads a save file, it checks this number to determine which weapons are present in inventory. When reverse
engineering this part of the game, we need to choose our methods wisely. Since there are 128 possible combinations, using conditional
operations would be extremely inefficient. Bitwise operations are perfect for this scenario. We account for the base case of no weapons,
and then we use our else block to check if the unique bit of a weapon is present in the config variable, and then set our values to display accordingly.
When storing our new weapons configuration, we just perform the same operations in reverse.
We start with the base number of 1, and then increment based on the checked weapons.
Lastly, we write the calculated number to the save file.
byte newWeaponsConfigNum = 1;
if (chkPistols.Checked) newWeaponsConfigNum += 2;
if (chkDesertEagle.Checked) newWeaponsConfigNum += 4;
if (chkUzis.Checked) newWeaponsConfigNum += 8;
if (chkShotgun.Checked) newWeaponsConfigNum += 16;
if (chkMp5.Checked) newWeaponsConfigNum += 32;
if (chkRocketLauncher.Checked) newWeaponsConfigNum += 64;
if (chkGrenadeLauncher.Checked) newWeaponsConfigNum += 128;
WriteByte(weaponsConfigNumOffset, newWeaponsConfigNum);
Calculating ammunition offsets
Ammunition is stored on up to two different offsets. It is always stored on a lower offset, which I call the primary ammo offset, and then it is
stored on an additional offset, which I call the secondary ammo offset. If the respective weapon is not in inventory, then the ammo is only stored on
the primary offset. If the weapon is equipped, then the ammo is stored on both offsets. There can be anywhere from 1-9 different secondary ammo offsets
per level.
The "correct" secondary ammo offset changes throughout the level, and seems to depend on the number of active entities in the game.
Writing to incorrect or multiple secondary offsets typically results in the game crashing upon loading. To determine which secondary offset is the correct
one to write to, we take the base secondary offset and loop through the potential secondary offsets, using 0x12 as an iterator.
We then check the current ammo index with GetAmmoIndex() and add both the primary and secondary offsets to our list, and return as an array.
public int[] GetValidAmmoOffsets(int primaryOffset, int baseSecondaryOffset)
{
List<int> secondaryOffsets = new List<int>();
List<int> validOffsets = new List<int>();
int currentAmmoIndex = GetAmmoIndex();
for (int i = 0; i < 10; i++)
{
secondaryOffsets.Add(baseSecondaryOffset + i * 0x12);
}
validOffsets.Add(primaryOffset);
validOffsets.Add(secondaryOffsets[currentAmmoIndex]);
return validOffsets.ToArray();
}
Determining the current ammo index
The ammo index is based on the number of active entities in the game. If there are no active entities,
the ammo index is 0. If there is 1 entity, the index is 1, and so on. Ammo indices are also uniform,
meaning if the current ammo index for MP5 is 2, then the ammo index for the shotgun and other weapons is also 2.
One possible way to determine the current ammo index would be to reverse the entity data structures, and find
common byte flags which may identify them as active entities. Since there are a total of 32 entities in the game,
this would be an extremely daunting task.
Fortunately, I was able to identify certain bytes in the savegame files that shift consistently along with the ammo index.
I am not entirely sure what this data represents, but it seems to correlate with entity data. It's a 4-byte array
consisting of {0xFF, 0xFF, 0xFF, 0xFF} just preceeding the null padding. Here's what that data looks like on the same
level with an ammo index of 0 (left) versus an ammo index of 1 (right).
So we take note of the 0xFF array locations for the different ammo indices for each level, then store that data in a dictionary. There are 20
levels and the arrays are stored differently on each one. We then call the dictionary in GetAmmoIndex() to check where the 0xFF array
is currently located. We will then know what the current ammo index is.
public int GetAmmoIndex()
{
string lvlName = GetCleanLvlName();
int ammoIndex = 0;
if (ammoIndexData.ContainsKey(lvlName))
{
Dictionary<int, int[]> indexData = ammoIndexData[lvlName];
for (int i = 0; i < indexData.Count; i++)
{
int key = indexData.ElementAt(i).Key;
int[] offsets = indexData.ElementAt(i).Value;
if (offsets.All(offset => ReadByte(offset) == 0xFF))
{
ammoIndex = key;
break;
}
}
}
return ammoIndex;
}
Determining the correct health offset
Health is stored on anywhere from 1 to 3 different offsets per level. The exact offset it is written to changes
as you progress through a level. Writing to the incorrect offset may crash the game or lead to other glitches.
To get around this issue, this savegame editor stores the known health offsets for each level on an array.
When pulling health information, it loops through the known health offsets, and does a couple of heuristic
checks to see which is the correct health offset. First, it checks for impossible health values (0 or greater than 1000).
Then, it checks the surrounding data. Since health is always stored 8 bytes away from the character animation data,
it checks those addresses for character animation byte flags. If a match is found, it returns the correct health offset.
public int GetHealthOffset()
{
for (int i = 0; i < healthOffsets.Count; i++)
{
int healthValue = ReadUInt16(healthOffsets[i]);
if (healthValue > MIN_HEALTH_VALUE && healthValue <= MAX_HEALTH_VALUE)
{
byte byteFlag1 = ReadByte(healthOffsets[i] - 10);
byte byteFlag2 = ReadByte(healthOffsets[i] - 9);
byte byteFlag3 = ReadByte(healthOffsets[i] - 8);
if (IsKnownByteFlagPattern(byteFlag1, byteFlag2, byteFlag3))
{
return healthOffsets[i];
}
}
}
return -1;
}
Offset tables
Aside from the level name and save number, the save file offsets differ on every level. The offsets for small medipack
and large medipack are 1 byte away. Flares is 2 bytes away from large medipack, and the weapons config number is 4 bytes away
from flares. Next to the weapons config number is the harpoon gun offset. The first ammo offset listed on each
table is the primary ammo offset, and the next one is the secondary base offset.