I'm gonna explain some "basics" of the MIPS Assembly. A lot of confusion seems to go around here about it, especially the part on how you load and store correctly. A lot of SMW Hackers somehow got an interest into SM64 Hacking and wanted to write some ASM code, but failed because they thought it would be that easy like in SNES's CPU 65C816. But unfortunately, MIPS does not have the ability for direct, absolute or long addressing. Instead, to make MIPS "realize" that we want to load the value from an address we need to do this by 16-bit steps and use more steps than you would in SNES.
In 65C816's assembly, you load the value from an address by simply using the opcode LDA and the address(as operand) you want to load the value from.
Code
LDA $7E0019 ; Long Addressing Load. Loads the current value of $7E0019 into the accumulator.
Unfortunately, MIPS is not like that and requires at least two steps for this. While CPUs like 65C816 allow "direct" addressing, MIPS allows only (a kind of) indirect addressing, which is officially called "Base Addressing"
Code
LUI T0, $8034 ; Load upper half address into T0.
LW T1, $B21D(T0) ; Load lower half address, MIPS notices it and automatically loads the value into T1.
In this case, T0 is our "base" which is added to the offest value in LW ( $B21D(T0) -> $8034B21D ) and loads the value from there. To conclude this: In base addressing the register acts as a pointer to an operand located at the memory location whose address is in the register.
0x8033B21D is the current amount of lives Mario haves. So, if Mario would have 4 lives, T1 would have the value $00000004 now.
So, how did this work? Well, first we load the upper half address $8033 into T0. So T0 is $80330000. LW simply loads the lower half address and binds it together with T0. The CPU automatically recognizes this is an actual address and will load the value from there into T1.
Now, if we actually want to change the lives and store it into the address, how would we do this? We do it in a similar way, just that we need now the value we want to store and we need the SW(Store Word) opcode now:
Code
LUI T0, $8034 ; Load upper half address.
ADDIU T1, T1, $000A ; Our value. T1 := $0000000A
SW T1, $B21D(T0) ; Store T1 into memory location of T0. ($8033B21D)
Now, Mario would have 10 lives now.
Also, when you're in practice you always should put the code like this:
Code
ADDIU SP, SP, $FFFC
SW RA, $0000 (SP)
// Code goes here.
LW RA, $0000 (SP)
JR RA
ADDIU SP, SP, $0004
or:
Code
ADDIU SP, SP, $FFE8 ; Allocate 6 words (no idea why. blame nintendo)
SW RA, $0014 (SP) ; Push return address to stack.
// Code goes here
LW RA, $0014 (SP) ; Pop return address from stack.
JR RA ; Jump to return address.
ADDIU SP, SP, $0018 ; Restore stack
The first code actually allocates one word space. Unfortunately, there are some hot discussions why SM64 wasted so much space for the return address( actually they allocated $FFE8 (24 bytes, wtf!) ) which is still weird. shyguyhex and me are still finding out why this was done. We both guessed that it haves something to do with the 8 multiplier rule. The rule says, that a 64-bit data object can be pushed to the stack, but eventually generate an address alignment error at runtime. So, it is required to make the stack pointer always the multiple of 8.
This code ensures that our return address is not lost. It's being pushed onto the stack. It's useful when you call a subroutine in your main subroutine (which logically replaces the old return address) and once the main subroutine is done, it will get back the old return address and jump to it.
[To be continued]