Please: If you find any bugs in this guide or do not understand anything, please say so here (or if topic is older than a month), on MSN Messenger (address found on my profile).
Welcome, to my ASM guide. Now, before we start, I am still learning ASM fully myself, so, there may be some bugs still in this guide. So, it is important to ALWAYS back-up your hack-work before making changes. Plus, I'm using Sonic 2 disassembly, so this guide is based on Sonic 2. If you're hacking Sonic 1 or something else, you can read on, but the activities will be different.
In this guide, I'll teach you
- What ASM means
- The basics of ASM
- Learning how ASM works
- Editing some basics with ASM
- Making a special ring for entering special stages
This guide is for people who know a little ASM or none at all. I've learnt my ASM skills from drx's ASM guide, and from SMTP, Stephen and Ultima.
Ok, you need all the basics. Here, from the help of Stephen, is everything packed that you'll need. I will be using Nemesis ASM build, so all references will be from his. If you wish to use a different build, you may do so, but not everything may be correct here. The pack I've given contains Nemesis build from drx's site. You'll also need SonED2 (contained in the pack).
(Downloads will be here in guide later)
If you get the build and SonED2 separately, firstly, extract everything from the build into a folder. Put your Sonic 2 ROM file in the same folder (must be a clean S2 ROM) and rename it to S2.BIN. Double click on split.bat and a load of folders will be created. Then, extract all the contents from SonED2 into the same folder into the build (say yes to anything it wants to over-write).
Once you have everything, we'll start learning.
What is ASM?
ASM stands for ASseMbly. It helps build your 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.
The basics commands of ASM
Okay, in your folder, look for the S2.asm file and open it with a word editor (I use Notepad). 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 go to the first one.
The MOVE command:
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's the .w mean? Well, this means word. You can 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 a word (I highlighted it blue, in case you do not know what I mean). You see the text in RED saying 'The MOVE command: '? This is called a label, helping branches (that will be explained later). So, this command, what is it doing?
Well, let's go through it, 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 Genesis knows it is a byte. And the $FFFFFEB8 is the address we want it to go and you must always put a $ in front of the address, so Genesis knows it's an address.. Let's say that at address $FFFFFEB8 it is 00 00. After the command above, it'll become 00 02.
Do you see how it works? No? Let's do another example.
Let's say we want that 02 to be an 08, we do this:
Here's a longword example. It works the exact same way.
Let's make things slightly more difficult. Let's say that
$FFFFFFE6 would become 04 03 04 06, as the 04 in RED is what you just did.
NOTE: It is not recommended to pick any old RAM address, as it may be being used elsewhere. For a list of addresses, 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.
There are more MOVE commands, but I wouldn't worry about them for now. Here they are if you ever look back here:
The ADD command
This is pretty much the same as the command MOVE, but it adds instead. Example, let's say
add.w #4,($FFFFFEB8).w ; going to add the word 04 to $FFFFFEB8
Here is an extra command:
The SUB command:
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.
sub.b #3,($FFFFFEB8).w ; going to take the byte 03 from $FFFFFEB8
Here is an extra command:
Data and address registers
Data registers are temporarily data holders (like memory again). It will remember a byte you told it to remember. All data registers are long words, and these types of registers exist from d0-d7. Example.
d0: 00 00 00 00
move.b #3,d0 ; going to move 03 to the data register 0
d0: 03 00 00 00
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) ; going to move the data from d0 to $FFFFFEB8
d0 will still be 03 00 00 00, but
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. Stack is a reserved data of the RAM for temporary things and I'm not quite sure what, so to be on the safe side, don't use it.
More ASM commands
The CMP command:
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 00 03 07 08, this equals the word 00 03. If $FFFFFEB8 was 05 03 00 00, it would not equal the word 00 03.
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 Genesis to branch (or go) to when you use this command. What do I mean? Well, take a look at this.
You're probably thinking, 'What the hell?' Do not panic. Let's go through this. First, it's saying it's moving the word 00 03 to $FFFFFEB8. Then it's adding the word 00 03 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. So, the next thing it does, it is moving 00 03 to $FFFFFEB8.
The sub.w command was ignored. This is because it got branched. If you took the BRA command out, it would do that SUB command.
In the Morecodinghere label, you can see underneath its MOVE command, is a new command:
The RTS command
This means 'Return To Subroutine'. So, in that code, it's telling to go back to the label, 'The CMP:'. It is important to put RTS. If you do not, it will carry on.
Because of that RTS command, the label 'Sumrandomcode' is being ignored. If the RTS command was not there, the 'Sumrandomcode' label would take place.
Here's 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 comes into use. Take a look at this.
Let's go through this. First we're comparing if $FFFFFEB8 equals 00 03. Then we've got our BEQ command. Basically, it is saying if $FFFFFEB8 DID equal 00 03, then branch to the label 'Itequaled'. Now, because it branched, the sub.b command is ignored and it goes to the label 'Itequaled'. If $FFFFFEB8 did NOT equal 00 03, it will carry on and do the sub.b command.
Now, look at this. If $FFFFFEB8 did NOT equal 00 03, it WILL branch, as we've used the BNE command. However, if it DID equal 00 03, it will carry on and do the sub.b command. I hope you understand this. Here is a test. For this test, pretend that $FFFFFF10 equals 00 00 00 00.
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.
Branch to SubRoutine
Branch if EQual
Branch if Not Equal
Branch if Greater or Equal
Branch if Greater Than
Branch if Less or Equal
Branch if Less Than
I hope this makes sense. There are more branches, but I do not understand them myself.
The JMP command
JMP stands for JuMP. It does exactly the same as BRA, but it can jump to any area in the ROM. You see, Branch can't go anywhere, but it is still more common than JMP. Even though JMP can go anywhere, there is no such thing as 'Jump if equal' or 'Jump if not equal' etc. Only JMP, and JSR (Jump to SubRoutine), which is pretty much the same.
The LEA command
I've never used this, but I know what it means. LEA stands for Load Effective Address. You can load an address to somewhere else. Example:
Imagine at $FFFFFF10 was 00 02 00 04 and a4 was 00 00 00 00. After this command, a4 will now be 00 02 00 04. Apparently, this works in the exact same way as MOVEA does, but don't take me word for it.
The NOP command
This command is apparently illegal. This stands for No OPeration. It does nothing, literally. So, what is it used for, I do not know, to slow down the process of where it is? It's been explained that it waits for all processes to finish, whether it's true, I have no idea.
The DC command
This is to store data in the ROM. What does DC stand for? I'm not actually sure, but you can see this anywhere. 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 there is more than one nibble being inputted it. Why a $ instead of a # sign, I don't know. When you get a number on its own, like 1, when the ROM gets built, a 0 appears in front. So 1 equals 01. Example:
dc.b 0, $11, 1 = Correct
dc.b 0, 11, 1 = Incorrect
dc.b 0, $11, 01 = Incorrect
You can also have dc.w dc.l. 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. Let's do an example. Pretend that $FFFFFF10 is 05 05 05 05.
clr.b ($FFFFFF10).w ; Clear the byte from $FFFFFF10
clr.w ($FFFFFF10).w ; Clear the word from $FFFFFF10
clr.l ($FFFFFF10).w ; Clear the longword from $FFFFFF10
$FFFFFEB8 is 00 00 00 00 and $FFFFFF10 is 01 01 01 01. What will these two address show after this process?
Answer: $FFFFFEB8 is 00 01 00 00 and $FFFFFF10 is 00 00 00 09. 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 "array" (without the quotation marks). Keep looking until you 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 two or more nibbles, 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 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, if you can't find that, contact me.
Making the special ring object
Ok, things are going to get complicated here, but if you follow this thoroughly without missing bits, you'll be fine, and it'll be a breeze. Okay, first of all, open up your s2.asm file. Edit > Find > Type "SprPoint:" (without the quotation marks). Here, is the list object. These are pointers on where the code is. The comments are the object number, but not in HEX format. Here's a little guide on the object list.
Sprite_1637C is an empty coding. So, object 4C is empty. We can use this. Find:
dc.l Sprite_1637C ; 76
Name it to:
dc.l Sprite_Specialring ; 76
Now, it'll be going to the Specialring pointer, except it doesn't exist. But we can sort that. Find "Red" (without the quotation marks) and the second find should display this:
; Red spring
Just before the first line before the word Sprite, insert all this:
; Special Ring
Sprite_Specialring: ;This is when the pointer we changed comes to.
jmp off_SpecialringIndex(pc,d1.w) ;moves us to the next bit of the code (look down)
loc_Specialring_Init: ;graphics and such are set up here
loc_Specialring_Frame: ;executed every frame - for animation/drawing
loc_Specialring_Collision: ;executed upon collision with sonic
loc_Specialring_Delete: ;delete the object
Oh, my. This looks complicated eh? There are comments there to help explain what which part does. This is what Ultima gave me, as my new object code sucked. Always use this when making new objects, and just change to name Specialring to whatever, but in this case, leave it. The loc_Specialring_Collision: part is where we are going to add our brand new code.
loc_Specialring_Collision: ;executed upon collision with sonic
move.w #1,($FFFFFEB9).w ;add one word to $FFFFFEB9
This is all we need at the collision, seriously. Now, we need to find the bit when the level fades to go to the next level. Save the ASM file, the go on EDIT > Find > loc_1429C: and you should find this:
loc_1429C: ; CODE XREF: ROM:00014292 j
Change it to:
loc_1429C: ; CODE XREF: ROM:00014292j
cmpi.w #1,($FFFFFEB9).w ; Does $FFFFFEB9 equal 00 01?
bne.s nextlevel ; Branch if not
move.b #$10,($FFFFF600).w ; set game mode to Special Stage
move.w #1,($FFFFFE02).w ; Set level reset
Ok, the line in blue has been deleted. The lines in RED is what you've added, and the line in green is what you've shifted. Now what happens is just before the level fades, if $FFFFFEB9 equals 00 01, it will go to the special stage instead of the next level. If it does not equal, it will go to the next level. When you go to the special stage or next level, $FFFFFEB9 also gets cleared, so it's ready for the next level when you put another ring in there.
Now, Find > Edit > "loc_1428C: " (without the quotation marks) and you'll find this:
loc_1428C: ; CODE XREF: ROM:00014286j
loc_1428C: ; CODE XREF: ROM:00014286j
The lines in green is what you add/edit. This supports the ROM to go to the next level after the special stage. If followed correctly, this should now work. Save your ASM file, and put an object in EHZ act 1 to test it. Put 4D 00 08 as the object. To build, double-click build.bat and after, S2BUILT.BIN will appear. You may need to fix the checksum before playing. Now test away.
Edited by Selbi, 29 April 2011 - 11:43 PM.