Save hacking 

Posted on: Jun 22, 2019 5:37 PM UTC

Contents

This article is fully software-centered.
I will describe how to edit PS1 game saves.
If you change the context according to your needs, this tutorial would apply to any saved file though.
Here is a quick reminder about PS1 memory cards.
A whole memory card can contain 128 KiB of data.
This size is divided in 16 blocks: the first one contains the memory card metadata and the 15 remaining blocks contain game data.
The size of each block is 8 KiB, which is 8192 bytes.
Saves can only use whole blocks, so they take a multiple of 8192 bytes.
Those n*8192 bytes are divided into two parts: the header (icons included) and the game data.
The header is 128*(p+1) bytes wide, with p being the number of icons of the save.
In this article we will temper with the remaining n*8192 - (p+1)*128 bytes of game data.
There are 3 possible ways to do this:
  • use an already described save format
  • decompile the game
  • use an hex editor and understand the structure

Already described save format

If you are lucky your game saves format has already been found.
For example, for Final Fantasy VII you can take a look here.
Then you just have to edit the relevant bytes to do what you want.

Decompiling the game

I won't write much about this as I didn't do it yet.
The games are loading the needed data from the saves so if you find the loading function, you may be able to understand the structure by looking at the surroundings.
For this you can use IDA Pro if you happen to have a (costly) licence, or better, Ghidra.

Guessing

This is the actual goal of the article.
I use Metal Gear Solid as an example.
I think everybody reading a PS1 website in 2019 knows what this game is, if not: it's a game about a guy infiltrating a base and carrying weapons and stuff.

What are we looking at?

Before editing you need to understand what is inside the file.
For this, open the data with an hex editor (I personally love wxHexEditor) and take a look.
MGS has 1-block (n = 1) wide saves with one static icon (p = 1) so there are 8064 bytes to analyze.
Look at them to already spot some patterns.
PS1 header in red.
One icon in blue.
Game data in green.
The red dots roughly represents the separation of the replicas.
In this block, you can see the data is replicated three times (check with a script) and there are blocks of 0x00 bytes (which sometimes means an unused byte, just like 0xff).
Each replica is 2624 byte long.
This leaves 8192 - (3*2624 + (p+1)*128) = 64 bytes at the end of the file, which are always 0x00 in my saves.
We already found a potential structure: [header + icon] [data] [data] [data] [0x00 * 64]
I can go a bit further:
  • The first 4 bytes of data are always 0x40 0x0A 0x00 0x00 in my saves.
    0x40 0x0A 0x00 0x00 means 0x00000A40 in little endian and 0x00000A40 = 2624, which is unsurprisingly the assumed size of data.
  • Also we see some readable strings: CAMPBELL, OTACON, etc.
    Those are people you can call with your radio.
    Finding the pattern here is easy: 2 bytes + 18 bytes of name, right-padded with 0x00.

Find atomic differences

One good way of understanding the format is by comparing different files.
Comparing files from hugely different parts of the game will likely results in a huge number of hits.
Instead try saving twice in a row: you often end up with info like checksums and game internal time.
Here is the diff between two such saves.
I manually highlighted two bytes because of a visual bug of my editor.
We can see four affected places, let's focus on the first three.
Their sizes are 4, 2 and 1 byte long, let's name them P1, P2 and P3.
Repeat the process and you will observe that:
  • P1 is always completely different from one save to another, also it is at the beginning of the data.
  • P2 can actually be 3 byte long if you wait enough between saving.
  • P3 always differs by 1 between two consecutive saves.
This is enough info to be confident about their roles.
  • P1 seems to be a 32bit checksum.
  • P2 seems to be related to elapsed time.
  • P3 seems to be the number of times the player saved, which is by the way a displayed value at the end of the game.
Proving you are right depends on the situation: editing P2 and P3 just before the end of the game should show you the edited values when completing the game.
I did, and when trying to load the edited save, I got a loading error message.
That's unfortunate but expected, developpers often check consistency before using data.

Find the checksum function, if any

To guess what checksum is performed, one needs to think about the context: the console was launched in 1994 (its CPU even before) and the game was launched in 1999.
Obviously you won't find SHA3 in here.
You'll instead encounter things like 8bit XOR checksum and 32bit CRC32.
CRC32 seems a decent enough candidate in our case as the output is 4 bytes.
Next you need to choose what data to hash, and as the game data is replicated three times, it would be logical to hash only one replica, with the checksum (and data size, which is just before) being of course stripped out.
When we hash this data we obtain 0xC5520176, which corresponds to 0x76 0x01 0x52 0xC5 (little endian), which is the P1 value.
Finally we can edit the save: first edit what you want then recalculate the checksum accordingly.

Search everything you know is saved

Now that we can edit the save and make the game load it, we need to actually decipher the data.
In order to do this we will search inside the data some known values.
For example for this game, saved data include ammunition, objects, location, rations used, number of deaths, number of kills, etc.
Just keep in mind that some games (including this one) only save the state of the game when you enter a new area, so you have to change your location before saving for the values to change.
I'll take one example: ammunition of the rifle.
Although usual integers in the PS1 are 4 byte long, small values might be half of that or even be a single byte.
I know the number of bullets can be at least 451 so the value must take at least 2 bytes.
In this save I have 295 bullets (0x0127, i.e. 0x27 0x01 in little endian).
When I search for 0x27 0x01 in the memory card content, I found only one result (actually 3 as data is replicated 3 times) at the offset 0x164 of the whole file (it's offset 0x164 - (n+p)*128 = 0x64 of data).
Changing this value to 0x34 0x12 (and recalculating the checksum) gives us 0x1234 = 4660 bullets.
Keep in mind that the value you search for is critical, looking for 0x01 or even 0x00 would have brought less success.

Random editions

Once you found everything you know exists, the remaining unknown data have to be tested by editing it.
This is known as fuzzing.
I don't have much to say about this, just open your hexadecimal editor, change random bytes, fix the checksum, load the edited file and look for what have changed.
In other situations you can even script the whole process: editing the data, loading it, performing actions and finding patterns in the resulting situation.