Actions

SCHG How-to

Difference between revisions of "Free up 2 universal SSTs"

From Sonic Retro

(Replaced spaces with tabs, added notices about non-Git steps, now uses LinkRetro, grammar)
m (Text replacement - "</asm>" to "</syntaxhighlight>")
Line 24: Line 24:
 
<asm>
 
<asm>
 
anim_frame_duration = $1E
 
anim_frame_duration = $1E
</asm>
+
</syntaxhighlight>
  
 
anim_frame_duration is a word, so we need to convert this to a byte. Doing so is quite easy. If you search through your ASM file (s2.asm) for "anim_frame_duration", you see half of them is set to word, and the others are a byte. So, the ones set to a byte do not need editing. The ones set to a word, obviously do. So, to start, go to "loc_A542:" and you'll see:
 
anim_frame_duration is a word, so we need to convert this to a byte. Doing so is quite easy. If you search through your ASM file (s2.asm) for "anim_frame_duration", you see half of them is set to word, and the others are a byte. So, the ones set to a byte do not need editing. The ones set to a word, obviously do. So, to start, go to "loc_A542:" and you'll see:
Line 35: Line 35:
 
move.w #$100,anim_frame_duration(a1)
 
move.w #$100,anim_frame_duration(a1)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
This is part of the object code to trigger the rescue plane and birds from the ending sequence. As you can see, it moves a word ($100) to anim_frame_duration. This needs to be turned into a byte, so change it to this:
 
This is part of the object code to trigger the rescue plane and birds from the ending sequence. As you can see, it moves a word ($100) to anim_frame_duration. This needs to be turned into a byte, so change it to this:
Line 45: Line 45:
 
move.b #$FF,anim_frame_duration(a1)
 
move.b #$FF,anim_frame_duration(a1)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
As you can see, it's now "move.b" and not "move.w". Because of this, it cannot be $100 anymore and must be a byte-length. So, I put it to $FF. It's only $1 byte off, and I checked this in my game, and you won't spot any difference, so this will work fine.
 
As you can see, it's now "move.b" and not "move.w". Because of this, it cannot be $100 anymore and must be a byte-length. So, I put it to $FF. It's only $1 byte off, and I checked this in my game, and you won't spot any difference, so this will work fine.
Line 55: Line 55:
 
addq.b #2,routine(a0)
 
addq.b #2,routine(a0)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
This is part of the "Game Over" object. When you get a Game Over, and the text appears, $2D0 gets moved to anim_frame_duration. Then every frame, it goes down a byte, and when it finally reaches 0, the game restarts and goes to SEGA (or, it will go to SEGA screen when you press A, B or C). We can't have $2D0 because it's a word. So, change it to this:
 
This is part of the "Game Over" object. When you get a Game Over, and the text appears, $2D0 gets moved to anim_frame_duration. Then every frame, it goes down a byte, and when it finally reaches 0, the game restarts and goes to SEGA (or, it will go to SEGA screen when you press A, B or C). We can't have $2D0 because it's a word. So, change it to this:
Line 63: Line 63:
 
addq.b #2,routine(a0)
 
addq.b #2,routine(a0)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
Now, it is set to $C0 (notice again, it's been changed to "move.b"). Now you're thinking "it's going to count down too quick, surely?" Well, that's what we're about to fix next. Go to "loc_13FEE:" and you'll see:
 
Now, it is set to $C0 (notice again, it's been changed to "move.b"). Now you're thinking "it's going to count down too quick, surely?" Well, that's what we're about to fix next. Go to "loc_13FEE:" and you'll see:
Line 78: Line 78:
 
subq.w #1,anim_frame_duration(a0)
 
subq.w #1,anim_frame_duration(a0)
 
bra.w DisplaySprite
 
bra.w DisplaySprite
</asm>
+
</syntaxhighlight>
  
 
See that? It's testing the anim_frame_duration. If 0, branch, otherwise, it will subtract one from anim_frame_duration. It will keep doing this until it branches. These both need to changed to .b, but that's not enough. As said, it's counting down too quickly, so change it, to this:
 
See that? It's testing the anim_frame_duration. If 0, branch, otherwise, it will subtract one from anim_frame_duration. It will keep doing this until it branches. These both need to changed to .b, but that's not enough. As said, it's counting down too quickly, so change it, to this:
Line 97: Line 97:
 
subq.b #1,anim_frame_duration(a0)
 
subq.b #1,anim_frame_duration(a0)
 
bra.w DisplaySprite
 
bra.w DisplaySprite
</asm>
+
</syntaxhighlight>
  
 
Both anim_frame_duration have been changed from .w to .b again, but there's a bit of extra code here: Before subtracting 1 from anim_frame_duration, it will move the Game Frame Timer to d0 (Game Frame Timer goes up a byte every single frame). The next command will "and" d0 by 3 (so, every frame, d0 will go 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, etc). Then the next command, saying it will branch to DisplaySprite if d0 does NOT equal 0. If it DOES equal 0, it will then subtract 1 from anim_frame_duration and then DisplaySprite. Because of this, it will not subtract 1 from anim_frame_duration so quickly, and therefore, it will take a bit longer for it to reach 0. Instead of subtracting 1 from anim_frame_duration every frame, it subtracts 1 from anim_frame_duration every 4 frames. This works perfectly.
 
Both anim_frame_duration have been changed from .w to .b again, but there's a bit of extra code here: Before subtracting 1 from anim_frame_duration, it will move the Game Frame Timer to d0 (Game Frame Timer goes up a byte every single frame). The next command will "and" d0 by 3 (so, every frame, d0 will go 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, etc). Then the next command, saying it will branch to DisplaySprite if d0 does NOT equal 0. If it DOES equal 0, it will then subtract 1 from anim_frame_duration and then DisplaySprite. Because of this, it will not subtract 1 from anim_frame_duration so quickly, and therefore, it will take a bit longer for it to reach 0. Instead of subtracting 1 from anim_frame_duration every frame, it subtracts 1 from anim_frame_duration every 4 frames. This works perfectly.
Line 113: Line 113:
 
move.b #$A,routine(a0)
 
move.b #$A,routine(a0)
 
move.w #$B4,anim_frame_duration(a0)
 
move.w #$B4,anim_frame_duration(a0)
</asm>
+
</syntaxhighlight>
  
 
And change it to this:
 
And change it to this:
Line 126: Line 126:
 
move.b #$A,routine(a0)
 
move.b #$A,routine(a0)
 
move.w #$B4,objoff_34(a0)
 
move.w #$B4,objoff_34(a0)
</asm>
+
</syntaxhighlight>
  
 
'''Please note:''' that it's still "move.w" and this is because, further down, it will be doing "subq.w" an they cannot be changed to .b, as they do it for multiple places which uses words. That's another reason why I'm using objoff_34: It's just a lot easier and simpler than doing a lot of calculations, and that might slow down the results screen, which we do not want. For this object, it will always be .w and not .b, so just make sure of that. Anyway...
 
'''Please note:''' that it's still "move.w" and this is because, further down, it will be doing "subq.w" an they cannot be changed to .b, as they do it for multiple places which uses words. That's another reason why I'm using objoff_34: It's just a lot easier and simpler than doing a lot of calculations, and that might slow down the results screen, which we do not want. For this object, it will always be .w and not .b, so just make sure of that. Anyway...
Line 136: Line 136:
 
bne.s BranchTo18_DisplaySprite
 
bne.s BranchTo18_DisplaySprite
 
addq.b #2,routine(a0)
 
addq.b #2,routine(a0)
</asm>
+
</syntaxhighlight>
  
  
Line 145: Line 145:
 
bne.s BranchTo18_DisplaySprite
 
bne.s BranchTo18_DisplaySprite
 
addq.b #2,routine(a0)
 
addq.b #2,routine(a0)
</asm>
+
</syntaxhighlight>
  
 
Next, go to:
 
Next, go to:
Line 161: Line 161:
 
move.w #$12C,anim_frame_duration(a0)
 
move.w #$12C,anim_frame_duration(a0)
 
lea next_object(a0),a1 ; a1=object
 
lea next_object(a0),a1 ; a1=object
</asm>
+
</syntaxhighlight>
  
 
And change to:
 
And change to:
Line 177: Line 177:
 
move.w #$12C,objoff_34(a0) ; The time the game waits until the total score has totaled and do the 'continue' jingle and to move on to next level
 
move.w #$12C,objoff_34(a0) ; The time the game waits until the total score has totaled and do the 'continue' jingle and to move on to next level
 
lea next_object(a0),a1 ; a1=object
 
lea next_object(a0),a1 ; a1=object
</asm>
+
</syntaxhighlight>
  
 
Next, go to:
 
Next, go to:
Line 191: Line 191:
 
move.w #$3C,anim_frame_duration(a1)
 
move.w #$3C,anim_frame_duration(a1)
 
addq.b #1,(Continue_count).w
 
addq.b #1,(Continue_count).w
</asm>
+
</syntaxhighlight>
  
 
And change to:
 
And change to:
Line 205: Line 205:
 
move.w #$3C,objoff_34(a1)
 
move.w #$3C,objoff_34(a1)
 
addq.b #1,(Continue_count).w
 
addq.b #1,(Continue_count).w
</asm>
+
</syntaxhighlight>
  
 
Next, go to:
 
Next, go to:
Line 214: Line 214:
 
subq.w #1,anim_frame_duration(a0)
 
subq.w #1,anim_frame_duration(a0)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
And change to:
 
And change to:
Line 223: Line 223:
 
subq.w #1,objoff_34(a0)
 
subq.w #1,objoff_34(a0)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
Next, go to:
 
Next, go to:
Line 233: Line 233:
 
addq.b #1,anim_frame(a0)
 
addq.b #1,anim_frame(a0)
 
andi.b #1,anim_frame(a0)
 
andi.b #1,anim_frame(a0)
</asm>
+
</syntaxhighlight>
  
 
And change to:
 
And change to:
Line 243: Line 243:
 
addq.b #1,anim_frame(a0)
 
addq.b #1,anim_frame(a0)
 
andi.b #1,anim_frame(a0)
 
andi.b #1,anim_frame(a0)
</asm>
+
</syntaxhighlight>
  
 
Done. All these changes are just for the "End of level" results screen. Using objoff_34 instead of anim_frame_duration is perfectly fine and causes no troubles whatsoever. Next, everything will be going back to .b and not .w soo...
 
Done. All these changes are just for the "End of level" results screen. Using objoff_34 instead of anim_frame_duration is perfectly fine and causes no troubles whatsoever. Next, everything will be going back to .b and not .w soo...
Line 264: Line 264:
 
bne.s loc_3AB60
 
bne.s loc_3AB60
 
move.b #$F,y_radius(a1)
 
move.b #$F,y_radius(a1)
</asm>
+
</syntaxhighlight>
  
 
Basically, this has the same problem as the first object (triggering the rescue plane and birds from ending sequence): It's moving $100 to anim_frame_duration again, so change it to this:
 
Basically, this has the same problem as the first object (triggering the rescue plane and birds from ending sequence): It's moving $100 to anim_frame_duration again, so change it to this:
Line 283: Line 283:
 
bne.s loc_3AB60
 
bne.s loc_3AB60
 
move.b #$F,y_radius(a1)
 
move.b #$F,y_radius(a1)
</asm>
+
</syntaxhighlight>
  
 
It's now "move.b" instead, and moving $FF instead of $100. $1 byte isn't going to make a difference (I couldn't see a difference).
 
It's now "move.b" instead, and moving $FF instead of $100. $1 byte isn't going to make a difference (I couldn't see a difference).
Line 293: Line 293:
 
move.w #$100,anim_frame_duration(a1)
 
move.w #$100,anim_frame_duration(a1)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
And change to:
 
And change to:
Line 302: Line 302:
 
move.b #$FF,anim_frame_duration(a1)
 
move.b #$FF,anim_frame_duration(a1)
 
rts
 
rts
</asm>
+
</syntaxhighlight>
  
 
Same problem as before really... and that's the hardest parts done, and be honest, that wasn't hard, was it? =P
 
Same problem as before really... and that's the hardest parts done, and be honest, that wasn't hard, was it? =P
Line 317: Line 317:
 
<asm>
 
<asm>
 
move.w #$2D,anim_frame_duration(a1)
 
move.w #$2D,anim_frame_duration(a1)
</asm>
+
</syntaxhighlight>
  
 
It's extremely simple, you change it to:
 
It's extremely simple, you change it to:
 
<asm>
 
<asm>
 
move.b #$2D,anim_frame_duration(a1)
 
move.b #$2D,anim_frame_duration(a1)
</asm>
+
</syntaxhighlight>
  
 
Simple. Do this for all your search results.
 
Simple. Do this for all your search results.
Line 329: Line 329:
 
<asm>
 
<asm>
 
move.w #$2D,next_object+anim_frame_duration(a1)
 
move.w #$2D,next_object+anim_frame_duration(a1)
</asm>
+
</syntaxhighlight>
  
 
and change to:
 
and change to:
 
<asm>
 
<asm>
 
move.b #$2D,next_object+anim_frame_duration(a1)
 
move.b #$2D,next_object+anim_frame_duration(a1)
</asm>
+
</syntaxhighlight>
  
 
How hard is that? Do this with all your results.
 
How hard is that? Do this with all your results.
Line 353: Line 353:
 
<asm>
 
<asm>
 
move.b #$488,anim_frame_duration(a0)
 
move.b #$488,anim_frame_duration(a0)
</asm>
+
</syntaxhighlight>
  
 
back to this:
 
back to this:
 
<asm>
 
<asm>
 
move.w #$488,anim_frame_duration(a0)
 
move.w #$488,anim_frame_duration(a0)
</asm>
+
</syntaxhighlight>
  
 
Do this for all the ones in the list. You MUST change them back to .w at the locations in the above list ONLY.
 
Do this for all the ones in the list. You MUST change them back to .w at the locations in the above list ONLY.
Line 368: Line 368:
 
<asm>
 
<asm>
 
anim_frame_duration = $1E
 
anim_frame_duration = $1E
</asm>
+
</syntaxhighlight>
  
 
to this
 
to this
 
<asm>
 
<asm>
 
anim_frame_duration = $23
 
anim_frame_duration = $23
</asm>
+
</syntaxhighlight>
  
 
Then, look for respawn_index and change:
 
Then, look for respawn_index and change:
 
<asm>
 
<asm>
 
respawn_index = $23
 
respawn_index = $23
</asm>
+
</syntaxhighlight>
  
 
to this:
 
to this:
 
<asm>
 
<asm>
 
respawn_index = $1E
 
respawn_index = $1E
</asm>
+
</syntaxhighlight>
  
 
Now, anim_frame_duration is using $23 (it won't use $24 because we changed it to a byte), and respawn_index is now $1E, and because we have made $1F free, respawn_index can be used as a word and can now easily use $1E and $1F! BUT, because of this, ARZ, CNZ and MCZ bosses have become affected again. This is because they're moving a word to anim_frame_duration, which is $23. Because it's moving a word, it overwrites $24, which is "routine", causing the game to freeze. Easily fixed. Here's how. You need to go to these labels in the list below and change change anim_frame_duration to respawn_index. '''Again, Git users should not have to do this.'''
 
Now, anim_frame_duration is using $23 (it won't use $24 because we changed it to a byte), and respawn_index is now $1E, and because we have made $1F free, respawn_index can be used as a word and can now easily use $1E and $1F! BUT, because of this, ARZ, CNZ and MCZ bosses have become affected again. This is because they're moving a word to anim_frame_duration, which is $23. Because it's moving a word, it overwrites $24, which is "routine", causing the game to freeze. Easily fixed. Here's how. You need to go to these labels in the list below and change change anim_frame_duration to respawn_index. '''Again, Git users should not have to do this.'''
Line 397: Line 397:
 
<asm>
 
<asm>
 
move.w #$488,anim_frame_duration(a0)
 
move.w #$488,anim_frame_duration(a0)
</asm>
+
</syntaxhighlight>
  
 
back to this:
 
back to this:
 
<asm>
 
<asm>
 
move.w #$488,respawn_index(a0)
 
move.w #$488,respawn_index(a0)
</asm>
+
</syntaxhighlight>
  
 
Do this for all the ones in the list above. The bosses can use respawn_index freely as it is universal and it is a word. Basically, it's doing the same as it used to do with anim_frame_duration (using $1E and $1F like it used to in the first place).
 
Do this for all the ones in the list above. The bosses can use respawn_index freely as it is universal and it is a word. Basically, it's doing the same as it used to do with anim_frame_duration (using $1E and $1F like it used to in the first place).
Line 412: Line 412:
 
<asm>
 
<asm>
 
inertia = $14 ; and $15 ; directionless representation of speed... not updated in the air
 
inertia = $14 ; and $15 ; directionless representation of speed... not updated in the air
</asm>
+
</syntaxhighlight>
  
 
Change it to
 
Change it to
 
<asm>
 
<asm>
 
inertia = $20 ; and $21 ; directionless representation of speed... not updated in the air
 
inertia = $20 ; and $21 ; directionless representation of speed... not updated in the air
</asm>
+
</syntaxhighlight>
  
 
For Sonic and Tails, $20 and $21 is free, so move it there. Why? Simple. $14 is a "convention followed by most objects". I think $15 is still only used by Sonic and Tails, but that doesn't matter. Now, instead of the original $1F, $20 and $21 being free for Sonic and Tails, it's now $15 and $1F that is free for them ($14 is NOT free for Sonic and Tails! About to explain why!)
 
For Sonic and Tails, $20 and $21 is free, so move it there. Why? Simple. $14 is a "convention followed by most objects". I think $15 is still only used by Sonic and Tails, but that doesn't matter. Now, instead of the original $1F, $20 and $21 being free for Sonic and Tails, it's now $15 and $1F that is free for them ($14 is NOT free for Sonic and Tails! About to explain why!)
Line 424: Line 424:
 
<asm>
 
<asm>
 
width_pixels = $19
 
width_pixels = $19
</asm>
+
</syntaxhighlight>
  
 
and change to
 
and change to
 
<asm>
 
<asm>
 
width_pixels = $14
 
width_pixels = $14
</asm>
+
</syntaxhighlight>
  
 
width_pixels can be moved to $14, which means $19 is now free, and it's universal! How about that? Told you it was piss easy! I am using $19 as part of priority (in S3K's priority manager, the priority is a word, so I use $18 and $19). And that's the reason why $14 isn't free for Sonic and Tails, because it's being used for width_pixel. Yes, you're going to lose a SST from them but it's better to gain a universal SST, right?
 
width_pixels can be moved to $14, which means $19 is now free, and it's universal! How about that? Told you it was piss easy! I am using $19 as part of priority (in S3K's priority manager, the priority is a word, so I use $18 and $19). And that's the reason why $14 isn't free for Sonic and Tails, because it's being used for width_pixel. Yes, you're going to lose a SST from them but it's better to gain a universal SST, right?
Line 465: Line 465:
 
move.w #2,(a2)+
 
move.w #2,(a2)+
 
bra.w loc_2AE56
 
bra.w loc_2AE56
</asm>
+
</syntaxhighlight>
  
 
Cut out the line "move.b #$18,width_pixels(a0)" so it's not there anymore. We're going to move it.
 
Cut out the line "move.b #$18,width_pixels(a0)" so it's not there anymore. We're going to move it.
Line 480: Line 480:
 
beq.s loc_2ABA0
 
beq.s loc_2ABA0
 
bra.w JmpTo4_DisplaySprite3
 
bra.w JmpTo4_DisplaySprite3
</asm>
+
</syntaxhighlight>
  
 
Just before the "move.w #$200,d0", paste the width_pixel line there. So, you end up with this:
 
Just before the "move.w #$200,d0", paste the width_pixel line there. So, you end up with this:
Line 494: Line 494:
 
beq.s loc_2ABA0
 
beq.s loc_2ABA0
 
bra.w JmpTo4_DisplaySprite3
 
bra.w JmpTo4_DisplaySprite3
</asm>
+
</syntaxhighlight>
  
 
There. That object is now fixed. I've gone through the whole of S2 with Sonic and Tails, and this was the only thing I spotted that went wrong. Everything else seems to be intact. You should now be all set, and able to port over the S3K Object and Priority Managers!
 
There. That object is now fixed. I've gone through the whole of S2 with Sonic and Tails, and this was the only thing I spotted that went wrong. Everything else seems to be intact. You should now be all set, and able to port over the S3K Object and Priority Managers!

Revision as of 21:30, 20 December 2015

(Guide written by redhotsonic)

WARNING: Please back-up your whole disassembly before following this guide. I will NOT be held responsible if anything goes wrong when following this guide.

PLEASE NOTE: This guide assumes that all your SST's are equated. If you have made your own objects or ported objects and still have stuff like "move.w #$488,$1E(a0)" instead of "move.w #$488,anim_frame_duration(a0)", you must rename all the SST's so that they use your equates, so they can easily be moved. Otherwise, things will start to go wrong. This is your only warning about this.

There are a few guides out there to free up SST's, but these only seem to be for Sonic and Tails only. This is great if you want to use a SST for a double-jump flag, or a homing-attack flag, whatever. Then you think that you want to do something bold. Like, you want to follow
Sonic Retro
Module's guide on how to port S3K's object manager to S2
or you may want to follow
Sonic Retro
my guide on how to port S3K's priority manager into S2
. I've now got the object manager ported into my hack, but the biggest trouble with the guide, is that you need to free up a universal SST. Module tries to explain that you can expand the SST table, but this is very complicated and annoying, and you have to edit all the other objects, which is tiring. To make his guide a lot easier to follow, freeing a universal SST would be a lot easier and a big time-saver. But how?

Freeing up one of Sonic and/or Tails SST is no good. Because as explained, only Sonic and Tails uses this. All other objects do not. For example, if you had a free SST in Sonic and Tails, say, $34, then move the Priority from $18 to $34, your hack will freeze pretty much immediately when you next start it. That's because the SEGA logo cannot read from Priority anymore, neither can the title screen, neither can any badniks, blah blah blah.

Well, I'm not going to show you how to free up 1, but how to free up 2 of them! Part 1 is not too complicated, Part 2 is very very easy. BUT, if you plan to port the S3K object manager using Module's guide, I highly advice you to do Part 1 first!

If you're only interested in my "How to port S3K Priority Manager into S2" guide, then you can skip step 1 if you want to.


This guide may look really big and you're probably thinking "Oh gawd, this looks tough", but it's actually easy if you read the guide carefully. And I've tried to make it as easy as I can for you and explained as much as I can too!

PLEASE NOTE: This guide is designed for Xenowhirl's 2007 disassembly. You can use this guide if you have the Git disassembly, but you'll have to work around the differences. You definitely cannot do it if you are using Hivebrain's disassembly. This is also for Sonic 2 only!

Part 1: Free up one "universal" SST

Okay, first of all, I must admit, this technically is not universal, but it is "conventions followed by most objects". This is almost as good as universal, and is actually perfect for Module's guide on porting the S3K object manager in. Do not worry, I promise part 2 will free up a universal SST. So, let's get started.

Step 1: Changing the hardest parts of "anim_frame_duration"

<asm> anim_frame_duration = $1E </syntaxhighlight>

anim_frame_duration is a word, so we need to convert this to a byte. Doing so is quite easy. If you search through your ASM file (s2.asm) for "anim_frame_duration", you see half of them is set to word, and the others are a byte. So, the ones set to a byte do not need editing. The ones set to a word, obviously do. So, to start, go to "loc_A542:" and you'll see:

<asm> loc_A542: move.w d0,y_pos(a1) move.w x_pos(a0),x_pos(a1) move.l #$1000505,mapping_frame(a1) move.w #$100,anim_frame_duration(a1) rts </syntaxhighlight>

This is part of the object code to trigger the rescue plane and birds from the ending sequence. As you can see, it moves a word ($100) to anim_frame_duration. This needs to be turned into a byte, so change it to this: <asm> loc_A542: move.w d0,y_pos(a1) move.w x_pos(a0),x_pos(a1) move.l #$1000505,mapping_frame(a1) move.b #$FF,anim_frame_duration(a1) rts </syntaxhighlight>

As you can see, it's now "move.b" and not "move.w". Because of this, it cannot be $100 anymore and must be a byte-length. So, I put it to $FF. It's only $1 byte off, and I checked this in my game, and you won't spot any difference, so this will work fine.

Next, go to "loc_13FE2:" and you'll see this: <asm> loc_13FE2: move.w #$2D0,anim_frame_duration(a0) addq.b #2,routine(a0) rts </syntaxhighlight>

This is part of the "Game Over" object. When you get a Game Over, and the text appears, $2D0 gets moved to anim_frame_duration. Then every frame, it goes down a byte, and when it finally reaches 0, the game restarts and goes to SEGA (or, it will go to SEGA screen when you press A, B or C). We can't have $2D0 because it's a word. So, change it to this: <asm> loc_13FE2: move.b #$C0,anim_frame_duration(a0) addq.b #2,routine(a0) rts </syntaxhighlight>

Now, it is set to $C0 (notice again, it's been changed to "move.b"). Now you're thinking "it's going to count down too quick, surely?" Well, that's what we're about to fix next. Go to "loc_13FEE:" and you'll see:

<asm> btst #0,mapping_frame(a0) bne.w BranchTo17_DisplaySprite move.b (Ctrl_1_Press).w,d0 or.b (Ctrl_2_Press).w,d0 andi.b #$70,d0 bne.s loc_14014 tst.w anim_frame_duration(a0) beq.s loc_14014 subq.w #1,anim_frame_duration(a0) bra.w DisplaySprite </syntaxhighlight>

See that? It's testing the anim_frame_duration. If 0, branch, otherwise, it will subtract one from anim_frame_duration. It will keep doing this until it branches. These both need to changed to .b, but that's not enough. As said, it's counting down too quickly, so change it, to this:

<asm> loc_13FEE: btst #0,mapping_frame(a0) bne.w BranchTo17_DisplaySprite move.b (Ctrl_1_Press).w,d0 or.b (Ctrl_2_Press).w,d0 andi.b #$70,d0 bne.s loc_14014 tst.b anim_frame_duration(a0) beq.s loc_14014 move.b ($FFFFFE05).w,d0 ; Move Game Frame timer to d0 andi.b #3,d0 ; andi d0 by 3 bne.w DisplaySprite ; if d0 does NOT equal 0, skip subtracting 1 byte from anim_frame_duration subq.b #1,anim_frame_duration(a0) bra.w DisplaySprite </syntaxhighlight>

Both anim_frame_duration have been changed from .w to .b again, but there's a bit of extra code here: Before subtracting 1 from anim_frame_duration, it will move the Game Frame Timer to d0 (Game Frame Timer goes up a byte every single frame). The next command will "and" d0 by 3 (so, every frame, d0 will go 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, etc). Then the next command, saying it will branch to DisplaySprite if d0 does NOT equal 0. If it DOES equal 0, it will then subtract 1 from anim_frame_duration and then DisplaySprite. Because of this, it will not subtract 1 from anim_frame_duration so quickly, and therefore, it will take a bit longer for it to reach 0. Instead of subtracting 1 from anim_frame_duration every frame, it subtracts 1 from anim_frame_duration every 4 frames. This works perfectly.

Now, next, is to do with the "End of level" results screen object. There's quite a lot to do with this object: Instead of doing a lot of coding here similar to "Game Over", I changed anim_frame_duration to objoff_34. objoff_34 is not used in the "End of level" results screen and therefore, is free to use for this object. Because objoff_34 will be set as a word, it will be using objoff_35 too, which again, is free for this object. The ending of each level still works perfectly for me after doing this. So, go to "loc_14118:" and you'll see:

<asm> loc_14118:

move.b d0,mapping_frame(a0) bsr.w sub_13E1C move.w x_pixel(a0),d0 cmp.w objoff_30(a0),d0 bne.w return_14138 move.b #$A,routine(a0) move.w #$B4,anim_frame_duration(a0) </syntaxhighlight>

And change it to this: <asm> loc_14118:

move.b d0,mapping_frame(a0) bsr.w sub_13E1C move.w x_pixel(a0),d0 cmp.w objoff_30(a0),d0 bne.w return_14138 move.b #$A,routine(a0) move.w #$B4,objoff_34(a0) </syntaxhighlight>

Please note: that it's still "move.w" and this is because, further down, it will be doing "subq.w" an they cannot be changed to .b, as they do it for multiple places which uses words. That's another reason why I'm using objoff_34: It's just a lot easier and simpler than doing a lot of calculations, and that might slow down the results screen, which we do not want. For this object, it will always be .w and not .b, so just make sure of that. Anyway...

Next, go to: <asm> loc_1419C: subq.w #1,anim_frame_duration(a0) bne.s BranchTo18_DisplaySprite addq.b #2,routine(a0) </syntaxhighlight>


And change it to: <asm> loc_1419C: subq.w #1,objoff_34(a0) bne.s BranchTo18_DisplaySprite addq.b #2,routine(a0) </syntaxhighlight>

Next, go to: <asm> loc_141E6: add.w d0,($FFFFFF8E).w tst.w d0 bne.s loc_14256 move.w #$C5,d0 jsr (PlaySound).l addq.b #2,routine(a0) move.w #$B4,anim_frame_duration(a0) cmpi.w #1000,($FFFFFF8E).w bcs.s return_14254 move.w #$12C,anim_frame_duration(a0) lea next_object(a0),a1 ; a1=object </syntaxhighlight>

And change to: <asm> loc_141E6: add.w d0,($FFFFFF8E).w tst.w d0 bne.s loc_14256 move.w #$C5,d0 jsr (PlaySound).l addq.b #2,routine(a0) move.w #$B4,objoff_34(a0) ; The time the game waits until the total score has totaled and to move on to next level cmpi.w #1000,($FFFFFF8E).w bcs.s return_14254 move.w #$12C,objoff_34(a0) ; The time the game waits until the total score has totaled and do the 'continue' jingle and to move on to next level lea next_object(a0),a1 ; a1=object </syntaxhighlight>

Next, go to: <asm> loc_14220: _move.b #$3A,0(a1) ; load obj3A (uses screen-space) move.b #$12,routine(a1) move.w #$188,x_pixel(a1) move.w #$118,y_pixel(a1) move.l #Obj3A_MapUnc_14CBC,mappings(a1) bsr.w Adjust2PArtPointer2 move.b #0,render_flags(a1) move.w #$3C,anim_frame_duration(a1) addq.b #1,(Continue_count).w </syntaxhighlight>

And change to: <asm> loc_14220: _move.b #$3A,0(a1) ; load obj3A (uses screen-space) move.b #$12,routine(a1) move.w #$188,x_pixel(a1) move.w #$118,y_pixel(a1) move.l #Obj3A_MapUnc_14CBC,mappings(a1) bsr.w Adjust2PArtPointer2 move.b #0,render_flags(a1) move.w #$3C,objoff_34(a1) addq.b #1,(Continue_count).w </syntaxhighlight>

Next, go to: <asm> loc_142B0: tst.w anim_frame_duration(a0) beq.s loc_142BC subq.w #1,anim_frame_duration(a0) rts </syntaxhighlight>

And change to: <asm> loc_142B0: tst.w objoff_34(a0) beq.s loc_142BC subq.w #1,objoff_34(a0) rts </syntaxhighlight>

Next, go to: <asm> loc_142CC: subq.w #1,anim_frame_duration(a0) bpl.s loc_142E2 move.w #$13,anim_frame_duration(a0) addq.b #1,anim_frame(a0) andi.b #1,anim_frame(a0) </syntaxhighlight>

And change to: <asm> loc_142CC: subq.w #1,objoff_34(a0) bpl.s loc_142E2 move.w #$13,objoff_34(a0) addq.b #1,anim_frame(a0) andi.b #1,anim_frame(a0) </syntaxhighlight>

Done. All these changes are just for the "End of level" results screen. Using objoff_34 instead of anim_frame_duration is perfectly fine and causes no troubles whatsoever. Next, everything will be going back to .b and not .w soo...

Now, there's one more object to deal with: The Tornado (Tails' Plane). Go to "loc_3AB18:" and you'll see: <asm> loc_3AB18: clr.w (Ctrl_1_Logical).w lea (MainCharacter).w,a1 ; a1=character move.w x_pos(a0),x_pos(a1) clr.w x_vel(a1) clr.w y_vel(a1) clr.w inertia(a1) bclr #1,status(a1) bclr #2,status(a1) move.l #$1000505,mapping_frame(a1) move.w #$100,anim_frame_duration(a1) move.b #$13,y_radius(a1) cmpi.w #2,(Player_mode).w bne.s loc_3AB60 move.b #$F,y_radius(a1) </syntaxhighlight>

Basically, this has the same problem as the first object (triggering the rescue plane and birds from ending sequence): It's moving $100 to anim_frame_duration again, so change it to this: <asm> loc_3AB18: clr.w (Ctrl_1_Logical).w lea (MainCharacter).w,a1 ; a1=character move.w x_pos(a0),x_pos(a1) clr.w x_vel(a1) clr.w y_vel(a1) clr.w inertia(a1) bclr #1,status(a1) bclr #2,status(a1) move.l #$1000505,mapping_frame(a1) move.b #$FF,anim_frame_duration(a1) move.b #$13,y_radius(a1) cmpi.w #2,(Player_mode).w bne.s loc_3AB60 move.b #$F,y_radius(a1) </syntaxhighlight>

It's now "move.b" instead, and moving $FF instead of $100. $1 byte isn't going to make a difference (I couldn't see a difference).

Next, go to "loc_3AC56:" (still part of the Tornado object) and you'll see: <asm> lea (MainCharacter).w,a1 ; a1=character move.l #$1000505,mapping_frame(a1) move.w #$100,anim_frame_duration(a1) rts </syntaxhighlight>

And change to: <asm> loc_3AC56: lea (MainCharacter).w,a1 ; a1=character move.l #$1000505,mapping_frame(a1) move.b #$FF,anim_frame_duration(a1) rts </syntaxhighlight>

Same problem as before really... and that's the hardest parts done, and be honest, that wasn't hard, was it? =P

Step 2: Changing .w to .b in all "anim_frame_duration"

This bit is easy, but time consuming. It shouldn't take you any longer than 20 minutes. So, go to the top of your ASM file, or go to "StartOfRom:"

On your notepad (or whatever you're using), use the search function (Edit > Search). In the search box, search for "anim_frame_duration" (without the "quotation marks" of course).

For every result you find, if it is has a .w to it, change it to .b. (Please note: If it is already .b then you can leave it alone and does not need changing)

Example, your first result you come to, should be (at "loc_42E8:):

<asm> move.w #$2D,anim_frame_duration(a1) </syntaxhighlight>

It's extremely simple, you change it to: <asm> move.b #$2D,anim_frame_duration(a1) </syntaxhighlight>

Simple. Do this for all your search results.

One more incase you're not getting it. The next result (which is actually 2 lines down) is: <asm> move.w #$2D,next_object+anim_frame_duration(a1) </syntaxhighlight>

and change to: <asm> move.b #$2D,next_object+anim_frame_duration(a1) </syntaxhighlight>

How hard is that? Do this with all your results.

Step 3: Changing .b to .w back in the ARZ, CNZ and MCZ boss's "anim_frame_duration"

Users of the Git disassembly should not have to do this step. Because of step 2, you've probably change .w to .b's anim_frame_duration in ARZ, CNZ and MCZ's bosses. This is a big problem: The bosses HAVE to use these as a word, otherwise the bosses will not work correctly. You cannot change it to another SST, because these have to use universal SST's, as a word, and unfortunately, the bosses are already using all the universal SST's available. So, go to these locations:

  • loc_304D4:
  • loc_30824:
  • loc_30FB8:
  • loc_3130A:
  • loc_31904:
  • loc_31E76:

At all these locations, where you see a command for anim_frame_duration, change the .b back to .w

Example, at "loc_304D4:", change: <asm> move.b #$488,anim_frame_duration(a0) </syntaxhighlight>

back to this: <asm> move.w #$488,anim_frame_duration(a0) </syntaxhighlight>

Do this for all the ones in the list. You MUST change them back to .w at the locations in the above list ONLY.

Step 4: Change "anim_frame_duration" and "respawn_index" around. OPTIONAL, BUT RECOMMENDED

Okay, you've freed an SST that can be used by pretty much any object! Good work, you should be proud. $1E is still anim_frame_duration, but $1F is now free for you to use!

BUT, if you're planning to follow Module's guide on porting the S3K object manager to S2, follow this step. In his guide, he says you need to make the SST respawn_index into a word, instead of a byte. So, we can easily re-arrange this. Search for "anim_frame_duration = $1E" (without the "quotation marks") and change: <asm> anim_frame_duration = $1E </syntaxhighlight>

to this <asm> anim_frame_duration = $23 </syntaxhighlight>

Then, look for respawn_index and change: <asm> respawn_index = $23 </syntaxhighlight>

to this: <asm> respawn_index = $1E </syntaxhighlight>

Now, anim_frame_duration is using $23 (it won't use $24 because we changed it to a byte), and respawn_index is now $1E, and because we have made $1F free, respawn_index can be used as a word and can now easily use $1E and $1F! BUT, because of this, ARZ, CNZ and MCZ bosses have become affected again. This is because they're moving a word to anim_frame_duration, which is $23. Because it's moving a word, it overwrites $24, which is "routine", causing the game to freeze. Easily fixed. Here's how. You need to go to these labels in the list below and change change anim_frame_duration to respawn_index. Again, Git users should not have to do this.

  • loc_304D4:
  • loc_30824:
  • loc_30FB8:
  • loc_3130A:
  • loc_31904:
  • loc_31E76:

Example, at "loc_304D4:", change: <asm> move.w #$488,anim_frame_duration(a0) </syntaxhighlight>

back to this: <asm> move.w #$488,respawn_index(a0) </syntaxhighlight>

Do this for all the ones in the list above. The bosses can use respawn_index freely as it is universal and it is a word. Basically, it's doing the same as it used to do with anim_frame_duration (using $1E and $1F like it used to in the first place).

All done for Part 1. That was easy, hey? Now, for part 2. Now this one is universal, and it a hell of a lot quicker. I freed up this one for my priority manager, but you can use it for whatever you want.

Part 2: Free up the second universal SST

It's so easy. First, go to Sonic's SST's and you'll see <asm> inertia = $14 ; and $15 ; directionless representation of speed... not updated in the air </syntaxhighlight>

Change it to <asm> inertia = $20 ; and $21 ; directionless representation of speed... not updated in the air </syntaxhighlight>

For Sonic and Tails, $20 and $21 is free, so move it there. Why? Simple. $14 is a "convention followed by most objects". I think $15 is still only used by Sonic and Tails, but that doesn't matter. Now, instead of the original $1F, $20 and $21 being free for Sonic and Tails, it's now $15 and $1F that is free for them ($14 is NOT free for Sonic and Tails! About to explain why!)

Next, go to: <asm> width_pixels = $19 </syntaxhighlight>

and change to <asm> width_pixels = $14 </syntaxhighlight>

width_pixels can be moved to $14, which means $19 is now free, and it's universal! How about that? Told you it was piss easy! I am using $19 as part of priority (in S3K's priority manager, the priority is a word, so I use $18 and $19). And that's the reason why $14 isn't free for Sonic and Tails, because it's being used for width_pixel. Yes, you're going to lose a SST from them but it's better to gain a universal SST, right?

So now, you have $19 free universally. If you did NOT follow step 4 in part 1, then you have $1F free which can be used by almost every object. If you have followed step 4 in part 1, then $1F is still free until you port S3K's object manager in using Module's guide. Also, for Sonic and Tails, you have $15, $1F and $23 free.

WARNING: If you change the equates to use something else, it can cause problems (mainly ARZ, CNZ and MCZ bosses). For example, if you have these bosses set to respawn_index and then you swap respawn_index with routine, respawn_index will use $24 fine, but as the bosses are using it as a word, it will over-write $25, which is routine_secondary. Then, the game will freeze when you come to one of these bosses. Just a word of warning. This is not the case with the Git disassembly

Part 3: Fixing a bug

Applying this guide to a clean Sonic 2 disassembly, the ONLY bug I could find involved Casino Night Zone's pull spring object.

The width_pixels have gone wrong on this. The problem is that part of its height is involved and overwrites the new width_pixels SST. You cannot change the height's location of this object, otherwise the object will never work. But I realized in this object, that its priority is used for something different as well. Instead, its priority is moved to d0 then jumps to DisplaySprite3. So, I made width_pixels do the same thing, and now the object is fine. Anyway, the fix:

Go to "loc_2ABFA:" and you'll see this: <asm> loc_2ABFA: bsr.w JmpTo49_Adjust2PArtPointer move.b #4,render_flags(a0) bset #6,render_flags(a0) move.b #1,objoff_B(a0) tst.b subtype(a0) beq.s loc_2AC54 addq.b #2,routine(a0) move.b #$20,objoff_E(a0) move.b #$18,width_pixels(a0) move.w x_pos(a0),objoff_2E(a0) move.w y_pos(a0),objoff_34(a0) move.w x_pos(a0),d2 move.w y_pos(a0),d3 addi.w #0,d3 move.b #1,objoff_F(a0) lea $10(a0),a2 move.w d2,(a2)+ move.w d3,(a2)+ move.w #2,(a2)+ bra.w loc_2AE56 </syntaxhighlight>

Cut out the line "move.b #$18,width_pixels(a0)" so it's not there anymore. We're going to move it.

Next, go to "Obj85:" and you'll see this: <asm> Obj85: moveq #0,d0 move.b routine(a0),d0 move.w off_2ABCE(pc,d0.w),d1 jsr off_2ABCE(pc,d1.w) move.w #$200,d0 tst.w (Two_player_mode).w beq.s loc_2ABA0 bra.w JmpTo4_DisplaySprite3 </syntaxhighlight>

Just before the "move.w #$200,d0", paste the width_pixel line there. So, you end up with this: <asm> Obj85: moveq #0,d0 move.b routine(a0),d0 move.w off_2ABCE(pc,d0.w),d1 jsr off_2ABCE(pc,d1.w) move.b #$18,width_pixels(a0) ; Now moved here instead of being at loc_2ABFA move.w #$200,d0 tst.w (Two_player_mode).w beq.s loc_2ABA0 bra.w JmpTo4_DisplaySprite3 </syntaxhighlight>

There. That object is now fixed. I've gone through the whole of S2 with Sonic and Tails, and this was the only thing I spotted that went wrong. Everything else seems to be intact. You should now be all set, and able to port over the S3K Object and Priority Managers!

SCHG How-To Guide: Sonic the Hedgehog 2 (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix Super Sonic Bugs | Use Correct Height When Roll Jumping | Fix Jump Height Bug When Exiting Water | Fix Screen Boundary Spin Dash Bug | Correct Drowning Bugs | Fix Camera Y Position for Tails | Fix Tails Subanimation Error | Fix Tails' Respawn Speeds | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix Rexon Crash | Fix Monitor Collision Bug | Fix EHZ Deformation Bug | Correct CPZ Boss Attack Behavior | Fix Bug in ARZ Boss Arrow's Platform Behavior | Fix ARZ Boss Walking on Air Glitch | Fix ARZ Boss Sprite Behavior | Fix Multiple CNZ Boss Bugs | Fix HTZ Background Scrolling Mountains | Fix OOZ Launcher Speed Up Glitch | Fix DEZ Giant Mech Collision Glitch | Fix Boss Deconstruction Behavior | Fix Speed Bugs | Fix 14 Continues Cheat | Fix Debug Mode Crash | Fix 99+ Lives | Fix Sonic 2's Sega Screen
Design Choices
Remove the Air Speed Cap | Disable Floor Collision While Dying | Modify Super Sonic Transformation Methods & Behavior | Enable/Disable Tails in Certain Levels | Collide with Water After Being Hurt | Retain Rings When Returning at a Star Post | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Insert LZ Water Ripple Effect | Restore Lost CPZ Boss Feature | Prevent SCZ Tornado Spin Dash Death | Improve ObjectMove Subroutines | Port S3K Rings Manager | Port S3K Object Manager | Port S3K Priority Manager | Edit Level Order with ASM‎ | Alter Ring Requirements in Special Stages | Make Special Stage Characters Use Normal DPLCs | Speed Up Ring Loss Process | Change spike behaviour in Sonic 2
Adding Features
Create Insta-kill and High Jump Monitors | Create Clone and Special Stage Monitors | Port Knuckles
Sound Features
Expand Music Index to Start at $00 | Port Sonic 1 Sound Driver | Port Sonic 2 Clone Driver | Port Sonic 3 Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Expand the Music Index to Start at $00 (Sonic 2 Clone Driver Version) | Play Different Songs Per Act
Extending the Game
Extend the Level Index Past $10 | Extend the Level Select | Extend Water Tables | Add Extra Characters | Free Up 2 Universal SSTs