EEPROM
Users browsing this thread: 1 Guest(s)

I've been doing some digging on the EEPROM in the past few days, and now I want to share some of my findings. For those who don't know, the EEPROM is where the game's save data is stored. Super Mario 64 uses a 512 byte EEPROM, which means we have a very limited amount of space to save data 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):

Code:
​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):

Code:
​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)
Spoiler :
9: STUFF
1 - no cap (snowman)
2 - no cap (bird)
3 - no cap /monkey)
4 - no cap
5 - 50 star door
6 - ?
7 - ?
8 - ?

A: STUFF
BITWISE
1 - Seems to be set when opening the DDR/BitFS room?
2 - WATER OUTSIDE CASTLE
3 - PEACH SLIDE DOOR
4 - 1 star WF
5 - 3 stars CCM door
6 - 3 stars JRB door
7 - 8 Stars door
8 - 30 stars door

B :
BITWISE
1 - is file existing
2 - wing cap blocks
3 - Metal cap
4 - vanish cap
5 - B1 key
6 - B2 Key
7 - B1 key door is opened
8 - B2 key door is opened
Main Menu Data (0x20 bytes):

Code:
​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.

Code:
​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 CajeASM, this is a simple way of calling one these functions:

Code:
​LUI A0, 0x8033
ORI A0, A0, 0xAF78
ADDIU A1, R0, 0x7
LUI A2, 0x807F
JAL 0x80328AF0 // osEepromWrite, writes 8 bytes to the EEPROM
ADDIU A2, A2, 0x0100

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 CajeASM for this part.

Code:
​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: http://pastebin.com/raw/55vm3rXC
Spoiler: ASM code
​// loadEeprom function
.org 0x349DC
​ADDIU SP, SP, 0xFFD0
​SW RA, 0x1C (SP)

LUI AT, 0x8034
SB R0, 0xB4A5 (AT) // Set value at 0x8033B4A5 to 0 (false)
SB R0, 0xB4A6 (AT) // Set value at 0x8033B4A6 to 0 (false)

LUI A0, 0x8020
ADDIU A0, A0, 0x7700
JAL 0x80324570 // bzero function
ADDIU A1, R0, 0x200

LUI A0, 0x8020
​ADDIU A0, A0, 0x7700

JAL 0x80279174 // Copy EEPROM to 0x80207700
ADDIU A1, R0, 0x200
LW RA, 0x1C (SP)
JR RA
ADDIU SP, SP, 0x30
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: http://pastebin.com/raw/ajieEW1x
Spoiler: New ASM code
​// saveFileData function (A0 = fileNumber. File A = 0, File B = 1, File C = 2, File D = 3)
.org 0x34840
​ADDIU SP, SP, 0xFFE8
​SW RA, 0x14 (SP)
​SW A0, 0x18 (SP)

​LUI T6, 0x8034
​LB T6, 0xB4A6 (T6)

BEQZ T6, SkipFileSave // Branch to end if 0x8033B4A6(bool shouldSaveFileData) is 0(false)
NOP

LW T4, 0x18 (SP)
​ADDIU T5, R0, 0x70

MULT T5, T4 // T5 = fileNumber * 0x70
MFLO T5 // Get result from mult
LUI T6, 0x8020
​ADDIU T6, T6, 0x7700
​LUI A0, 0x8033

ORI A0, A0, 0xAF78 // A0 = pointer to message queue (0x8033AF78)
ADDIU A1, R0, 0xE // A1 = 0xE = (0x70 / 8)
MULT A1, T4 // A1 = 0xE * fileNumber, used to find address in EEPROM.
MFLO A1 // Get result from mult
ADDU A2, T5, T6 // A2 = pointer in RAM to copy to EEPROM
JAL 0x803247D0 // osEepromLongWrite, writes data to the EEPROM
ADDIU A3, R0, 0x70 // A3 = number of bytes to save

​LUI AT, 0x8034

SB R0, 0xB4A6 (AT) // Set value at 0x8033B4A6 to 0 (false)

SkipFileSave:
JAL 0x802794A0 // Call saveMenuData() function
NOP

LW RA, 0x14 (SP) // End of function
JR RA
​ADDIU SP, SP, 0x18
To finish this off we have saveMenuData function, which is very similar to saveFileData but shorter in length.

original saveMenuData ASM code: http://pastebin.com/raw/X7uzMAgz
Spoiler: New ASM code
​// saveMenuData function
.org 0x344A0
​ADDIU SP, SP, 0xFFE8
​SW RA, 0x14 (SP)


LUI T6, 0x8034
​LB T6, 0xB4A5 (T6)

BEQZ T6, SkipMenuSave // Branch to end if 0x8033B4A5(bool shouldSaveMenuData) is 0(false)
NOP

LUI A0, 0x8033
ORI A0, A0, 0xAF78 // A0 = pointer to message queue (0x8033AF78)
ADDIU A1, R0, 0x38 // A1 = 0x38 = (0x1C0 / 8)
LUI A2, 0x8020
ADDIU A2, A2, 0x78C0 // 0x80207700 + 0x1C0, pointer in RAM to copy to EEPROM
JAL 0x803247D0 // osEepromLongWrite, writes data to the EEPROM
ADDIU A3, R0, 0x40 // A3 = number of bytes to save

LUI T6, 0x8034
SB T6, 0xB4A5 (T6) // Set value at 0x8033B4A5 to 0 (false)

SkipMenuSave:
LW RA, 0x14 (SP) // End of function
JR RA
​ADDIU SP, SP, 0x18
And that's it! You should be have twice the storage space and be able to freely read/write to the EEPROM. 

​Disclaimer: 
I just found this method right before writing this post, so I can't guarantee that it's 100% safe until more testing is done. Make sure that this works on a test ROM before you start using this on your hack.

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: http://pastebin.com/raw/NzGag7SC
See this EEPROM post on smwc for more information on how to expand the EEPROM: http://www.smwcentral.net/?p=viewthread&t=74721
(This post was last modified: 15-07-2016, 03:28 AM by David.)

Oh, that's cool. I remember that topic long ago. Cool to see that you investigated more time on this. (If anyone asking: Yes, CajeASM v8.0 is still not released yet, sorry. Still problems left)
R.I.P Tarek701. 2005-2016

Please don't change the syntax too much, I'd want code made in 7.2 to still work.
Also is there anyway to increase that star count from 186 to something like 255?
Gone for a while.

(28-03-2016, 06:03 PM)Mr. GreenThunder Wrote: Please don't change the syntax too much, I'd want code made in 7.2 to still work.
Also is there anyway to increase that star count from 186 to something like 255?


Disabling the checksum should allows us to have twice the amount of storage space per file, since we can freely overwrite the copied file data in the EEPROM. Currently every level can only use 1 byte to store stars, but in theory we should be able to increase this to 2 bytes for each level. We could potentially have 225 course stars and 144 secret stars, which would make a maximum of 379 stars.  Smile
(This post was last modified: 28-03-2016, 06:31 PM by David. Edit Reason: Forgot the 100 coin stars )

"379 stars". Yeah I probably would not even want to play a Hack if it were to be THAT long. P.S. I thought a person could add an infinite number of secret stars as long as the warping was proper, or if they made it so there was more than 15 main courses...
Signed---A quite strange fellow

(28-03-2016, 09:23 PM)Blakeoramo Wrote: "379 stars". Yeah I probably would not even want to play a Hack if it were to be THAT long. P.S. I thought a person could add an infinite number of secret stars as long as the warping was proper, or if they made it so there was more than 15 main courses...


Every course in the game has 8 bits (1 byte) of information to save for the course stars/cannon. The first 7 bits are for stars, and the 8th bit is to determine if the player has unlocked the cannon for that level. A bit can only be a 1 or a 0. If the player has collected a star, then the bit for that star will be set to 1, otherwise it will be zero. Courses #1 (Bob-omb battlefield) to #15 (Rainbow Ride) have specific named mission stars for the first 6 stars and a 100 coin star. Other courses have just 7 stars that use the generic "One of the castle's secret stars" name. Multiple levels can have the same course ID, so they can technically share the same stars.

Basically If a level has a course ID from 1 to 15, then it will have mission stars. Otherwise the stars in that level will just be secret stars.

Course IDs (These are different from level IDs)
Course #0 = Castle grounds, Castle courtyard, and Inside Castle
Course #1 = Bob-omb Battlefield
Course #2 = Whomp's Fortress
... (Skip a few, you should know these)
Course #14 = Tick-Tock Clock
Course #15 = Rainbow Ride
Course #16 = Bowser 1 course & Bowser 1 fight
Course #17 = Bowser 2 course & Bowser 2 fight
Course #18 = Bowser 3 course & Bowser 3 fight
Course #19 = Princess's secret slide
Course #20 = Metal Cap
Course #21 = Wing Cap
Course #22 = Vanish Cap
Course #23 = Rainbow Clouds
Course #24 = Secret aquarium
Course #25 = End Cake Picture

Course IDs for levels in RAM = value at (0x8032DD97 + level ID number)
(This post was last modified: 29-03-2016, 07:37 PM by David.)

one easy way to have 8 more bytes per file and to still have the checksum thing intact is to simply edit the table

E8D98
00 00 00 05 04 00 06 08 01 0A 0B 03 0D 0E 0F 00
10 16 11 18 12 07 09 02 19 00 13 14 15 10 17 00
11 12 00 0C
+8020770B+filenumber*70 = EEProm mapping;

change 0 to -5 or so on unused levelIDs and it will safe those bytes additionally. that's the way ive been doing it the whole time.

But isn't there like, 35 unused level IDS?
Signed---A quite strange fellow

It turns out that my NOP method wasn't very good, as the function 0x80279218 in saveFileData was causing the game to soft-lock. So I decided just to rewrite the 3 functions so that their won't be anymore problems with them. I will be updating the main post shortly after posting this.
Spoiler: ASM Code
​// loadEeprom function
.org 0x349DC
​ADDIU SP, SP, 0xFFD0
​SW RA, 0x1C (SP)

LUI AT, 0x8034
SB R0, 0xB4A5 (AT) // Set value at 0x8033B4A5 to 0 (false)
SB R0, 0xB4A6 (AT) // Set value at 0x8033B4A6 to 0 (false)

LUI A0, 0x8020
ADDIU A0, A0, 0x7700
JAL 0x80324570 // bzero function
ADDIU A1, R0, 0x200

LUI A0, 0x8020
​ADDIU A0, A0, 0x7700

JAL 0x80279174 // Copy EEPROM to 0x80207700
ADDIU A1, R0, 0x200

​LW RA, 0x1C (SP)
​JR RA
​ADDIU SP, SP, 0x30

​/*****************************************************************/

​// saveFileData function (A0 = fileNumber. File A = 0, File B = 1, File C = 2, File D = 3)
.org 0x34840
​ADDIU SP, SP, 0xFFE8
​SW RA, 0x14 (SP)
​SW A0, 0x18 (SP)

​LUI T6, 0x8034
​LB T6, 0xB4A6 (T6)

BEQZ T6, SkipFileSave // Branch to end if 0x8033B4A6(bool shouldSaveFileData) is 0(false)
NOP

LW T4, 0x18 (SP)
​ADDIU T5, R0, 0x70

MULT T5, T4 // T5 = fileNumber * 0x70
MFLO T5 // Get result from mult
LUI T6, 0x8020
​ADDIU T6, T6, 0x7700
​LUI A0, 0x8033

ORI A0, A0, 0xAF78 // A0 = pointer to message queue (0x8033AF78)
ADDIU A1, R0, 0xE // A1 = 0xE = (0x70 / 8)
MULT A1, T4 // A1 = 0xE * fileNumber, used to find address in EEPROM.
MFLO A1 // Get result from mult
ADDU A2, T5, T6 // A2 = pointer in RAM to copy to EEPROM
JAL 0x803247D0 // osEepromLongWrite, writes data to the EEPROM
ADDIU A3, R0, 0x70 // A3 = number of bytes to save

​LUI AT, 0x8034

SB R0, 0xB4A6 (AT) // Set value at 0x8033B4A6 to 0 (false)

SkipFileSave:
JAL 0x802794A0 // Call saveMenuData() function
NOP

LW RA, 0x14 (SP) // End of function
JR RA
​ADDIU SP, SP, 0x18 

​/*****************************************************************/

​// saveMenuData function
.org 0x344A0
​ADDIU SP, SP, 0xFFE8
​SW RA, 0x14 (SP)


LUI T6, 0x8034
​LB T6, 0xB4A5 (T6)

BEQZ T6, SkipMenuSave // Branch to end if 0x8033B4A5(bool shouldSaveMenuData) is 0(false)
NOP

LUI A0, 0x8033
ORI A0, A0, 0xAF78 // A0 = pointer to message queue (0x8033AF78)
ADDIU A1, R0, 0x38 // A1 = 0x38 = (0x1C0 / 8)
LUI A2, 0x8020
ADDIU A2, A2, 0x78C0 // 0x80207700 + 0x1C0, pointer in RAM to copy to EEPROM
JAL 0x803247D0 // osEepromLongWrite, writes data to the EEPROM
ADDIU A3, R0, 0x40 // A3 = number of bytes to save

LUI T6, 0x8034
SB T6, 0xB4A5 (T6) // Set value at 0x8033B4A5 to 0 (false)

SkipMenuSave:
LW RA, 0x14 (SP) // End of function
JR RA
​ADDIU SP, SP, 0x18

You can download the ASM code file here: http://bin.smwcentral.net/u/26355/DisableChksum.asm

This time I actually tested the code to be sure that it worked properly. I did a quick 40 star play-through. I got all the caps, both bowser keys, and got a bunch of secret stars. The game played with no issues, and this is my result with the EEPROM data:

[Image: NwXSDww.png]

All of those 0xFF's you see where the copied data used to be? That is now free space that we can modify to have whatever we want. My saveFileData function still saves the 0x70 bytes for each file, so you can just put your own data there and it will be saved along with the File's data.

I still would recommend that you test this out in a Test ROM first before using this in your hack.
(This post was last modified: 30-03-2016, 01:24 AM by David.)

How to read/save the bits of an byte? Would you write a simple example code with comments, please? Thanks!

EEPROM
Users browsing this thread: 1 Guest(s)