Work with Motorola 68000 assembly
From Sonic Retro
(Original guide by redhotsonic)
Welcome, to my ASM guide. It is important to ALWAYS back-up your hack/work/disassembly before making changes. This will teach you very basic ASM commands what make you aware what ASM is and what a disassembly is used for.
In this guide, I'll teach you
- What ASM means
- What a disassembly is used for
- The basics of ASM
- Learning how ASM works
- Editing some basics with ASM
This guide is for people who know a little ASM or none at all. If you have intermediate knowledge of ASM, then you might find a few tips from this guide, but if you're an expert at ASM, then this guide isn't for you because you know all this already.
Contents
What is ASM?
ASM stands for ASseMbly. It helps builds your commands into a ROM. Basically, ASM is instead of Hex (Machine Code); it uses words instead of numbers. That's why people use ASM more than Machine Code.
For example, this is hex: 11FC 0010 F766.
In ASM, it will look like this: move.b #$10,($FFFFF766).w.
It's just easier to read for us in words.
What is a disassembly?
A disassembly is a ROM (or a game) taken apart in displays all its bare code. It can have separate/multiple data files but only representing one thing. For example, the ROM will contain art for a character and codes for an object, etc, all in one ROM image/file. A disassembler will have 1 file for the characters' art, 1 file (or data) for an objects code, etc. It also means we can edit or add codes and it will shift the rest of the code and the pointers will be changed accordingly. Whereas if you tried editing the code in the ROM itself, we'll have a fear of over-writing some code as none of the code will shift. You can shift it yourself, but then all the pointers will need changing.
So it's easier to use a disassembler. But obviously you cannot play the game disassembled. So, you have to 'build' it. Once you're happy with your edits, you can click on the .BAT file called 'BUILD'. What this will do is assemble all those data files and codes into that final ROM image again. And there you have it. Your game is ready to play!
Getting started
If you have a disassembly already, you can skip this part. For the majority of the guide, I am going to be using Sonic 2 as the game reference, and Xenowhirls disassembly as the ASM reference. A list of disassemblies are here on the Disassemblies page. Or if you want to use the same game and disassembly I am using for this guide, then you can download it straight from here. Once you've downloaded the disassembly, you'll need to split the game (using a plain Sonic 2 ROM, named s2.bin). Download a Sonic 2 ROM and name it s2.bin and put it in the same folder as the disassembly. Then, open the .BAT file called SPLIT. You're disassembly is now ready to use!
The basics commands of ASM
Okay, in your folder, look for the S2.asm file and open it with a word editor (I use Notepad2, but the first notepad will be fine). Look at that, there's so much to it, but we can learn it. Close the file down for now, and let's get learning. I'll teach you some ASM commands. Let's get to the basics.
The Basic understanding
TheMoveCommand:
move.w #2,($FFFFFEB8).w
Let's look at the bit where it says move.w. Move, literally means move. It moves whatever you want to move to a certain point. But what does the .w mean? Well, this means word. You can also get .b (byte) and .l (longword). A byte is two nibbles (00), a word is 4 nibbles (0000), and a longword is 8 nibbles (00000000). The $FFFFFEB8 is a RAM address; these store bytes that you want and can change. You can consider it as a memory. When moving something to a RAM address, it must always be in brackets with a .w afterwards (well, most of the time this is the case, but this is a beginner's guide, so for now, it's always in brackets ending with .w).
You see the text 'TheMoveCommand:'? This is called a label, helping branches (that will be explained later).
Okay, so, move.w means it's moving a word. #2 is the byte we want to move, and must always put # in front of the number, so the Megadrive knows it is a decimal number. Because it's moving a word, that #2 in a word is #0002 (remember, a word is 4 nibbles). So #2 in 4 nibbles is #0002. You can put #0002 if you want, but you can ignore the 0's if they come first, so #2 is fine. If it was #20 we're moving, you'd then have to put either #20 or #0020. You can't put #2 because it thinks you mean #0002, it doesn't know you want that 0 after the 2. So putting #20 will be converted to #0020. If you want #200, you must either put #0200 or #200, etc.
If it was to be a move.b, it would only be moving a byte (two nibbles), so #2 would be #02. If it was to be a move.l, it would only be moving a longword (eight nibbles), so #2 would be #0000002. Get the idea?
So # means it's not a memory and is something else, for most of this guide, a decimal.
So # is decimal, so, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13...
If you add a dollar sign so you get this: #$2, it will then be treated as hexademical. So basically:
The #$ means hexadecimal, so, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12, 13...
The disassembler will always convert it into a hexadecimal number either way when you build. But #2 converted to hex is still #$2. If you put #9, it will be converted to hex, which is still #$9. But if you put #10, then when building, that will change to #$A. Using the numbers I just put above, can you see why? So if you put #12, it will be converted to $C. If you actually want it as 12, then you must put #$12, because that way, you've made it hexadecimal yourself, and when building, it will stay as $12.
The MOVE command
Anyway, this command, what is it doing?
TheMoveCommand:
move.w #2,($FFFFFEB8).w
The $FFFFFEB8 is the address we want it to go. The $ sign indicates the number is hexadecimal, and memory addresses are always represented that way. Let's say that at address $FFFFFEB8 it is #$0000. After the command above, it'll become #$0002. So before the command, ($FFFFFEB8).w equalled #$0000. After the command, ($FFFFFEB8).w equals #$0002.
Do you see how it works? No? Let's do another example.
Pretend $FFFFFEB8 is: #$0002
Let's say we want that 02 to be an 08, we do this:
move.w #8,($FFFFFEB8).w
Now, $FFFFFEB8 is: #$0008.
It works. Here's a longword example. It works the exact same way.
Pretend $FFFFFE10 is: #$00000000
move.l #6,($FFFFFE10).w
Now, $FFFFFE10 is: #$00000006
But remember that RAM addresses are treated as a hexadecimal number all the time. So for this next example, pretend $FFFFFE10 is: #$00000000
move.l #10,($FFFFFE10).w
We're moving a decimal number #10 to the RAM, but the RAM will treat it as a hexadecimal, so after this command, $FFFFFE10 will be #$0000000A. Remembering that the RAM address is hexadecimal, so it would have gone 8, 9, A, B... NOT 8, 9, 10, 11...
If you want that RAM to be 10, then you have to treat the number you're moving as a hexadecimal number. So pretend $FFFFFE10 is: #$00000000, after this next command:
move.l #$10,($FFFFFE10).w
$FFFFFE10 will be #$00000010. This is because you've converted the number to hexadecimal yourself; you moved #$10 to the RAM. Hope this makes sense!
Let's make things slightly more difficult. Let's say that $FFFFFFE6 is #$02030405. What would this become if we did this command?
move.w #4,($FFFFFFE6).w
Well, $FFFFFFE6 will become #$00040405. The first four nibbles (#0004) is what you just did. Remembering that the move.w is moving a word (4 nibbles, #0000), and there is 8 nibbles to this RAM address, it only does the first four nibbles. The last 4 nibbles in the RAM address remain unaffected because we only moved a word. If we did:
move.l #4,($FFFFFFE6).w
$FFFFFFE6 would become #$00000004, because you moved a longword (8 nibbles).
As a sidenote, remember that the smallest unit the MC68000 can address is a byte, so each RAM address refers to a byte. As an example:
$FFFFFE00 = #$12
$FFFFFE01 = #$34
$FFFFFE02 = #$56
$FFFFFE03 = #$78
Each address here is being treated as a byte. But, if we decided to use one of these addresses as a word:
move.w #5,($FFFFFE00).w
Then the above four addresses will look like this:
$FFFFFE00 = #$00
$FFFFFE01 = #$05
$FFFFFE02 = #$56
$FFFFFE03 = #$78
Note that moving a word to $FFFFFE00 affected both $FFFFFE00 and $FFFFFE01. Additionally, if $FFFFFE00 was interpreted as a longword, then its original value was $12345678, and its new value is $00055678. Since we only wrote a single word, $FFFFFE02 and $FFFFFE03 remain unaffected.
NOTE: It is not recommended to pick any old RAM address, as it may be being used for specific things. For a list of addresses and what they are used for in Sonic 2, look here.
All commands can have comments (it doesn't HAVE to have a comment). This is if you want to remember what this command does. For example:
move.b #4,($FFFFFFE6).w ; moves the byte 04 to the address shown
If you want to add comments after a command, you MUST put the Semicolon symbol ( ; ) after the command, then put whatever you want, as long as it stays on the same line. You can literally put any comment you want:
move.b #4,($FFFFFFE6).w ; moves 4 to the 'I am Super' RAM thingamajig, wazza wazza!
Comments are useful if you want to remember what the command is doing or just to set some notes.
The MOVE command isn't just restricted to moving numbers to RAM addresses. You can move a RAM address to another for example:
move.w ($FFFFFFE6).w,($FFFFFFE8).w ; copy RAM address to another
It's simple. But you MUST remember, RAM addresses are treated as hexadecimal all the time. Imagine $FFFFFFE6 is #$9AB6, and $FFFFFFE8 is #$1234. If you move $FFFFFFE6 to $FFFFFFE8, $FFFFFFE8 will now be #$9AB6. $FFFFFFE6 will also remain the same as you haven't told anything to move to that. So basically, you've just copied it. Another example:
move.l d0,($FFFFFFE8).w ; move data register to RAM address
d0 is a data register (hexadecimal again), but we'll explain what it's used for a little bit later on, but for now, imagine d0 is #$01234567. And $FFFFFFE8 is #$55555555. So, after this command, $FFFFFFE8 will be #$01234567. d0 will still stay the same obviously.
You don't have to move things to RAM address either:
move.w #$2534,d0 ; move this hexadecimal number to d0
d0 will become #$2534 after the above command.
There are more MOVE commands, but I wouldn't worry about them for now as this is only a basic guide. But here they are and a small description on what they do:
movem / MOVE Multiple / Can move data to multiple addresses. An example is it can move data from an address register to d0 and d1 and d2, etc.
The ADD command
addi.b #9,($FFFFFEB8).w
This is pretty much the same as the command MOVE, but it adds instead. You're probably asking, what does that i stand for in addi? i stand for immediate. No, it does not mean it will immediately do the command, it means it will add a decimal/hexadecimal number to the destination. So if you want to add a number to something, it's addi. If you want to add a RAM or register to something, then it's just add. REMEMBER, RAM addresses are always treated as hexadecimal.
Anyway, let's say $FFFFFEB8 is #$01010101 and then we did this command above; it'll become #$0A010101. It's A because it's hex, remember? We only added a byte, so it only does the first 2 nibbles. Do you see how it works? Here's another example.
Assuming that $FFFFFEB8 is #$01040202, after executing the below command:
addi.w #9,($FFFFFEB8).w ; add the word 09 to $FFFFFEB8
It'll become #$010D0202. Because we added a word. 9 in a word is #0009, it's only doing the first 4 nibbles in the address, so #$0104 add #0009 is #$010D. Remembering that the RAM address is hexadecimal, so it would of gone 8, 9, A, B, C, D. NOT 8, 9, 10, 11, 12, 13.
Again, it doesn't need to be numbers, you can do:
add.w ($FFFFFFE6).w,($FFFFFFE8).w ; add RAM address to another
Now, because we're not adding a number, it's no longer addi and just plain-old add. So, if $FFFFFFE6 is #$0003 and $FFFFFFE8 is #$0008, after this command, $FFFFFFE8 will be #$000B. Yes, B, because it's hexadecimal. So it would of gone 8, 9, A, B. NOT 8, 9, 10, 11. Otherwise, it's as simple as that. It is important for you to know that $FFFFFFE6 will stay as #$0003 seeming as we haven't told it to change.
Another example:
add.w d0,($FFFFFFE8).w ; add data register to a RAM address
If d0 is #$1234 and $FFFFFFE8 is #$AAAA, after the command, $FFFFFFE8 will be #$BCDE. d0 will obviously stay as #$1234.
Here is an extra command, the addq command:
addq.b #6,($FFFFFEB8).w ; add 6 quickly to the RAM address
addq is the fastest way to add and can be used on any destination (pretty much). But the problem is, it can only be used when adding a number between 1 and 8. So, if you want to add 6 to a RAM address, then addq is better to use than addi. If you want to add 3 to a RAM address, then:
addq.b #3,($FFFFFEB8).w ; add 3 quickly to the RAM address
But however, if you want to add 9 (or more) to a RAM address, then addi has to be used:
addi.b #9,($FFFFFEB8).w ; add 9 to the RAM address
If you're adding a RAM address to another like so:
add.w ($FFFFFFE6).w,($FFFFFFE8).w ; add RAM address to another
it doesn't matter if $FFFFFFE6 is between 1 - 8, you cannot use the addq. addq is literally only used when adding a number itself to the destination. As it's not a number and it's a RAM address, it's not addi either, it's just add.
The SUB command
subi.b #$12,($FFFFFEB8).w
This is nearly the same as the add command, except we're going the other way. Instead of adding, we're subtracting. I'll only give one example here, as this is pretty straight forward.
Pretend $FFFFFEB8 is: #$15161718
subi.b #$12,($FFFFFEB8).w ; going to take the byte $12 from $FFFFFEB8
Now $FFFFFEB8 is: #$03161718
Again, it doesn't need to be numbers, you can do:
sub.b ($FFFFFFE6).w,($FFFFFFE8).w ; add RAM address to another
If $FFFFFFE6 is #$6 and $FFFFFFE8 is #$8, after the command, $FFFFFFE8 will be #$2. $FFFFFFE6 will remain as #$6 obviously. Remember that for these type of commands, it's sub and not subi.
There is another command, subq. It works exactly the same way ass addq, but subtracting instead of adding. Again, only numbers, and only between 1 - 8.
Number formats and prefixes
I've already explained the basic two; decimal and hexadecimal. There are four ways in total to represent a number in ASM:
- Decimal (no prefix) - Digits are 0-9. This is what humans are used to reading.
- Binary (%) - Each digit represents one bit, which can be 1 (on) or 0 (off). '%10' in binary is the same as '#2' in decimal.
- Hexadecimal ($) - Each digit is one nibble (4 bits) and ranges from $0-$F. '#$10' in hexadecimal is the same as '#16' in decimal
- Octal (@) - 3 bits. Numbers range from 0-7. This is rarely used if ever. '@10' is the same as '#8'
The # is special and basically means the number after it is NOT a memory address. It can be used in conjunction to the above as follows, assuming $FFFFFEB8 is #$0000:
move.w #$FF,($FFFFFEB8).w
$FFFFFEB8 becomes: #$00FF, the #$FF would be the same as inputting #255:
move.w #255,($FFFFFEB8).w
Using either of these commands, ($FFFFFEB8).w will be #$FF.
Data and address registers
Data registers are temporarily data holders (like memory again). It will remember what you told it to remember. All data registers are longwords (whereas RAM addresses were bytes), and these types of registers exist from d0-d7. Example:
d0: #$00000000
move.b #3,d0 ; going to move 03 to the data register 0
d0: #$00000003
Until you tell it to change, that'll stay like that. Like said, it doesn't necessarily have to be d0, it can be... I don't know... d4? You can make d0 go to other places too.
move.b d0,($FFFFFEB8).w ; going to move the data from d0 to $FFFFFEB8
d0 will still be #$00000003, but $FFFFFEB8 will be #$03000000.
If the command was:
move.w d0,($FFFFFEB8).w ; going to move the data from d0 to $FFFFFEB8
d0 will still be #$00000003, but $FFFFFEB8 will be #$00030000.
If the command was:
move.l d0,($FFFFFEB8).w ; going to move the data from d0 to $FFFFFEB8
d0 will still be #$00000003, but $FFFFFEB8 will be #$00000003.
Address registers are also data holders, but these normally hold pointers, but can store data. These exist from a0 to a7, but avoid using a7 as it is a stack holder, which I will not cover in this basic guide but anyway, don't use it. We'll cover about the address registers a little bit later.
More ASM commands
The CMP command
cmpi.w #3,($FFFFFEB8).w
Ah, the COMPARE command. This just compares the byte you've put to the address. In this case, the word 03 to $FFFFFEB8. If $FFFFFEB8 is #$00030708, this equals the word #$0003. If $FFFFFEB8 was #$05030000, it would not equal the word #$0003 (because it's #$0503). Again, the i (immediate) is here. Same rules apply like the addi/subi. If it's a number you're comparing, it's cmpi, otherwise, it's cmp. Example:
cmpi.w #3,d0 ; See if d0 equals #0003 (it's cmpi here)
cmp.w d0,d1 ; See if d0 equals the same as d1 (it's cmp here)
There is no such thing as cmpq though.
The TST command
tst.w ($FFFFFEB8).w
tst stands for TeST. This is a disguise for CMP. Basically, 'tst' is comparing to see if the destination equals 0. Basically:
tst.w ($FFFFFEB8).w
Is the EXACT same as:
cmpi.w #0,($FFFFFEB8).w
The reason why you want to use tst instead of cmp is when seeing if something equals #0 or not, is because tst is slightly faster. You can consider this as 'cmpq', but only works on #0.
The BRA commands
This, is the BRANCH command. There are many branch commands, but we're going to take it one step at a time. The command BRA stands for BRanch Anyway. This is what you want the MegaDrive to branch (or go) to when you use this command. What do I mean? Well, take a look at this.
TheCode:
move.w #3,($FFFFFEB8).w
addi.w #3,($FFFFFF10).w
bra.s Morecodinghere
subi.w #3,($FFFFFEB8).w
Morecodinghere:
move.w #3,($FFFFFEBA).w
rts
You're probably thinking, 'What the hell?' Do not panic. Let's go through this. First, it's saying it's moving the word #0003 to $FFFFFEB8. Then it's adding the word #0003 to $FFFFFF10. Here is when our new command, BRA, comes in. It's saying to branch (go to) 'Morecodinghere'. As you can see, there is a label called 'Morecodinghere'. So, when it sees this, it goes to this label. Remember me mentioning about labels earlier? This is what labels are useful for! So now, we're starting from the label 'Morecodinghere'. So, the next thing it does, it is moving #0003 to $FFFFFEBA.
The subi.w command was ignored. This is because it got branched. If you took the BRA command out, it would do that SUBI command.
In the Morecodinghere label, you can see underneath its MOVE command, is a new command:
The RTS and BSR command
RTS means 'ReTurn from Subroutine'. So, it's telling to go back to where this subroutine was first executed. Let's look at this example:
Somecodes:
move.w #3,($FFFFFEB8).w
addi.w #3,($FFFFFF10).w
bsr.s Morecodinghere2 ; *
subi.w #3,($FFFFFEB8).w
rts
Morecodinghere2:
move.w #3,($FFFFFEBA).w
rts
For this example, that bra.s has changed to bsr.s. BSR stand for 'Branch to SubRotuine'. This means to branch to a new code, but remember to come back (in simple terms). So let's follow this code. First, it moves #0003 to $FFFFFEB8, and then adds #0003 to $FFFFFF10, then the new command, bsr. This means, go to the label 'Morecodinghere2' and return to me. So, go to 'Morecodinghere2', next command is to move #0003 to $FFFFFEBA. Next is 'RTS', which is telling you to return from this subroutine. Basically, where the '*' is in the comments. So now, you're back there. Next command is to subtract #0003 from $FFFFFEB8. Then finally, rts again, and that will return to whatever made the code jump to the label 'Somecodes'.
I hope that makes sense.
So far, you've been introduced to BRA and BSR. You might have seen me put a .s after these. The s stands for short. Try to add .s to the end of each branch whenever possible. But hence the name short, so it can only branch to short distances. If you tried making it branch to a label far away, .s might not work. If so, use .w (which stands for word) like so:
addq.w #3,($FFFFFF10).w
bra.w afarawaylabel
subi.w #$13,($FFFFFEB8).w
.s and .w are just as quick as each other, but .s is smaller in size, saving you space. So use .s when you can and if the branch is too long, use .w. Sometimes, even a .w can't reach, and if so, then you use:
The JMP and JSR commands
Whereas BSR stood for 'Branch to SubRoutine', JSR stands for 'Jump to SubRoutine' and JMP literaly stands for Jump. These two commands can jump almost anywhere. These commands do not need .s or .w at the end. So, you can use them in the same way:
add1.w #3,($FFFFFF10).w
jmp afarfarfarawaylabel
subi.w #$13,($FFFFFEB8).w
JMP and JSR uses more space and time to perform than BRA and BSR, so try to use the BRA/BSR. Remember:
bra.s > bra.w > jmp
bsr.s > bsr.w > jsr
Here are two new branch commands:
The BEQ and BNE commands
BEQ stands for 'Branch if EQual'. BNE stands for 'Branch if Not Equal'. This is where the compare command (cmp/cmpi) comes into use. Take a look at this.
BEQlabel:
cmpi.w #3,($FFFFFEB8).w
beq.s Itequaled
subi.b #$B,($FFFFFEB8).w
rts
Itequaled:
addq.b #2,($FFFFFEB8).w
rts
Let's go through this. First we're comparing if $FFFFFEB8 equals #0003. Then we've got our BEQ command. Basically, it is saying if $FFFFFEB8 DID equal #0003, then branch to the label 'Itequaled'. Now, because it branched, the subi.b command is ignored and it goes to the label 'Itequaled'. If $FFFFFEB8 did NOT equal #0003, it will carry on and do the subi.b command instead.
BNElabel:
cmpi.w #3,($FFFFFEB8).w
bne.s Itdidnotequal
subi.b #$B,($FFFFFEB8).w
Itdidnotequal:
addq.b #2,($FFFFFEB8).w
rts
Now, look at this. If $FFFFFEB8 did NOT equal #0003, it WILL branch, as we've used the BNE command. However, if it DID equal #0003, it will NOT branch and will carry on and does the subi.b command. I hope you understand this. Here is a test. For this test, pretend that $FFFFFF10 equals #$00000000.
Newlabel:
move.w #3,($FFFFFF10).w
addq.w #3,($FFFFFF10).w
cmpi.w #6,($FFFFFF10).w
beq.s Morecodinghere
subq.w #3,($FFFFFF10).w
Morecodinghere:
move.w #3,($FFFFFF10).w
rts
Here's the question; In 'New Label', does it branch to 'Morecodinghere'? The answer is yes. I'm not going to explain why it does; study it. If you still do not know why, go back to revise.
There are more BRANCH commands. Here they are, but I'm not going to explain, as you should get the idea.
BGE / Branch if Greater or Equal / If it equals, or is greater than the equal amount, branch
BGT / Branch if Greater Than / If it is greater than the equal amount, branch
BLE / Branch if Less or Equal / If it equals, or is less than the equal amount, branch
BLT / Branch if Less Than / If it is less than the equal amount, branchI hope this makes sense. There are more branches, but it’s not relevant to the basic guide.
The LEA command
LEA stands for Load Effective Address. You can load an address to an address register. This is where a0 to a6 becomes useful (briefly talked about them earlier). Example:
lea ($FFFFFF10).w,a4
Now $FFFFFF10 will be in address register a4. Or another way of putting it, a4 contains $FFFFFF10. This can be useful if you want to do a lot of calculations to one RAM address. Example:
move.b #3,($FFFFFF10).w
addq.b #2,($FFFFFF10).w
cmpi.b #1,($FFFFFF10).w
beq.s Gotolabelifequaled
addi.b #$43,($FFFFFF10).w
cmpi.b #$48,($FFFFFF10).w
beq.s Anotherlabel
rts
This is moving/adding/comparing $FFFFFF10 quite a bit. While there is no problem with this, there can be a quicker way. This is when address registers and the LEA command can help. If we load $FFFFFF10 into a1 for example, we can do this instead:
lea ($FFFFFF10).w,a1
move.b #3,(a1)
addq.b #2,(a1)
cmpi.b #1,(a1)
beq.s Gotolabelifequaled
addi.b #$43,(a1)
cmpi.b #$48,(a1)
beq.s Anotherlabel
rts
Basically, (a1) has taken ($FFFFFF10).w's place. This is a much quicker way, as ASM can do calculations quicker to an address register than to a RAM address. The charm is, after these commands, $FFFFFF10 will still have these calculations applied to it. So at the end, both (a1) and ($FFFFFF10).w will equal #$48.
If however, you want to do these commands above, but you don't want $FFFFFF10 to have any changed effects, then data registers are the answer:
move.b ($FFFFFF10).w,d0
move.b #3,d0
addq.b #2,d0
cmpi.b #1,d0
beq.s Gotolabelifequaled
addi.b #$43,d0
cmpi.b #$48,d0
beq.s Anotherlabel
rts
So after all this, d0 will equal #$48, but $FFFFFF10 will remain as #0.
The NOP command
This stands for No OPeration. It does nothing, literally. So, what is it used for? Mainly to slow things down. There are some technical calculations that take time to complete, and sometimes you don't want it to carry on too quickly. So using a nop will slow things down by 4 cycles, but that's it.
The DC command
This is to store data in the ROM. You can see it for art, mappings and other sorts of data. Here's an example.
dc.b 0, 1,$F8, 5, 0, 0, 0, 0,$FF,$F8
This is part of the ring's mappings. Looks weird, eh? As it is dc.b, its adding bytes, so here, it is adding 00 01 F8 05 00 00 00 00 FF F8. As you can see, there are $ signs there. This just means that they are hexadecimal, but if putting number 0 - 9, you don't have to put a $.
- dc.b 0, $11, 1 = Correct
- dc.b 0, 11, 1 = Incorrect
You can also have dc.w or dc.l (word and longwords). If that was dc.w, it'll be putting into the ROM 00 00 00 01 00 F8 00 05 00 00 00 00 00 00 00 00 00 FF 00 F8.
DC can be used for palettes, mappings, pretty much anything.
The CLR command
This means CLeaR. This can clear data (obviously) to any RAM address you want (can clear data and address registers too). Let's do an example. Pretend that $FFFFFF10 is #$05050505.
clr.b ($FFFFFF10).w ; Clear the byte from $FFFFFF10
Now, $FFFFFF10 is #$00050505.
clr.w ($FFFFFF10).w ; Clear the word from $FFFFFF10
Now, $FFFFFF10 is #$00000505.
clr.l ($FFFFFF10).w ; Clear the longword from $FFFFFF10
Now, $FFFFFF10 is #$00000000.
Quick Homework
$FFFFFEB8 is #$00000000 and $FFFFFF10 is #$01010101. What will these two address show after this process?
Code:
addq.w #4,($FFFFFEB8).w
addq.b #3,($FFFFFF10).w
cmpi.w #3,($FFFFFEB8).w
beq.s Codetwo
move.l #$A,($FFFFFF10).w
subq.w #3,($FFFFFEB8).w
rts
Codetwo:
clr.l ($FFFFFF10).w
rts
Answer: $FFFFFEB8 is #$00010000 and $FFFFFF10 is #$0000000A. Did you get that? If you didn't, try looking at it again, or go back to more commands for help.
Editing the basics
Editing the basic is pimp-squeak. You literally do not need any ASM knowledge at all. If you ever want to edit palettes, mappings, text, arrays, start positions, etc, you can search for them. Open up the file s2.asm and go on Edit > Find. Type 'word_C054:' (without the quotation marks). You should come to Level size array. Underneath, you'll see this:
word_C054: dc.w 0,$29A0, 0, $320; 0
This, is the level size array for Emerald Hill Act 1. They go down in order of Level ID. If you remember what I told you about the command DC, you should know this is 00 00 29 A0 00 00 03 20. The very last 0 is a comment as ; is before it. It's a weird comment. Change the array to what you want. But remember, if you going to change them 0's into A or more, put a $ in front. Example:
- word_C054: dc.w 0,$29A0, 22, $320; 0 = Wrong
- word_C054: dc.w 0,$29A0, $22, $320; 0 = Right
If for example, if you change that $29A0 into $2900, when built, EHZ1 will end a bit quicker!
Or say you want Sonic's top speed to go a little bit faster when you get speedshoes. Search for "super_shoes:" and you see
move.w #$C00,(Sonic_top_speed).w
You can change that number higher if you want him a bit faster or lower for slower! Simples!
If you ever want to change things like this, search for it using the Find tool. Example, you want to edit Sonic's start position. Edit > Find > type 'start' (without the quotation marks) and keep finding until you come to Character start location array. Edit away.
Some things can't be done in ASM, like Sonic's palette. ASM tells to load a file. If you go to the folder ART/PALETTES, you'll find a file in there called something like 'Sonic's palette'. You edit that using a hex editor. So if you can't find the stuff you want to edit in ASM, look for the file.
That's it. Hope you enjoyed the guide and have learnt something today. Now get out there and show us what you're made of!