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.
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
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.
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.
// loadEeprom function .org0x349DC ADDIUSP, SP, 0xFFD0 SWRA, 0x1C(SP) LUIAT, 0x8034 SBR0, 0xB4A5(AT)// Set value at 0x8033B4A5 to 0 (false) SBR0, 0xB4A6(AT)// Set value at 0x8033B4A6 to 0 (false)
Next we need to editsaveFileData 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.
// saveFileData function (A0 = fileNumber. File A = 0, File B = 1, File C = 2, File D = 3) .org0x34840 ADDIUSP, SP, 0xFFE8 SWRA, 0x14(SP) SWA0, 0x18(SP)
LUIT6, 0x8034 LBT6, 0xB4A6(T6) BEQZT6, SkipFileSave// Branch to end if 0x8033B4A6(bool shouldSaveFileData) is 0(false) NOP
LWT4, 0x18(SP) ADDIUT5, R0, 0x70 MULTT5, T4// T5 = fileNumber * 0x70 MFLOT5// Get result from mult LUIT6, 0x8020 ADDIUT6, T6, 0x7700 LUIA0, 0x8033 ORIA0, A0, 0xAF78// A0 = pointer to message queue (0x8033AF78) ADDIUA1, R0, 0xE// A1 = 0xE = (0x70 / 8) MULTA1, T4// A1 = 0xE * fileNumber, used to find address in EEPROM. MFLOA1// Get result from mult ADDUA2, T5, T6// A2 = pointer in RAM to copy to EEPROM JAL0x803247D0// osEepromLongWrite, writes data to the EEPROM ADDIUA3, R0, 0x70// A3 = number of bytes to save LUIAT, 0x8034 SBR0, 0xB4A6(AT)// Set value at 0x8033B4A6 to 0 (false)
SkipFileSave: JAL0x802794A0// Call saveMenuData() function NOP
LWRA, 0x14(SP)// End of function JRRA ADDIUSP, SP, 0x18
To finish this off we have saveMenuData function, which is very similar to saveFileData but shorter in length.
// saveMenuData function .org0x344A0 ADDIUSP, SP, 0xFFE8 SWRA, 0x14(SP)
LUIT6, 0x8034 LBT6, 0xB4A5(T6) BEQZT6, SkipMenuSave// Branch to end if 0x8033B4A5(bool shouldSaveMenuData) is 0(false) NOP
LUIA0, 0x8033 ORIA0, A0, 0xAF78// A0 = pointer to message queue (0x8033AF78) ADDIUA1, R0, 0x38// A1 = 0x38 = (0x1C0 / 8) LUIA2, 0x8020 ADDIUA2, A2, 0x78C0// 0x80207700 + 0x1C0, pointer in RAM to copy to EEPROM JAL0x803247D0// osEepromLongWrite, writes data to the EEPROM ADDIUA3, R0, 0x40// A3 = number of bytes to save
LUIT6, 0x8034 SBT6, 0xB4A5(T6)// Set value at 0x8033B4A5 to 0 (false)
SkipMenuSave: LWRA, 0x14(SP)// End of function JRRA ADDIUSP, 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.
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)
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?
(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.
(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...
(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.)
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 .org0x349DC ADDIUSP, SP, 0xFFD0 SWRA, 0x1C(SP) LUIAT, 0x8034 SBR0, 0xB4A5(AT)// Set value at 0x8033B4A5 to 0 (false) SBR0, 0xB4A6(AT)// Set value at 0x8033B4A6 to 0 (false)
// saveFileData function (A0 = fileNumber. File A = 0, File B = 1, File C = 2, File D = 3) .org0x34840 ADDIUSP, SP, 0xFFE8 SWRA, 0x14(SP) SWA0, 0x18(SP)
LUIT6, 0x8034 LBT6, 0xB4A6(T6) BEQZT6, SkipFileSave// Branch to end if 0x8033B4A6(bool shouldSaveFileData) is 0(false) NOP
LWT4, 0x18(SP) ADDIUT5, R0, 0x70 MULTT5, T4// T5 = fileNumber * 0x70 MFLOT5// Get result from mult LUIT6, 0x8020 ADDIUT6, T6, 0x7700 LUIA0, 0x8033 ORIA0, A0, 0xAF78// A0 = pointer to message queue (0x8033AF78) ADDIUA1, R0, 0xE// A1 = 0xE = (0x70 / 8) MULTA1, T4// A1 = 0xE * fileNumber, used to find address in EEPROM. MFLOA1// Get result from mult ADDUA2, T5, T6// A2 = pointer in RAM to copy to EEPROM JAL0x803247D0// osEepromLongWrite, writes data to the EEPROM ADDIUA3, R0, 0x70// A3 = number of bytes to save LUIAT, 0x8034 SBR0, 0xB4A6(AT)// Set value at 0x8033B4A6 to 0 (false)
SkipFileSave: JAL0x802794A0// Call saveMenuData() function NOP
LWRA, 0x14(SP)// End of function JRRA ADDIUSP, SP, 0x18 /*****************************************************************/
// saveMenuData function .org0x344A0 ADDIUSP, SP, 0xFFE8 SWRA, 0x14(SP)
LUIT6, 0x8034 LBT6, 0xB4A5(T6) BEQZT6, SkipMenuSave// Branch to end if 0x8033B4A5(bool shouldSaveMenuData) is 0(false) NOP
LUIA0, 0x8033 ORIA0, A0, 0xAF78// A0 = pointer to message queue (0x8033AF78) ADDIUA1, R0, 0x38// A1 = 0x38 = (0x1C0 / 8) LUIA2, 0x8020 ADDIUA2, A2, 0x78C0// 0x80207700 + 0x1C0, pointer in RAM to copy to EEPROM JAL0x803247D0// osEepromLongWrite, writes data to the EEPROM ADDIUA3, R0, 0x40// A3 = number of bytes to save
LUIT6, 0x8034 SBT6, 0xB4A5(T6)// Set value at 0x8033B4A5 to 0 (false)
SkipMenuSave: LWRA, 0x14(SP)// End of function JRRA ADDIUSP, SP, 0x18
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:
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.)