Language…
8 users online: BeancityMakes,  Donut, Golden Yoshi, gorpo_c, its_4life, Maw, pial, TBird73 - Guests: 796 - Bots: 132
Users: 61,747 (2,253 active)
Latest user: VTR

ASM Tutorial

Gotta do a serious tutorial;
First, download these files:
http://www.mediafire.com/download/mjat5mj5yx278j4/ASM_Tutorial.zip

Part 1: Hello World
First, have a loom at Mario's behavior. Open the Behavior scripts.txt file.

21CCC0/002EC0 00 00 00 00
21CCC4/002EC4 10 05 00 00 <- irrelevant
21CCC8/002EC8 11 01 01 00 <- irrelevant
21CCCC/002ECC 11 03 00 01 <- irrelevant
21CCD0/002ED0 23 00 00 00 00 25 00 A0 <-collision sphere
21CCD8/002ED8 08 00 00 00 <-start of a loop, that gets called every frame
21CCDC/002EDC 0C 00 00 00 80 2C B1 C0 <-calls a function
21CCE4/002EE4 0C 00 00 00 80 29 CA 58 <-calls a function
21CCEC/002EEC 0C 00 00 00 80 2C B2 64 <-calls a function
21CCF4/002EF4 09 00 00 00 <- end of the loop
behavior script command 0x0C calls a function. that's what we need.
Luckily, Mario's behavior calls an useless debug menu, which we can override. For function, that are between 80 24 60 00 and 80 33 00 00, we can just sub 80245000 from it, to find the ROM offset.
(if you are looking into the .txt file, you'll notice, that the 0x0Cs are allready named)

So we've got to calculate 802CB1C0-80245000 = 861C0

Now, open LEM ASM, press F6 and F7 to get the correct view mode and go to this address. on top should be "ADDIU SP, SP, $FFD8"

Now, let's do the first code;
for the start, i won't explain, what exactly it does, but start every function with:
Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)


and end it with:
Code
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


if you look into the functions.txt file, you'll see a list of functions. we need the "PrintXY" function.
PRINTXY: 802D66C0, A0 (xpos) a1(y pos), A2 (message location);
a function gets called through the command JAL. In our example, it would be
Code
JAL $802D66C0
;
but we've got to be carefull with this command. It executes the next line after it, before it actually JALs the function. I'll show this later in the example.
In ASM, arguments are getting loaded into registers. so for this example, we will have to set a0, a1 and a2 with own commands too.
Commands to load immidiate values are:
LUI A0, $value - loads a value, that get's multiplied by $10000; so
Code
LUI A0, $802C

would load 802C0000 into A0.


ORI A0, A0, $VALUE - ORs all bits, if at least 1 bit is 1, it will put the result to 1; example:
(imagine, A0 is allready 802C0000)
Code
ORI A0, A0, $B23C

802C0000 <-A0
0000B23C <-immidiate Value
802CB23C <-Result

if you want to see this bitwise, look into win calculator.

We will just use these 2 operants to put arguments to our function.
Now you might wonder, how we can find out the "message location";
it works just as we found our ROM offset for the 0x0C command (except, that we have to calculate from ROM to RAM instead of from RAM to ROM. Write a text somewhere into the checksum area, where you have space and add 80245000 to it. in our example, i'd write it to 8623C, because it's still in the debug menu, which we are overwriting anyways.

to find the rom offset then: 8623C+80245000 = 802CB23C;

so we can extend our code to:
Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)
LUI A2, $802C
ORI A2, A2, $B23C
JAL $802D66C0

LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


now we've just got to set A0 and A1, which is the position. the positions should be around $70. Note, that when using ORI, you'll have to give a register and an immidiate value. So the register has to have the value 0. For such cased, we got R0. This register can just contain 0, so doing anything with it won't change anything. So the codeline for these registers is:

Code
ORI A0, R0, $0070
ORI A1, R0, $0070

well, you can change the immidiate value, if you want other printing positions, obviously.


now, i mentioned above, that the line after the JAL gets executes, before it actually JALs. so we have to put on of these 2 lines after the JAL.
So, the code looks like:


Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)
LUI A2, $802C
ORI A2, A2, $B23C
ORI A0, R0, $0070
JAL $802D66C0
ORI A1, R0, $0070
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


(to inser tit with LEM ASM, press f3, btw.)


next lesson will have ANDI, Branches and Loads!
This is how the result should look like, if you've entered the code correctly:
http://bin.smwcentral.net/u/21427/Project64%2B2013-09-08%2B12-42-34-79.png
Nice Tutorial. #smw{:TUP:} However, you should've explained the part, you were subtracting -12 (Hex) from Stack. Even if it's not important for the start, you could summary it.

Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)


and the end code:
Code
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


On one side I agree, beginners cannot really make any profit of it. But they more they know, the better they can make use of it in future and move on, on their development to SM64 Hackers. Of course, there are exceptions, that completely fail as miserable SM64 hackers and like to do the worst edits to their ROM ever.

For those who are that badly curious:
This code ensures, that our return address is safely stored and not lost while we code + it won't write into the stack by prior functions. However, it's only needed if we use our current subroutine to call another subroutine. (Logic: If you do a JAL in a subroutine, the old return address will be replaced and this can end "horribly" So the code subtracts $12 (Hex) from Stack and saves it to the $14 part in stack. If the "main" subroutine is done, then the original return address gets load from stack again and the stack will be added with $0018 and THEN it jumps back to the return address.
Your layout has been removed.
We have table stretch, could you link to the image?
well, your answer is not correct at all;
at first, it subtracts 0x18, which is pretty important, because otherwise the stackpointer would get messed up and this whole thing wouldn't make any sense at all;
i didn't wanted to explain it so far, because i first got to explain LWs etc, before i'll explain such things.
important is, that you can also save it to $00 or something in the stack, you'd have to ADDU $FFFC then. this would allready be enough, if you don't have to save anythign into the stack. part 2 will come tomorrow and i'll explain button activators.
Originally posted by Kazeshin
well, your answer is not correct at all;
at first, it subtracts 0x18, which is pretty important, because otherwise the stackpointer would get messed up and this whole thing wouldn't make any sense at all;
i didn't wanted to explain it so far, because i first got to explain LWs etc, before i'll explain such things.
important is, that you can also save it to $00 or something in the stack, you'd have to ADDU $FFFC then. this would allready be enough, if you don't have to save anythign into the stack. part 2 will come tomorrow and i'll explain button activators.


Wait, 0x18 is decimal. I'm it is -12 in Hexadecimal. Whatever. I simply explained your code as it stands there. Mistakes happen. Well.

Yes, I was explaining "your" code. It's obvious, that you could have chosen others like $00, as you said.

Damn, well. Let's make a simple summary without going any further:
That code is generally used, when calling another subroutines in a subroutine. (In this case: Only if we're going to use another JAL in our subroutine) It simply ensures, that the "original" return address isn't overwritten. Could we agree both here and let this count as a summary? If not, then I don't really care. It's your tutorial.
Your layout has been removed.
Originally posted by UGotBalls
Wait, 0x18 is decimal. I'm it is -12 in Hexadecimal. Whatever. I simply explained your code as it stands there. Mistakes happen. Well.

Yes, I was explaining "your" code. It's obvious, that you could have chosen others like $00, as you said.

Damn, well. Let's make a simple summary without going any further:
That code is generally used, when calling another subroutines in a subroutine. (In this case: Only if we're going to use another JAL in our subroutine) It simply ensures, that the "original" return address isn't overwritten. Could we agree both here and let this count as a summary? If not, then I don't really care. It's your tutorial.


yeah, the summary is correct, but ADDU FFE8 is subbing 0x18; in dec it would be 24. to calculate the number, calculate 0x10000 - 0xFFE8; you'll get 0x18.
edit: changed "1000" to "10000" to prevent missunderstoods.
Originally posted by Kazeshin

but ADDU FFE8 is subbing 0x18; in dec it would be 24. to calculate the number, calculate 0x1000 - 0xFFE8; you'll get 0x18.

(You probarly meant 0x10000)

Woops. My bad. Just noticed it now. I feel ashamed.
0x10000 - 0xFFE8 = 0x18 -> 24 in decimal.
Your layout has been removed.
Part 2: Button Activator


as before, we are going to start our code with Code
Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)

and end it with:
Code
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


we are going to extent our old code with some commands, to make it display 2 different texts.

For this, i write "TEXT1" and "TEXT2" to 86250. note, that one byte (2 numbers in hex) have to be "00", to end the string.
Data, that we will need:
The controller data is at $8034AF90. the controler has different things like "stick x/y", "button pressed" and some more things, that i won't explain right now.
the sticks can go from -127 to +127. note, that negative numbers are represented different; 0x80 would be -127 as a single byte, in a halfword 0x8000 would be -32768, while FFFF would be -1. In a byte, -1 would be FF.

these are the values for the current button. if more buttons are pressed, they get added.
BUTTON_C_RIGHT = 0x0001,
BUTTON_C_LEFT = 0x0002,
BUTTON_C_DOWN = 0x0004,
BUTTON_C_UP = 0x0008,
BUTTON_R = 0x0010,
BUTTON_L = 0x0020,
BUTTON_D_RIGHT = 0x0100,
BUTTON_D_LEFT = 0x0200,
BUTTON_D_DOWN = 0x0400,
BUTTON_D_UP = 0x0800,
BUTTON_START = 0x1000,
BUTTON_Z = 0x2000,
BUTTON_B = 0x4000,
BUTTON_A = 0x8000
--------------------------------------------------
i'll first show the code and then explain, what each line does:

Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)
//CHECK1
LUI T1, $8034
LH T1, $AFA0 (T1)
ANDI T2, T1, $8000
BEQ R0, T2, //CHECK2
NOP

//PRINT1
LUI A2, $802C
ORI A2, A2, $B250
ORI A0, R0, $0070
JAL $802D66C0
ORI A1, R0, $00D0

//CHECK2
LUI T1, $8034
LH T1, $AFA0 (T1)
ANDI T2, T1, $4000
BEQ R0, T2, //END
NOP

//PRINT1
LUI A2, $802C
ORI A2, A2, $B256
ORI A0, R0, $0070
JAL $802D66C0
ORI A1, R0, $0020

//END
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


note, that:
1. "//END" as example, is a lable. it's not anything, that cou can assemble. you'll need them to create codes before writing them to the rom, to see, what your code does.
2. "BEQ R0, T2, //CHECK2" is a goto. instead of "//CHECK2" you've got to assemble the address, that stands before the code. in our example, if you wrote the code to 861C0, it would have to assemble it as "BEQ R0, T2, $861f0".

now, to the explanations.

Code
ADDIU SP, SP, $FFE8
SW RA, $0014 (SP)
//CHECK1
LUI T1, $8034 <- T1 := 80340000
LH T1, $AFA0 (T1) <-LH loads the next 4 digits of the RAM address pointed to into the register, that is directly after the command. 
the content of T1 get's added to the immidiate value ("AFA0"). 
AFA0 is a negative number. if the number in a LH is negative, the whole thing will decrease by 0x10000. so i LUI'ed a value, 
that is 0x10000 higher than youd expect. 8033AFA0 is the 
address for the current button. the current button has the 
values from the list above. 

ANDI T2, T1, $8000 <- this is a bitwise instruction, that works just like the ORI, except, that BOTH bits have to be 1, to store 1 to the result. example:
$8034 <-value 1
$4034 <-value 2
$0034 <-result
look at the button values and translate them to binary. you'll notice something.
BEQ R0, T2, //CHECK2 <-if T2 is 0 after ANDIing, it will go to the 2nd check. i used $8000, because it's the value of the A button.
NOP

//PRINT1
LUI A2, $802C
ORI A2, A2, $B250
ORI A0, R0, $0070
JAL $802D66C0
ORI A1, R0, $00D0

//CHECK2
LUI T1, $8034
LH T1, $AFA0 (T1)
ANDI T2, T1, $4000 <- this time the B button
BEQ R0, T2, //END
NOP

//PRINT1
LUI A2, $802C
ORI A2, A2, $B256
ORI A0, R0, $0070
JAL $802D66C0
ORI A1, R0, $0020

//END
LW RA, $0014 (SP)
JR RA
ADDIU SP, SP, $0018


so, here's what will come out:





Nice part, Kazeshin!

A question:
Do you have some source code files from messiaens/fraubers folder? (He released it a while ago on Jul, but took it down) I'm actually searching for the fire mario code, he made in C.
Your layout has been removed.
Part 3: Debugging

Basic asm coding is done with this. If you are interested in more, look into the checksumarea_11_9.txt and try finding out, what any random code does (start with looking at the sitenotes), if you don't know a instruction, google it. from now on, you should be able to understand most of these codes with help from google.

When writing a code, it's easy to make mistakes in ASM, because there are no names or such things. to find the source of mistakes, you've got to debug. debugging will tell you, what register has which value at any time. Also, you can set breakpoints on specific memory addresses, to find out, how they are getting modified and create simple codes like "a one up gives 2 lifes" (see this)


Originally posted by Viper
If using Nemu64 0.8:

Open the Memory editor (Plugins > Memory)
Go to the address you want to BP on
Decide if you want BPR or BPW ("Watch Type")
Rightclick the value of the address you want a BP on. It will be
highlighted a different color depending on the Watch Type. You'll notice that
it's going to watch all 32bits.
Next, cause the address to set the BP on to change (e.g. for ammo, you'd
shoot). The game should freeze and the Commands window with come up (if it's
not already open). The highlighted address is the ASM opcode that wrote to
the address you're watching. open Registers (Plugins > Registers) to see
all the values of the General Purpose regs (like the Notepad in GSCC2k2 shows
you).


Setting Breakpoints On Execute (BPX)

Nemu64 0.8: It's easy to set a BPX on Nemu. Just click beside the address
of the opcode you want to BPX on in the Commands window, and a checkmark will
appear beside it. BPX set :)


next part will be a short floating point explanation, because they have some special rules. the tutorial is over after that.