(I request a sticky for this thread.)
This is a tutorial I've been working on a bit at a time, to explain ASM in a hopefully easy-to-understand way. I admit, Ersanio's tutorial is good, but when I was first learning ASM it was EXTREMELY hard for me to understand... Other ASM tutorials I found were scattered around the Internet, and most of them were even worse.
So, here's this. If you want to understand ASM better, read through this. Meanwhile I'll be here to answer any questions you have about it. Be warned, though, that people like smkdan and smwedit are about 20 times better at ASM than I am, so by their standards this tutorial might not be perfect. It's the best I got though. If anyone here has a suggestion as to how to improve the tutorial, let me know in this thread. I'm trying to help people here, not show off.
LESSON 1: The Core Commands
The first thing you should know are the two, what I call, Core Commands. These are LDA and STA. The way ASM works is that you get three variables to use-- the Accumalator (I like to call it "A"), the X Register (I call it "X") and the Y Register (I call it "Y"). I'll explain X and Y later on, but "A" is very very important. It's what you use to transfer data into the game.
The command LDA (which means "Load Data to Accumalator") loads a hex value into "A". The command STA (which means "Store Data from Accumalator") takes whatever's in "A" and transfers it to a value on the Ram Map. So suppose we want a custom block that immediately makes you Big Mario when you pass over it.
Well, let's look at the Ram Map. It says the address $19 is Mario's Powerup status. So how would we set this block up?
(Load the value 01 into "A". The # sign before the $ sign means we're loading a specific number, NOT a Ram Address. If we didn't have the # sign, it would load whatever's in $01 on the Ram, which is apparently the Y position of a sprite... We don't want that.)
(Take whatever's in "A", in this case the value 01, and change Ram Address $19 to that. Since $19 is Mario's Powerup status, setting it to 01 will change him to Big Mario.)
(This is a biggie. The RTS command stands for Return From Subroutine, and it makes the code stop executing and get back to the game. If you don't have RTS at the end of the code, the game will freeze.)
Get it so far? LDA loads a value into "A", STA takes "A" and puts it in the game, and RTS finishes the code.
Here's another nice command. STZ. It takes a Ram Address, and makes it 00 no matter what. You don't even have to have anything in "A" first. So if we want to make a custom block that always makes Mario small, we only need two lines:
(Set the Powerup status to 00, meaning Small Mario)
(End the code)
STZ is cool mainly 'cause you don't need to change "A" to get it to work. But be warned: you can't STZ a "long address" (an address that's 6 hex digits long instead of 2 or 4) or the game will crash.
LESSON 2: Branching and Conditions
Next up: Branching. What if you want your code to do certain things depending on if certain conditions are met? You need some new commands for this.
First is CMP. It takes whatever's in "A", and compares it to a value. Then you use other codes that do stuff depending on if the compared values match or are different. The BEQ command needs to be used RIGHT after CMP. If the compared values are EQUAL, the code jumps to somewhere else in the file that you specify. But how do we specify? We use something called Symbols.
Suppose we want the custom block to only make Mario big if he's small, kinda like how the Midway Point does. Well...
(We're loading whatever's in the Powerup status to "A". So if he's small, we're loading 00, if he's big we're loading 01, etcetera.)
(We take what's in "A", and compare it to the number 00, which is Small Mario.)
MakeBig (Okay, check this out. After BEQ I didn't put in a value, I put in a word. BEQ works like this. But you HAVE to have that word as a Symbol somewhere else in your code or the game will crash.)
(If $19 is NOT equal to the compared value 00, the game will not branch to "MakeBig" and instead call this code, ending the code and doing nothing.)
(Otherwise it'll jump to "MakeBig" and start calling the code below. See how I set this up? This is how you should set up Symbols.)
(Load 01 into "A"...)
(...then load "A", which is 01, into the Powerup Status, making Mario into Big Mario.)
(End the code.)
See how this works? First the game checks to see if the Powerup Status is 00. If it is, it skips ahead to the code that makes you Big Mario, then finishes. If it's not, it doesn't skip ahead, and instead finishes right away.
There are MANY other branching codes. BNE is the opposite of BEQ. The BNE command branches if the compared value is NOT equal. BCC branches if "A" is LESS THAN the compared value. BCS branches if "A" is GREATER THAN the compared value. Finally, BRA always branches, and you don't even need a CMP command in front of it. There are other branching codes, but these are just about the only ones you need.
Here's another technique to keep in mind. If you use a Branch command without using CMP first, it counts as using CMP #$00. This is true with any Branch command. The above example would've worked just as well without the CMP #$00 in there.
LESSON 3: Math commands
The INC command increases a value by 1, and DEC decreases a value by 1. So if we want a block that acts as a conveyor belt and moves Mario to the right one pixel every frame, we should use this code:
(Increase Mario's X Position on the screen by 1. The screen will scroll with him, making it act as a conveyor belt.)
(Finish the code.)
Or if we move to the left instead of the right? Simple, just change that INC to DEC.
This is important: If you use INC when the value is FF, it becomes 00, and if you use DEC when it's 00 it becomes FF. Don't forget this.
But more often than not, we don't want to count by just ones. Let's make a block that moves Mario 64 pixels to the right when he touches it (that's 40 in hex). For this, we need some new commands: CLC and ADC.
(First we load Mario's screen x position into "A").
(This goes by itself, and ADC always comes right after.)
(What this command does is add a value to "A". So if the value (in this case Mario's screen x-position) is, say, 24, now it's 64.)
(Now that we've taken the X position and added 40 to it, let's store the new value back into the Ram, essentially moving Mario to the right 40 pixels.)
(Finish the code.)
For subtraction, instead of CLC and ADC, you need to use SEC and SBC respectively.
For multiplication, use ASL. It should be set up like this:
All this does is multiply "A" by 2. For division, use LSR instead, but set it up the same way. You can also multiply what's in Ram Addresses by putting the Address instead of A next to ASL. Be careful when doing this though.
LESSON 4: Tables and Indexing
Remember how I said there were three variables you can use? "A" for Accumalator, the X register and the Y register. Yes. Well, it's time to learn about X and Y.
X and Y pretty much work the same way as "A", but with two differences. One, they have more uses than "A". Two, the game has more specific uses for them in many cases. You can load values to X and Y almost the same way as you can with "A". While the LDA command loads a value into "A", to load to X and Y you use LDX and LDY respectively. To take X or Y and store it to a Ram Address, instead of STA you use STX and STY respectively.
It's very, very useful to have these two extra variables at your disposal, as it helps to have more control over various elements of your code without having to do any serious data rearranging. But X and Y's main use is for Tables.
What is a Table? It's basically a variable with more than one possible value. Tables are set up like this (below), and they need to be set up in parts of the code SEPERATE from the executing part. So the best place to set these up are in between Symbols and RTS commands, where the code will never actually execute (but the game will still read these lines).
Note that the hex values on this Table were just values I pulled off the top of my head... The values in the Table can be anything you want, not just what I have written up there. But they're always set up just like that; Symbol name in front of the Table, dollar sign in front of each number (no # sign or spaces, ever), comma seperating each number and no comma at the end.
Making the Table is only the first step... Now we want to USE the table. Suppose we want a custom block that activates the Blue P-switch for a longer period of time depending on whether you're Small, Big, or Fire/Cape Mario. This is how we'd do it.
(Load the power-up status into the Y Register. If you're Small, 00 is loaded, if you're Big we load 01, Cape is 02 and Fire is 03.)
(See how I set this up? At the end of the code is the Table with this Symbol name attached. What we're doing is taking Y, which is the Powerup status of Mario, and choosing the value at that position of the Table and setting it to "A". So if it's 00, we take the first value, if it's 01 we take the second value, 02 we take the third, and 03 we take the fourth. Make sure you set it up JUST like this; no space after the Symbol name, a comma, and either the x or y in lower-case RIGHT after the comma.)
(Now we take "A", which has a value based on your Powerup status thanks to the Table below, and set it to the remaining Blue POW timer. Note that this does NOT change the music-- if we want to do that, we need to add some more code.)
(Finish the code.)
(Now we're declaring the Table, and we put it after the RTS so it won't be treated like actual code.)
dcb $3F, $7F, $FF, $FF
(Here's the values on the Table. Remember how I said 00 was small, 01 was Big, 02 and 03 were Fire and Cape Mario? Well, the first value on the Table, which loads if the Powerup Status is 00 (meaning Small), is 3F. So if Mario's Small, the Blue Pow Timer will be set to have 3F time left on it (which is 25% of the time a normal P-switch gives you; normal switches give you FF time). If the Powerup status is 01, it loads the second value, which is 7F, so the Blue Pow Timer gets 7F time on it if Mario is Big (which is 50% the normal value). Finally, if it's 02, it loads the third value, and if it's 03 it loads the fourth value. Both of these are FF, so this means if Mario is Fire OR Cape Mario, the Blue Pow Timer will have 100% it's max value when hit.)
We don't put another RTS at the end of the Table, since it's not actually executable code, so it's not needed.
So, what this block should do is set the Blue Pow Timer to 25% the normal time if you're Small, 50% time if you're Big, and 100% time if you're Fire or Cape Mario. And of course, you can change these table values around to change the times. If you want it to last longer if you're Small and shorter if you're Big or Fire or Cape, simply put bigger values in front and smaller values in back. The best way to decide what you like best is to experiment a bit until you find your favorite settings.
If we want to change the music when we hit the block, add this code right after the LDA $14AD and right before the RTS.
(Replace the dashes with the value that matches the music number you want to play. You may have to look in Lunar Magic to see.)
(Remember this address. $1DFB controls the music that's playing. Any time you write a value to this address, you can change the music.)
To make sound effects play along with or instead of music, use $1DF9, $1DFA and $1DFC. Each of these addresses control a different selection of sound effects, called "banks". There's probably a list somewhere of which sound numbers are what, but unless you have that you'll need to experiment, unless you have the SMAS SFX patch, which has a list, but with that patch you can't use non-SMAS custom music. (However you do get more sound effects to choose from.)
So, recap. LDX and LDY load values into the X and Y Registers. STX and STY take X and Y, and load them into a Ram Address (but you actually won't use these very often). To load a value from a table, use LDA [table name],x or LDA [table name],y but make SURE you actually have a value stored in X or in Y in the first place.
Here's some more sweet commands you can do with X and Y.
TAX and TAY take "A" and copy it into X or Y. These are very useful.
TXA and TYA do the opposite; they take X or Y and copy it to "A".
TXY takes X and copies it to Y.
TYX takes Y and, you guessed it, copies it to X.
LESSON 5: Using the Stack
Alrighty, time to learn about the Stack.
The Stack is awesome. It's like a really huge bookshelf you can put all your "books" in and take them out later. The problem is, you can only take out the last book you put in. If this doesn't make any sense, hopefully it will later. It's not really
a bookshelf... it's just a massive database in the Ram for your benefit. Watch...
PHA means "Push Accumalator". It takes "A" and stores it to the Stack as the next "book". Now this value is preserved, and you can restore it to "A" any time you like with PLA "Pull Accumalator".
It works like this. Suppose you load "A" with the value in some Table. Now, the value in the Table isn't always the same, obviously. And you need to use this value again in some later code, but you can't use LDA #$-- because the value isn't always the same. You use the stack! Use PHA (with nothing after it), and the value is stored in that virtual bookshelf, no matter what it is. Then when you write PLA (again, with nothing after it), it takes that last stored value, whatever it was, and loads it into "A" once again. Be careful; when Pulling from the Stack, whatever was "pulled" no longer exists in the Stack. Never try pulling something from the Stack that doesn't exist.
Furthermore, you can push multiple values to the Stack. If you have PHA in your code three seperate times, the Stack will have three values. You could say the "bookshelf" has three "books". But when you Pull, the code pulls only the number on TOP of the Stack. So if you have three PHA commands, then you have a PLA, it'll pull the value stored with PHA Number Three. Then if you PLA again, it'll pull the value from PHA Number Two. Again, don't try and pull from the Stack when the Stack is empty, or your emulator will fall through a woodchipper.
So, PHA and PLA deal with "A". To push X or Y, you should use PHX and PHY. And to pull from the Stack and load it to X or Y, you should use PLX and PLY.
There are not three seperate stacks for "A", X and Y. They all share the same Stack. Keep that in mind.
The Stack is extremely useful when coding custom sprites, as X and Y have to be certain values at certain points of the code (not so much in custom blocks). But I'll get to that in a future lesson.
Oh yes, one thing smkdan reminded me about... There are some commands known as JSR and JSL. Both of these work like the BRA command, except when the code comes to an RTS (or RTL, in the case of a JSL), it jumps back to the point right after the JSR/JSL. The catch: These commands use the Stack to store what part of the code to jump back to. So, if you Push to the Stack, then JSR, then Pull again before the JSR code is finished, well, your code will make itself into some delicious scrambled eggs. NOT something you want to do. Keep this in mind when using JSR/JSL and the Stack together. ;)
LESSON 6: Binary commands
This one's a little tricky. Basically it's possible to get multiples of a value by using Binary. To do that, you use the AND command.
What AND does is take the hex value in "A", and another hex value, then converts them both to an 8-digit binary number (binary is only 0s and 1s). Then it compares the two binary values, and makes a third value (also 8 digits) depending on what the other two values are. The rule: If the same digit of either value is a 0, the digit in the 3rd value is a 0. If both digits in either value are a 1, that digit in the 3rd value is a 1. Once the 3rd value is generated based on this rule, the value is converted back into X and stored to "A".
Complicated? Here's an example. By the way you should really use Windows Calculator to help you when transferring hex to binary and back.
(The first value is 88. In binary, this translates into 10001000.)
(The second value is C6. In binary, this becomes 11000100.)
Okay, now we have our two values. AND will do this to it:
The first digit had a 1 in both values, so it stayed a 1. All the other digits had at least one 0 between the two values, so that digit in the 3rd value became 0. Then the result, 10000000, is converted back into hex, which is 80. This means that "A" now has the value 80.
Here's another example. Let's change that LDA #$88 to an LDA #$7C. This translates into 01111100.
(second... it's the same as in last example)
The first digit had one 0, so the result had a 0. The second had two 1s, so it resulted in a 1. Digits 3 through 5 had one 0, so they all resulted in 0s. The 6th digit had two 1s, so the result was a 1. The 7th digit had a 0 so it results in a 0, and the last digit had two 0s so it also results in a 0. The result, 01000100, translates into hex as 44, so the value 44 is what's now stored in "A".
So how is AND useful? Suppose we want a custom block that randomly makes you Small, Big, Fire or Cape Mario. SMW does have a Random Number Generator, but it's difficult to use, so we can just get it based on the animation frame. And to make THAT work, we can use AND to make the animation frame a multiple of 4, and transfer it to the Powerup status! Watch.
(Load "A" with the Animation Frame number. Note: In Lunar Magic it says there are 16 animation frames. Well, one of THOSE animation frames is 0F of this.)
(Compare "A" with the Binary value 00000011. This makes "A" into a multiple of 4. It sounds crazy, but test it and it works every time. The first six digits are always 0, the last digit is 1 only if "A" is an Odd number, and the second-to-last digit is 1 if the ones digit has been Odd and then Even again an Even number of times. It sounds seriously crazy, but you'll amazingly get a number from 00 to 03 EVERY time with this. It's an incredible technique.)
(Store this multiple of 4 to the Powerup status. Now Mario will be Big, Small, Fire or Cape depending on the animation frame, which is moving so fast it's at a seemingly random time.)
(Finish the code.)
Remember, if you want to play a sound effect, you need to write in the extra code before the RTS.
Here are some other binary-effecting commands. ORA is like AND, only it works opposite. If either of the two values have a 1, the third will have a 1. The third value will only be a 0 if both values have a 0 in that digit. ORA is good when you are dealing with sprite properties, which are stored in binary format, and need to combine two values together.
Then there's EOR. This one's a bit tricky. If the first and second values both have 0, the third will have 0. If they both have 1, it'll also be 0. If one has a 1 and one has a 0, the third will be a 1. EOR is useful when you want a value to go back and forth between two possibilities based on some conditions, though it's not easy to set up sometimes.
There are other binary-effecting commands out there, but they're too obscure and/or complicated for me to have to explain.
ADVANCED FURTHER READING
The Data Bank (DB) register
When accessing memory (ROM or RAM), you pick an appropriate addressing mode to use in your instruction. The '816 has many of these, but here's a few common ones:
LDA $78 ;direct (1byte address)
LDA $0078 ;absolute (2byte address)
LDA $7E0078 ;long (3byte address)
Normally all of these would access the same RAM (7E:0078) but when using absolute addressing (and ONLY absolute addressing), the DB register is used. The '816 has 24bit addressing, but since absolute only provides a 16bit address, it needs the remaining 8bits to come form somewhere. This is what the DB register is used for; it specifies the bank / upper 8 bits of the address.
The DB register is modified with the PLB
instruction. It pulls 1 byte from the stack and sets that as the new DB. An example of it being used can be:
LDA #$7E ;I want to change the DB to the first RAM bank
PHA ;push A (7E)
PLB ;pull DB (DB = 7E now)
LDA $2000 ;if you look in the RAM map, this would access the first byte of a decompressed GFX file
A common use is in custom sprites, where data is accessed using absolute addressing.
PHB ;save the original DB
PHK ;by pushing the PB register (bank of code currently running)...
PLB ;...the DB is effectively set to the bank of the sprite's code and data
This allows absolute addressing to access data within the sprite's code bank.
When using direct
addressing, the DB is always overriden temporarily to use bank 00.
When using long
addressing, the DB is overridden temporarily to use the bank supplied in the instruction.
High on life is the best high.