Actions

SCHG How-to

Difference between revisions of "Work with Motorola 68000 assembly"

From Sonic Retro

(The ADD command: A bit of clarification, since that part was ambiguous and could mean two things)
(Data and address registers: Wow, that was amazingly wrong)
Line 125: Line 125:
 
<asm>move.b #3,d0 ; going to move 03 to the data register 0</asm>
 
<asm>move.b #3,d0 ; going to move 03 to the data register 0</asm>
  
d0: 03 00 00 00
+
d0: 00 00 00 03
  
 
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.
 
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.
Line 131: Line 131:
 
<asm>move.b #d0,($FFFFFEB8) ; going to move the data from d0 to $FFFFFEB8</asm>
 
<asm>move.b #d0,($FFFFFEB8) ; going to move the data from d0 to $FFFFFEB8</asm>
  
d0 will still be 03 00 00 00, but $FFFFFEB8 will also be 03 00 00 00.
+
d0 will still be 00 00 00 03, but $FFFFFEB8 will also be 03 00 00 00.
  
 
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.
 
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.

Revision as of 14:13, 9 July 2008

(Guide originally written by redhotsonic)

Welcome, to my ASM guide. 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, StephenUK and Ultima.

Getting started

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

<asm>move.w #2,($FFFFFEB8).w</asm>

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?

<asm>move.w #2,($FFFFFEB8).w</asm>

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.

Pretend $FFFFFEB8 is: 00 02

Let's say we want that 02 to be an 08, we do this:

<asm>move.w #8,($FFFFFEB8).w</asm>

Now, $FFFFFEB8 is: 00 08.

It works. Here's a longword example. It works the exact same way.

Pretend $FFFFFE10 is: 00 00 00 00

<asm>move.l #6,($FFFFFE10).w</asm>

Now, $FFFFFE10 is: 00 00 00 06

Let's make things slightly more difficult. Let's say that $FFFFFFE6 is 02 03 04 05. What would this become if we did this command?

<asm>move.w #4,($FFFFFFE6).w</asm>

Well, $FFFFFFE6 will become 00 04 04 05. The 00 04 in RED is what you just did. Remembering that .w is a word (4 nibbles, 00 00), and there is 8 nibbles to this address, it only does the first four nibbles. If we did: <asm>move.b #4,($FFFFFFE6).w</asm> $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:

<asm>move.b #4,($FFFFFFE6).w ; moves the byte 04 to the address shown</asm>

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:

MOVE

MOVE

MOVEQ MOVE Quick

MOVEM MOVE Multiple

MOVEA MOVE Address

The ADD command

<asm>add.b #2,($FFFFFEB8).w</asm>

This is pretty much the same as the command MOVE, but it adds instead. Example, let's say $FFFFFEB8 is 01 01 01 01 and then we did this command above, it'll become 03 01 01 01. Do you see how it works? One more example. Assuming once again that $FFFFFEB8 is 01 01 01 01, after executing the below command <asm>add.w #4,($FFFFFEB8).w ; going to add the word 04 to $FFFFFEB8</asm> it'll become 01 05 01 01.

Here is an extra command:

ADD ADD

ADDQ ADD Quick

The SUB command

<asm>sub.b #3,($FFFFFEB8).w</asm>

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: 09 09 09 09

<asm>sub.b #3,($FFFFFEB8).w ; going to take the byte 03 from $FFFFFEB8</asm>

Now $FFFFFEB8 is: 06 09 09 09

Here is an extra command:

SUB SUBtract

SUBQ SUBtract Quick

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

<asm>move.b #3,d0 ; going to move 03 to the data register 0</asm>

d0: 00 00 00 03

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.

<asm>move.b #d0,($FFFFFEB8) ; going to move the data from d0 to $FFFFFEB8</asm>

d0 will still be 00 00 00 03, but $FFFFFEB8 will also be 03 00 00 00.

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:

<asm>cmp.w #3,($FFFFFEB8).w</asm>

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.

The CMP: <asm>move.w #3,($FFFFFEB8).w add.w #3,($FFFFFF10).w bra Morecodinghere sub.w #3,($FFFFFEB8).w

Morecodinghere: move.w #3,($FFFFFEB8).w rts</asm>

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.

<asm>Morecodinghere: move.w #3,($FFFFFEB8).w rts Sumrandomcode: add.w #3,($FFFFFEB8).w</asm>

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.

<asm>BEQlabel: cmp.w #3,($FFFFFEB8).w beq Itequaled sub.b #2,($FFFFFEB8).w

Itequaled: add.b #2,($FFFFFEB8).w rts</asm>

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.

<asm>BNElabel: cmp.w #3,($FFFFFEB8).w bne Itequaled sub.b #2,($FFFFFEB8).w

Itequaled: add.b #2,($FFFFFEB8).w rts</asm>

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.

<asm>New label: move.w #3,($FFFFFF10).w add.w #3,($FFFFFF10).w cmp.w #6,($FFFFFF10).w bra Morecodinghere sub.w #3,($FFFFFF10).w

Morecodinghere: move.w #3,($FFFFFF10).w rts</asm>

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.

BRA BRanch Anyway

BSR Branch to SubRoutine

BEQ Branch if EQual

BNE Branch if Not Equal

BGE Branch if Greater or Equal

BGT Branch if Greater Than

BLE Branch if Less or Equal

BLT 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:

<asm>lea ($FFFFFF10).w,a4</asm>

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.

<asm>dc.b 0, 1,$F8, 5, 0, 0, 0, 0,$FF,$F8</asm>

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. <asm>clr.b ($FFFFFF10).w ; Clear the byte from $FFFFFF10</asm> Now, $FFFFFF10 is 00 05 05 05. <asm>clr.w ($FFFFFF10).w ; Clear the word from $FFFFFF10</asm> Now, $FFFFFF10 is 00 00 05 05. <asm>clr.l ($FFFFFF10).w ; Clear the longword from $FFFFFF10</asm> Now, $FFFFFF10 is 00 00 00 00.

Quick Homework

$FFFFFEB8 is 00 00 00 00 and $FFFFFF10 is 01 01 01 01. What will these two address show after this process?

<asm>Code: add.w #4,($FFFFFEB8).w add.b #3,($FFFFFF10).w cmp.w #3,($FFFFFEB8).w beq Codetwo move.l #9,($FFFFFF10).w sub.w #3,($FFFFFEB8).w

Codetwo: clr.l #4,($FFFFFF10).w rts</asm> 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:

<asm>word_C054: dc.w 0,$29A0, 0, $320; 0</asm>

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:

<asm>dc.l Sprite_1637C ; 76</asm>

Name it to: <asm>dc.l Sprite_Specialring ; 76</asm>

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: <asm>; ----------------------------------------------------------------------------

Sprite
Red spring
----------------------------------------------------------------------------</asm>

Just before the first line before the word Sprite, insert all this:

<asm>; ----------------------------------------------------------------------------

Sprite
Special Ring
----------------------------------------------------------------------------

Sprite_Specialring: ;This is when the pointer we changed comes to. moveq #0,d0 move.b $24(a0),d0 move.w off_SpecialringIndex(pc,d0.w),d1 jmp off_SpecialringIndex(pc,d1.w) ;moves us to the next bit of the code (look down)

off_SpecialringIndex: dc.w loc_Specialring_Init-off_SpecialringIndex dc.w loc_Specialring_Frame-off_SpecialringIndex dc.w loc_Specialring_Collision-off_SpecialringIndex dc.w loc_Specialring_Delete-off_SpecialringIndex

loc_Specialring_Init: ;graphics and such are set up here addq.b #2,$24(a0) move.w 8(a0),$32(a0) move.l #MapUnc_12382,4(a0) move.w #$26BC,2(a0) jsr sub_16D6E move.b #4,1(a0) move.b #2,$18(a0) move.b #$47,$20(a0) move.b #8,$19(a0)

loc_Specialring_Frame: ;executed every frame * for animation/drawing move.b #0,$1A(a0) move.w $32(a0),d0 jsr loc_1640A rts

loc_Specialring_Collision: ;executed upon collision with sonic

loc_Specialring_Delete: ;delete the object rts

                                                                                                                                                        • </asm>

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.

<asm>loc_Specialring_Collision: ;executed upon collision with sonic move.w #1,($FFFFFEB9).w ;add one word to $FFFFFEB9</asm>

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:

<asm>loc_1429C: ; CODE XREF: ROM:00014292 j move.w d0,($FFFFFE10).w clr.b ($FFFFFE30).w clr.b ($FFFFFEE0).w move.w #1,($FFFFFE02).w rts</asm>

Change it to: <asm>loc_1429C: ; CODE XREF: ROM:00014292j clr.b ($FFFFFE30).w clr.b ($FFFFFEE0).w 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 clr.w ($FFFFFEB9).w rts

nextlevel: move.w #1,($FFFFFE02).w ; Set level reset clr.b ($FFFFFEB9).w</asm> 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:

<asm>loc_1428C: ; CODE XREF: ROM:00014286j move.w (a1,d0.w),d0 tst.w d0 bpl.s loc_1429C move.b #0,(Mstr_Lvl_Trigger).w rts</asm>

Change to: <asm>loc_1428C: ; CODE XREF: ROM:00014286j move.w (a1,d0.w),d0 move.w d0,($FFFFFE10).w tst.w d0 bpl.s loc_1429C move.b #0,($FFFFF600).w rts</asm>

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.