Due to a request by RyuKuro Ryu I digged a bit more inside the structure of the P3 FES save file format, looking for a way to find out about the checksum which detects tampering with the save games and prevents them from loading with an error message such as “Data is corrupted and can’t be loaded”. Here are the findings which led to a way to change save games and keep them valid.
Bytes can be exchanged safely
It is safe to exchange pairs of bytes in the file, and the save will still load (and the values will be exchanged). This can be verified for example by changing the stats of a Persona (e.g. exchange Strength and Magic values). This hints at the checksum being either a parity byte or a modular sum (see the wikipedia article). A more sophisticated algorithm such as CRC would not be fooled by exhanging bytes.
The checksum is computed as a modular sum
This can be verified in the following fashion: Take the Strength and Magic values of one Persona you have, change the Strength in the save to be 10 less and add 10 to the Magic value. This file will load correctly and the values will be changed. This is due to the checksum being a sum of all bytes in certain areas of the file. If you subtract 10 from this sum and then add 10 again, the result is unchanged.
Keeping edited saves valid
The checksum can be found by comparing two very similar saves (e.g. just stand around for a minute and then save again). It is right at the end infront of 4 FF bytes, at offset 0x8D51. It is a modular sum, but it’s not the sum of all bytes in the file up to that point.I don’t think it’s easy to find out how the checksum is computed overall, there would be a lot of trial-and-error involved. But if the goal is to load a file, exchange information and then write the result back into a valid save file, it’s possible to keep the save valid. This is done the following way: Keep track of changes to the checksum in a variable. Initially, the variable is 0. After each change, the change is added (modulo 255) to the variable, keeping it in the correct range (0 to 255). After all changes have been made, the original checksum of the save file is off by this amount. To be valid, the checksum has to be corrected by adding the change to it.
Adding the accumulated changes to this variable will keep it valid with all changes intact.
In a hex editor
To apply changes in a hex editor, the following can be done: In a hex editor that supports checksum generation (e.g. the int8 checksum in Hex Workshop), use this feature to generate the checksum of almost the complete file until the old checksum, from offset 0x0 to 0x8D50. Make a note of this checksum. Then, apply all changes to the file, and after the last change, generate the checksum again. Then, subtract the first checksum from the second. This will result again in the change of checksum that has to be added to the original checksum (the value at 0x8D51) and entered at that offset. This works as the effect of the bytes that were not changes is cancelled out by the subtraction.
I took the chance to finally try out Silverlight after programming in WPF for some time now, and the result is a small helper applet to be used when editing a Persona 3 FES file. Just enter the checksum found in the file before any editing in the first field, and for each byte that you change while editing, enter the old and new value and commit the change by pressing the button. At the bottom of the applet, the checksum that has to be entered in the file after all changes are made at the offset mentioned above is shown. All input values and the checksum are entered/displayed as hexadecimal values, so you can just copy them from/into a hex editor.
Update: Changing the player level
As noticed by RyuKuro Ryu, the actual value to change in order to have the protagonist’s level change is at 0x72. The game will show the changed level upon loading, but after a fight, the game will reset the level to the old level.
Here’s what happens: Whenever you win a fight and gain XP, the game adds it to your current XP and checks if you should level up. It doesn’t care about the variable with the level at that moment, but just uses the amount of XP. If you don’t have the amount of XP you should have for your level, you will “gain” a lower level. The XP (all the XP you have gained in the game, not just since the last level) is an int32 value at 0xB0. I haven’t found a list with the experience per level on the net, so one would either have to find one or try some values. As a reference, 1192311 XP is a value I found working for level 94.