News:

11 March 2016 - Forum Rules

Main Menu

Metroid Zero Mission - SRAM Checksum

Started by MeganGrass, October 05, 2013, 10:46:48 PM

Previous topic - Next topic

MeganGrass

I'm sorry if this is the wrong section for such a topic.

I'm currently developing a SRAM modifier/creator (c/c++ with GUI) for various Metroid titles.

So far, I have completely mapped and documented all of Super Metroid and Metroid Zero Mission's SRAM, and I eventually plan to add support for Metroid Fusion, as well. Support for Super Metroid is 100% complete (with proper checksum calculation and placement).

...but...

I've ran into a problem with Zero Mission.

Without a proper algorithm to calculate a new checksum for Zero Mission's SRAM, completing support for it will be useless. Same deal for Fusion. Furthermore, my ARM7TDMI dis/assembly skills aren't too excellent, yet.

Can someone help with this matter, please?

EDIT:

SRAM is stored at 0x02038000 and loaded+checked at boot.

x0_000

I can probably poke at it, can you be more specific about what you want? My understanding is that you want to create saves from scratch and MZM uses a checksum to determine if the save is valid, so you want to know how the game makes the checksum?

MeganGrass

Quote from: x0_000 on October 17, 2013, 03:02:03 PM
I can probably poke at it, can you be more specific about what you want? My understanding is that you want to create saves from scratch and MZM uses a checksum to determine if the save is valid, so you want to know how the game makes the checksum?

Yep. :)

I need to know exactly how the game creates the checksum, so that I can do the same for my app.

Any help would most definitely be appreciated.

snarfblam

You might also want to ask at the forums at metroidconstruction.com. There are a couple of MZM hackers there.

x0_000

This is what I've got so far:

The checksum function is located at 08074624, it takes input r0 = 0 when it's checking the save data. The format of the data at 02038000 is as follows:

02038000 + saveID*0x1220 + 80 is the start of a save slot's data, and saveID = 0,1 or 2. I'll refer to this offset as the SaveOffset. The game does 3 things to check if the save is valid.

1. The game adds all the words in the 0x1220 area and compares that to the value stored at [SaveOffset, 0x10]. These values should be equal in a valid save.
2. The game compares the words stored at [SaveOffset, 0x10] and [SaveOffset,0x14]. It checks if the words are bitflips of each other (i.e. [SaveOffset, 0x10] + [SaveOffset,0x14] = 0xFFFFFFFF)
3. There are 3 strings the game expects to find:
3a. The first string is at [SaveOffset,0], the game expects this string to be "ZERO_MISSION_010" (The exact byte sequence is the 16 bytes starting at 08411410.)
3b. The second string is at [SaveOffset,0x4F], the game expects this string to be "Planet Zebes" followed by the 4 bytes 0x2e, 0x2e, 0x2e, 0x20 (The exact sequence is at 08411420.)
3c. The third string is at [SaveOffset, 0x250], the game expects this string to be " - Samus Aran - "  (The exact sequence is at 08411430.)

If none of the strings match, the game will ignore the save data. If there is a partial match, the game will report the save data as corrupt. If the strings match, but 1 or 2 fail, the game also reports the data as corrupt. Otherwise the game thinks the save data is valid.

I haven't checked how the game actually creates the checksum, but looking at the procedure, this should work:
1. Add all the words in the save slot except the words at [SaveOffset,0x10], [SaveOffset,0x14] and subtract 1 (Call this X.) Store X in [SaveOffset,0x10] and -1-X in [SaveOffset,0x14].
1. Store the strings in their locations.

Here's a partial disassembly of the routine: http://pastebin.com/6vEhYbrm

If anything's vague feel free to ask for clarification :p

MeganGrass

@snarfblam - Thanks for the link! I was previously unaware of that Metroid community.

Wow, many thanks, x0_000! :o

I'll try the method you describe, asap.

I'm just confused on a few things:

Quote1. Add all the words in the save slot except the words at [SaveOffset,0x10], [SaveOffset,0x14] and subtract 1 (Call this X.) Store X in [SaveOffset,0x10] and -1-X in [SaveOffset,0x14].

Do I start at [SaveOffset,0x18] or [SaveOffset,0x00]?

Also, words for my compiler are 2bytes, is it the same size for GBA? Endian-swapping isn't an issue for me, but should I take it into account?

x0_000

You start at SaveOffset, 0x0. When I say "word" I mean a sequence of 4 bytes, which are reversed in the ROM, so the sequence 0x0 0x1 0x2 0x3 in the ROM should be read 0x03020100. Also it might not be clear, but you save X and -1-X as a word as well.

MeganGrass

Quote from: x0_000 on October 18, 2013, 07:39:45 PM
You start at SaveOffset, 0x0. When I say "word" I mean a sequence of 4 bytes, which are reversed in the ROM, so the sequence 0x0 0x1 0x2 0x3 in the ROM should be read 0x03020100. Also it might not be clear, but you save X and -1-X as a word as well.

Ah, okay. Seems simple enough.

I just needed clarity, because a WORD in my compiler is only 2bytes, as I stated, where a DWORD is 4bytes. I had a feeling there'd be something different about GBA architecture.

Again, many thanks for your help!

x0_000

Cool, if what I suggested doesn't work then upload a test save somewhere and I'll debug whatever's happening.

MeganGrass

Hmm... that didn't seem to work.

download

I've provided a working SRAM file, in entirety - it won't be difficult to isolate the individual (0x1220 byte) save data.

If you figure a solution, please provide the mathematical algorithm in c/c++ format. :D

x0_000

#10
Assuming I get it working, what slot is it supposed to be in and what kind of save data should I expect?

EDIT: The save file only has data in the first save slot, the other 2 are empty. Although it looks like you also put data at where a hypothetical 4th save slot would be. That data is loading fine though when I put it in the other slot. Unless it's not supposed to load 69 HP, 58 minutes at Tourian?

As a reminder, the save slots IDs in the game are 0, 1 and 2, so their offsets are +0x80, +0x12a0 and +0x24c0.

MeganGrass

Slot 0/A





I know these screenshots seem 'messed-up'/impossible, but they are the result of various modifications (and verified working). If you're curious, SRAM can be modified after bootup, hence how I was able to map it without a checksum algorithm. The checksum will be recalculated at system shutdown or player death (return to title).

October 25, 2013, 12:18:48 AM - (Auto Merged - Double Posts are not allowed before 7 days.)

Quote from: x0_000 on October 24, 2013, 11:38:10 PMAlthough it looks like you also put data at where a hypothetical 4th save slot would be.

That's quite interesting, but no, I did not put data into the hypothetical 4th save slot. Perhaps, it is a temporary buffer area?

KingMike

Would a Zero Mission SRAM Checksum also need to include password memory for the unlockable NES game?
Or is that already part of the SRAM data?
"My watch says 30 chickens" Google, 2018

x0_000

Quote from: MarkGrass on October 25, 2013, 12:15:00 AM
Slot 0/A
I know these screenshots seem 'messed-up'/impossible, but they are the result of various modifications (and verified working). If you're curious, SRAM can be modified after bootup, hence how I was able to map it without a checksum algorithm. The checksum will be recalculated at system shutdown or player death (return to title).

That's quite interesting, but no, I did not put data into the hypothetical 4th save slot. Perhaps, it is a temporary buffer area?
In that case, what is the problem? The checksum is fine, and the save data is being read. What kind of behavior are you expecting this save data to have?

MeganGrass

Quote from: KingMike on October 25, 2013, 12:27:04 AM
Would a Zero Mission SRAM Checksum also need to include password memory for the unlockable NES game?
Or is that already part of the SRAM data?

Good question, I'll have to get back to you on that. Guesstimate: probably.

Quote from: x0_000 on October 25, 2013, 12:33:55 AM
In that case, what is the problem? The checksum is fine, and the save data is being read. What kind of behavior are you expecting this save data to have?

Sorry, I should have specified - the SRAM is modifiable while in memory, using VBA. :P

I need an algorithm for the checksum, so that I can create/edit savegames from scratch, without the use of an emulator. Again, any help is very much appreciated, and you will be credited in the app that I finish creating.

x0_000

Well, what I posted earlier should work? I don't code in C so here's the pseudo code algorithm:


saveOffset = saveID*0x1220 + 0x80
rom.write("ZERO_MISSION_010", saveOffset) ; 16 characters
rom.write("Planet Zebes... ", saveOffset+0x4f) ; that's Planet[Space]Zebes...[SPACE] for a total of 16 characters
rom.write(" - Samus Aran - ", saveOffset+0x250) ; [Space]-[Space]Samus[Space]Aran[Space]-[Space] for a total of 16 characters

saveValue = 0
for i from 0 to 3:
    saveValue += rom.readWord(saveOffset + i*4) ; that's 4 bytes, reversed
    saveValue && 0xFFFFFFFF ; bitwise and

for i from 6 to 1159:
    saveValue += rom.readWord(saveOFfset + i*4)
    saveValue && 0xFFFFFFFF

checkSum = (saveValue - 1) && 0xFFFFFFFF
negCheckSum = (-1 - checkSum) && 0xFFFFFFFF
rom.writeWord(checkSum, saveOffset+16)
rom.writeWord(negCheckSum, saveOffset+20)

MeganGrass

Well, that certainly worked, x0_000. Many thanks to You! :beer:

In your initial post, you made it seem like the strings are to be written afterward, which is exactly where I went wrong.

Anyways, it's fixed now, and here's the code in C (for future reference from anyone interested):


// This brief example sums an extracted save slot data (0),
// beginning at 0x80 in the SRAM file (0x1220 bytes).

signed int Sum = 0;
signed int Buf = 0;
signed int NegSum = 0;
signed long int _Offset = 0x00; // Starting at "ZERO_MISSION_010" string

for(unsigned int n = 0; n < 1159; n++, _Offset+=0x04)
{
switch(_Offset)
{
case 0x10:
case 0x14:
break;
default:
fseek(_File, _Offset, SEEK_SET);
fread(&Buf, 4, 1, _File);

Buf && 0xFFFFFFFF;
Sum += Buf;
break;
}

}

Sum -= 1;
NegSum = (-1 - Sum);

x0_000


MeganGrass

#18
Small Progress Report:

The "hypothetical 4th save slot", mentioned earlier in this thread, isn't a buffer area.

It is a backup slot. In fact, the area 0x36E0~0x6D40 (0x1220*3) is reserved for backup-copies of the three save games.

For example, if the data in save slot #2 is corrupt, you will receive the following error message, and the backup located at 0x4900 will be copied to 0x12A0 (the area reserved for save slot #2):



That said, each save slot ID is stored twice in the SRAM and should be properly handled during modification, deletion, copy, etc.

November 02, 2013, 12:57:37 AM - (Auto Merged - Double Posts are not allowed before 7 days.)

Small Progress Report:

Metroid Fusion uses the same algorithm for checksum calculation, only the SRAM is structured slightly different.

Each save game is 0x1200 bytes, and data begins at offset 0x200 in the SRAM. Like Zero Mission, each save game is copied for backup purpose, but again, structured differently.

Metroid Fusion Save Data
0x0200 Save Game A
0x1400 Save Game A (backup-copy)
0x2600 Save Game B
0x3800 Save Game B (backup-copy)
0x4A00 Save Game C
0x5C00 Save Game C (backup-copy)

MeganGrass

I can haz special edition GBA?! :D



^ I have no idea how to change the ID, as it seems to be hardcoded into the ROM.


I've also made more progress with the overall SRAM of Zero Mission... and the amount of checksums this thing undergoes is fucking ridiculous.

1 checksum for the header + two backups (total of 3)
1 for each save game+backup (total of 6)
1 for each 'key' setting (total of 4)
1 for 'time attack' + backup (total of 2)
1 for original Metroid data + backup (total of 2)

...for a grand total of 17 checksums (!), and I haven't even looked into the button log info for the Demo that plays at the title screen (produced by Debug ver. ROM, usable in retail).


The original Metroid data is strange (to me), and any attempt to modify it results in erasure. I included John Ratliff's password algorithm code into my app, to see if the raw and/or converted data is the same as MZM data... and it is not.

The original Metroid data begins at offset 0x7FD8, and backup is stored at 0x7FB0. Any help figuring this last piece of the puzzle would be appreciated.


Furthermore, because I went through the trouble of including John Ratliff's password algorithm, the app I am creating will be a universal savegame creator/modifier for the following games:

Metroid
Metroid II (nothing coded, yet)
Super Metroid
Metroid Fusion
Metroid Zero Mission

With the exception of figuring out how original Metroid data is handled for MZM, the only thing left to do is creation of GUI dialog, so, this app will be finished soon.