Super Mario 64 uses a 512 byte EEPROM to save the game's data, which means we have a very limited amount of space to save to. The game has 4 save files which each takes up 0x70 bytes, the last 0x40 bytes are used for other things like sound options. If any of the information below is wrong, or if I missed something important, please let me know and I'll fix it.

Map of the EEPROM (0x200 bytes in total):

0x000 - 0x037 = File A data
0x038 - 0x06F = File A data (Copy)
0x070 - 0x0A7 = File B data
0x0A8 - 0x0DF = File B data (Copy)
0x0E0 - 0x117 = File C data
0x118 - 0x14F = File C data (Copy)
0x150 - 0x187 = File D data
0x188 - 0x1BF = File D data (Copy)
0x1C0 - 0x1DF = Main Menu data
0x1E0 - 0x1FF = Main Menu data (Copy)

You will see that half of the EEPROM is just copied data. This is done to protect against data corruption. Each block of data has a 4 byte checksum at the end of it. If the checksum of the original and the checksum of the copy don't match, then the game knows that something bad happened to your save file. If that does happen then it will then fix your corrupted data with the copied data.  However, this causes a problem for us ROM hackers which i'll explain later in the post.

Save File Data (0x38 bytes):

0x00 - 0x07 = Always 0x00?
0x08 = Secret stars in castle
0x09 - 0x0B = Flags
0x0C - 0x1A = Course Stars (the 8th bit is for the level's cannon)
0x1B - 0x24 = Secret stars in courses
0x25 - 0x33 = Course Coin scores
0x34 - 0x37 = Checksum

The maximum star count for any file is 182 stars (105 course stars + 77 secret stars).

The first 8 bytes seem to always be loaded as 0x00, even if you try to save those bytes as something else. The secret stars at 0x08 are for Toads/Mips the rabbit. The flags at bytes 0x09 to 0x0B are there to determine if you done a specific action like unlock a door, lost your hat, drained the water, etc. Bytes 0x1B to 0x24 are for the stars in the secret courses/bowser levels. 0x25 to 0x33 are your coin scores. Because each level only gets 1 byte to store the coin score, the maximum possible score you can get is 255. The final 4 bytes contain the checksum for that file chunk.

See this video if you want to know more about the coin scores: SM64 - The 255 Coin Limit.

Flags (Notes by Kaze)
Main Menu Data (0x20 bytes):

0x00 - 0x0F = Related to high scores?
0x10 - 0x11 = Sound Select option: 0x0000 = Stereo, 0x0001 = Mono, 0x0002 = Headset
0x12 - 0x1B = unused?
0x1C - 0x1F = Checksum

This is data that is used in the file menu. Bytes 0x10-0x11 store the sound select option. Bytes 0x12 - 0x1B seem to be unused, you can write data here and not have it affect anything. Yup, out of all the 512 bytes in the EEPROM we can only edit 10 without affecting anything else. 

Reading/Writing to the EEPROM:

So how do we load/store data to the EEPROM using ASM code? Well, there are 4 simple functions that we can use.

0x80324690 = s32 osEepromLongRead(OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes);
0x803247D0 = s32 osEepromLongWrite(OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes);
0x80328AF0 = s32 osEepromWrite(OSMesgQueue *mq, u8 address, u8 *buffer);
0x80329150 = s32 osEepromRead(OSMesgQueue *mq, u8 address, u8 *buffer);

You don't need to worry about the "OSMesgQueue *mq" part, just know that A0 should always be 0x8033AF78. Assuming that your using armips, this is a simple way of calling one these functions:

la a0, 0x8033AF78
la a2, 0x807F0100
jal 0x80328AF0 // osEepromWrite, writes 8 bytes to the EEPROM
addiu a1, r0, 0x7

A0 = 0x8033AF78 // pointer to message queue
A1 = 0x07 // Address in EEPROM divided by 8. (this really points to the EEPROM address 0x38)
A2 = 0x807F0100 // pointer in RAM to copy to EEPROM

Both osEepromWrite and osEepromRead will always write/read 8 bytes from the EEPROM. The EEPROM itself is split up into 8 byte blocks due to hardware limitations, so you can't just save 3 bytes to the EEPROM address 0x14.

If you want to write/read more than 8 bytes then you will need to use osEepromLongWrite and osEepromLongRead. This uses a 4th parameter which will specify how many bytes you want to transfer.

A3 = 0x20 // This will transfer 0x20 bytes to/from the EEPROM

But if you remember from the first paragraph, the checksums prevent us from writing custom data to the EEPROM. Well, before writing this post I figured out a way to disable the EEPROM checksums that will allow us to freely read/write to the EEPROM.

Disabling the EEPROM checksums:

There are 3 functions that we need to change to get rid of the checksums. You will need Armips for this part.

0x802799DC = void loadEpprom(); // Loads the EEPROM to 0x80207700 and checks the checksums
0x80279840 = void saveFileData(s32 fileNumber); // fileNumber = 0 to 3 (File A = 0, File B = 1, etc)
0x802794A0 = void saveMenuData();

loadEpprom will be the easiest because it loads up the EEPROM first, then checks the checksums. So all we need to do is make an early return statement to disable that routine.

original loadEpprom ASM code:

New ASM code:
Next we need to edit saveFileData which is used to save a file. We are going to disable the checksum and the code that copies the save data, this way we can have an extra 0x38 bytes for each save file. 

original saveFileData ASM code:

New ASM code:
To finish this off we have saveMenuData function, which is very similar to saveFileData but shorter in length.

original saveMenuData ASM code:

New ASM code:
And that's it! You should be have twice the storage space and be able to freely read/write to the EEPROM. 

Special Thanks:
I want to thank QueueRAM, Kaze, shygoo, and Tarek701 for helping out with their notes/knowledge.

Here is a summary of my notes on the EEPROM:
See this EEPROM post on smwc for more information on how to expand the EEPROM:
note that you can also just set the unnused course IDs to slightly negative values to make the game store the other eepbits as well. i used that a few times in my own hacks and it worked with no further issues. those 8 bytes can be very useful for storing lifes/game overs/playtime/etc
after doing some debugging with these eeprom functions, i found that noping the jal at 3488c will stop the star count from being saved when collecting a star (at least that's all ive tested)
only use i can see for it is mini hacks so that they're properly replayable, like a classic sonic game, except hopefully shorter.
y-you too.
Thought i would point out that during gameplay the first two words of each file are used to keep track of the hat when it's blown off mario by a whoosh/wind.
0x00 - Course
0x01 - Area
0x02 - X
0x04 - Y
0x06 - Z

But it doesnt seem to actually save so who knows why they store it there specifically. must have just been a convenient spot.
Edit: Actually it does seem to save under the right conditions (seems like the hat needs to actually get blown off, simply setting the no hat flag doesn't cause these bytes to save nor do they save normally,) my emulator's memory view just didn't update after resetting which caused confusion. But even then, it gives you your hat back if only the "no hat" flag is set (not the enemy specific no hat flags.) when you re-load your save file, so im really not sure what the point is.

I guess if you wanted to for a hack you could nop away the instruction at 0x8027a120 and it would make it so the hat would need to be collected from the place it landed if it gets blown off and no enemies (such as Mr. Blizzard) get it, even between game sessions.

Users browsing this thread: 1 Guest(s)