How to free up 2 universal SSTs! (Sonic 2 only)

Discussion in 'Tutorials Archive' started by redhotsonic, May 8, 2012.

Thread Status:
Not open for further replies.
  1. redhotsonic

    redhotsonic Also known as RHS Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    Location:
    England
    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 automatically 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.


    Hello again. This will probably be the last guide I make as I don't have anything else for you (I have a plan for the future for another guide which is about porting S3K's Priority manager to S2, but not for a while as I haven't perfected it myself yet). But looking from my notes, this is the last guide I can supply.


    Also, I apologise if there has been a guide for something like this, but I couldn't find anything. Anyway...


    How to free up 2 universal SST's!


    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 Module's guide on how to port the S3K object manager to 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 port, 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 explains, 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 1st.


    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: I am using Xenowhirl's 2007 disassembly. I'm not sure if you can use this guide if you have the SVN disassembly, but correct me if wrong (give it a go, you never know). 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 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



    anim_frame_duration = $1E



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



    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



    This is part of the object code to trigger the rescue plane and birds from 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:



    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



    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:



    loc_13FE2:
    move.w #$2D0,anim_frame_duration(a0)


    addq.b #2,routine(a0)


    rts



    This is part of the "Game Over" object. When you get Game Over, and the text appears, $2D0 gets set 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:



    loc_13FE2:
    move.b #$C0,anim_frame_duration(a0)


    addq.b #2,routine(a0)


    rts



    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



    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



    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:



    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



    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 therefor, 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 therefor, 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:



    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)



    And change it to this:



    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)



    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:



    loc_1419C:
    subq.w #1,anim_frame_duration(a0)


    bne.s BranchTo18_DisplaySprite


    addq.b #2,routine(a0)



    And change it to:



    loc_1419C:
    subq.w #1,objoff_34(a0)


    bne.s BranchTo18_DisplaySprite


    addq.b #2,routine(a0)



    Next, go to:



    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



    And change to:



    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



    Next, go to:



    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



    And change to:



    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



    Next, go to:



    loc_142B0:
    tst.w anim_frame_duration(a0)


    beq.s loc_142BC


    subq.w #1,anim_frame_duration(a0)


    rts



    And change to:



    loc_142B0:
    tst.w objoff_34(a0)


    beq.s loc_142BC


    subq.w #1,objoff_34(a0)


    rts



    Next, go to:



    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)



    And change to:



    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)



    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:



    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)



    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:



    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)



    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:



    lea (MainCharacter).w,a1 ; a1=character
    move.l #$1000505,mapping_frame(a1)


    move.w #$100,anim_frame_duration(a1)


    rts



    And change to:



    loc_3AC56:
    lea (MainCharacter).w,a1 ; a1=character


    move.l #$1000505,mapping_frame(a1)


    move.b #$FF,anim_frame_duration(a1)


    rts



    Same problem as before really.


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



    move.w #$2D,anim_frame_duration(a1)



    It's extremely simple, you change it to:



    move.b #$2D,anim_frame_duration(a1)



    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:



    move.w #$2D,next_object+anim_frame_duration(a1)



    and change to:



    move.b #$2D,next_object+anim_frame_duration(a1)



    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


    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:



    move.b #$488,anim_frame_duration(a0)



    back to this:



    move.w #$488,anim_frame_duration(a0)



    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 have free'd 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 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:



    anim_frame_duration = $1E



    to this



    anim_frame_duration = $23



    Then, look for respawn_index and change:



    respawn_index = $23



    to this:



    respawn_index = $1E



    Now, anim_frame_duration is using $23 (it won't use $24 because we changed it to bytes), 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 owrd, it over-writes $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.

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



    Example, at "loc_304D4:", change:



    move.w #$488,anim_frame_duration(a0)



    back to this:



    move.w #$488,respawn_index(a0)



    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 free'd 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



    inertia = $14 ; and $15 ; directionless representation of speed... not updated in the air



    Change it to



    inertia = $20 ; and $21 ; directionless representation of speed... not updated in the air



    For Sonic and Tials, $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. Not, instead of the original $1F, $20 and $21 being free for Sonic and Tails, it's now $14, $15 and $1F that is free for them.


    SVN user? Read this quote! It's important. If Xenowhirl user, you can ignore the quote.


    Next, go to:



    width_pixels = $19



    and change to



    width_pixels = $14



    width_pixels can freely use. 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). But again, you can use $19 for whatever you want.


    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.


    Bugs


    EDIT: This bug has NOW been fixed. Click here for the fix.


    Following this guide to a clean Sonic 2 disassembly (and my Sonic2Recreation), the ONLY bug I could find was this:


    [​IMG]


    The width_pixels have gone wrong on this, and to be honest, I have no idea why. If anyone could find out why, that would be great. I've looked into it myself, and I don't see why it's gone wrong in the first place, but meh. 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.


    Another width_pixels problem is the ARZ boss, but that as already fucked in the original game. Module has shown in one of his guides how to fix it, although, it wasn't totally fixed. So I added an additional step to his guide (with his permission of course), which can be found here.


    Well, I hope you've enjoyed this guide. It's my last for a while until I can get my ported priority manager working correctly, and once that's done, I want to wait a while (probably after the contest results or something).


    To celebrate my last guide, I am putting a link to all my guides in my signature, in case you want to know =P


    Any questions and comments are welcome. Any mistakes, let me know. Any problems, let me know.


    Enjoy


    redhotsonic
     
    Last edited by a moderator: Sep 20, 2012
  2. redhotsonic

    redhotsonic Also known as RHS Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    Location:
    England
    I read through this guide again and completely forgot about the bug I mentioned.


    [​IMG]


    Well, I quickly looked into it and have fixed it.


    The problem was that part of it's height is involved and over-writes the new width_pixels SST. You cannot change the height's location of this object, otherwise the object will never work. But I realised in this object, that it's priority is used for something different as well. Instead, it's 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:



    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



    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:



    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



    Just before the "move.w #$200,d0", paste the width_pixel line there. So, you end up with this:



    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



    There. That object is now fixed. All other objects, I didn't notice any problems. If you run into anything, let me know!


    Cheers,


    redhotsonic
     
Thread Status:
Not open for further replies.