How to port the starposts from Sonic 2 to Sonic 1 - Hivebrain users

Discussion in 'Tutorials' started by DeltaWooloo, Aug 15, 2020.

  1. DeltaWooloo

    DeltaWooloo A hacker, helper, friend and a Wooloo Member

    Aug 7, 2019
    Comparing Sonic 1 and Sonic 2, there are many differences between each other. One of them is entering the special stages. With Sonic 1, you need to have 50 rings until going into the big ring at the end of the act. With Sonic 2, you need to have 50 rings then touch a lamppost which will take you into the special stage. There was originally a Russian tutorial on how to add it to Sonic 1 but since I’m not from Russia, nor speaking the language, I decided to make an English tutorial for you guys to try. Without further ado, let’s get started.

    Just a quick note: it is designed for Hivebrain disassembles. If you have SVN/GitHub, then it’s up to you to add it. Also, this isn’t just some copy and paste guides. I’m teaching people how to port things from Sonic 2 to 1 correctly without messing up.

    So before going into the juicy stuff we need to add the routine for the stars from the lamppost as that is used in Sonic 2's lamppost code so in Obj79_Index, after:

            dc.w Obj79_Twirl-Obj79_Index
    insert this:

            dc.w Obj79_Star-Obj79_Index
    Now that we have inserted a new routine, let's move on.

    Let’s take a look on Obj79 in the Sonic 2 Xenowhirl disassembly, mainly in Obj79_CheckActivation:

        andi.b    #$7F,d1
        move.b    subtype(a0),d2
        andi.b    #$7F,d2
        cmp.b    d2,d1
        bcc.w    loc_1F222
        move.w    x_pos(a3),d0
        sub.w    x_pos(a0),d0
        addi.w    #8,d0
        cmpi.w    #$10,d0
        bcc.w    return_1F220
        move.w    y_pos(a3),d0
        sub.w    y_pos(a0),d0
        addi.w    #$40,d0
        cmpi.w    #$68,d0
        bcc.w    return_1F220
        move.w    #$21+$80,d0 ; checkpoint ding-dong sound
        jsr    (PlaySound).l
        jsr    (SingleObjLoad).l
        bne.s    loc_1F206
        _move.b    #$79,0(a1) ; load obj79
        move.b    #6,routine(a1) ; => Obj79_Dongle
        move.w    x_pos(a0),objoff_30(a1)
        move.w    y_pos(a0),objoff_32(a1)
        subi.w    #$14,objoff_32(a1)
        move.l    mappings(a0),mappings(a1)
        move.w    art_tile(a0),art_tile(a1)
        move.b    #4,render_flags(a1)
        move.b    #8,width_pixels(a1)
        move.b    #4,priority(a1)
        move.b    #2,mapping_frame(a1)
        move.w    #$20,objoff_36(a1)
        move.w    a0,parent(a1)
        tst.w    (Two_player_mode).w
        bne.s    loc_1F206
        cmpi.b    #7,(Emerald_count).w ; DeltaWooloo: from here and below is what we'll be focusing on
        beq.s    loc_1F206
        cmpi.w    #$32,(Ring_count).w
        bcs.s    loc_1F206
        bsr.w    Obj79_MakeSpecialStars
    From where I marked is what we need to focus on. Here, it tells us if the sparkles will show if we don't have 7 emeralds collected or if we have 50 rings. We need to make sure that these requirements are added in our disassembly so the stars can appear once we follow the rules of enabling them. Insert this at the end of Obj79_HitLamp from where I marked to the end of the code.

    Now we need to change the code so it can work with Sonic 1. In Sonic 1, there are 6 emeralds, not 7 and the disassembly doesn't understand Sonic 2's constants. So we need to edit this line:

        cmpi.b    #7,(Emerald_count).w
    Replace the 7 with a 6 and replace the emerald count with $FFFFFE57 as that is the RAM used for the number of emeralds in Sonic 1.

    For the line below it, we need to change the branch loc_1F206 to loc_16F76. Do the same to the other line which is two lines below it.

    Now for this line:

        cmpi.w    #$32,(Ring_count).w
    Replace ring_count with $FFFFFE20.

    Now, all that is left is the line which jumps to Obj79_MakeSpecialStars. We need to port that and its contents to Sonic 1. First off, copy this below Obj79_LoadInfo:

    ; loc_1F4C4:
        moveq    #4-1,d1 ; execute the loop 4 times (1 for each star)
        moveq    #0,d2
    -    bsr.w    SingleObjLoad2
        bne.s    return_1F534
        _move.b    0(a0),0(a1) ; load obj79
        move.l    #Obj79_MapUnc_1F4A0,mappings(a1)
        move.w    #$47C,art_tile(a1)
        move.b    #4,render_flags(a1)
        move.b    #8,routine(a1) ; => Obj79_Star
        move.w    x_pos(a0),d0
        move.w    d0,x_pos(a1)
        move.w    d0,objoff_30(a1)
        move.w    y_pos(a0),d0
        subi.w    #$30,d0
        move.w    d0,y_pos(a1)
        move.w    d0,objoff_32(a1)
        move.b    priority(a0),priority(a1)
        move.b    #8,width_pixels(a1)
        move.b    #1,mapping_frame(a1)
        move.w    #-$400,x_vel(a1)
        move.w    #0,y_vel(a1)
        move.w    d2,objoff_34(a1) ; set the angle
        addi.w    #$40,d2 ; increase the angle for next time
        dbf    d1,- ; loop
    ; ===========================================================================
    ; loc_1F536:
        move.b    collision_property(a0),d0
        beq.w    loc_1F554
        andi.b    #1,d0
        beq.s    +
        move.b    #1,($FFFFF7CD).w
        move.b    #$10,(Game_Mode).w ; => SpecialStage
        clr.b    collision_property(a0)
        addi.w    #$A,objoff_34(a0)
        move.w    objoff_34(a0),d0
        andi.w    #$FF,d0
        jsr    (CalcSine).l
        asr.w    #5,d0
        asr.w    #3,d1
        move.w    d1,d3
        move.w    objoff_34(a0),d2
        andi.w    #$3E0,d2
        lsr.w    #5,d2
        moveq    #2,d5
        moveq    #0,d4
        cmpi.w    #$10,d2
        ble.s    +
        neg.w    d1
        andi.w    #$F,d2
        cmpi.w    #8,d2
        ble.s    loc_1F594
        neg.w    d2
        andi.w    #7,d2
        lsr.w    #1,d2
        beq.s    +
        add.w    d1,d4
        asl.w    #1,d1
        dbf    d5,loc_1F594
        asr.w    #4,d4
        add.w    d4,d0
        addq.w    #1,objoff_36(a0)
        move.w    objoff_36(a0),d1
        cmpi.w    #$80,d1
        beq.s    loc_1F5BE
        bgt.s    loc_1F5C4
        muls.w    d1,d0
        muls.w    d1,d3
        asr.w    #7,d0
        asr.w    #7,d3
        bra.s    loc_1F5D6
    ; ===========================================================================
        move.b    #$D8,collision_flags(a0)
        cmpi.w    #$180,d1
        ble.s    loc_1F5D6
        neg.w    d1
        addi.w    #$200,d1
        bmi.w    JmpTo10_DeleteObject
        bra.s    loc_1F5B4
    ; ===========================================================================
        move.w    objoff_30(a0),d2
        add.w    d3,d2
        move.w    d2,x_pos(a0)
        move.w    objoff_32(a0),d2
        add.w    d0,d2
        move.w    d2,y_pos(a0)
        addq.b    #1,anim_frame(a0)
        move.b    anim_frame(a0),d0
        andi.w    #6,d0
        lsr.w    #1,d0
        cmpi.b    #3,d0
        bne.s    +
        moveq    #1,d0
        move.b    d0,mapping_frame(a0)
        bra.w    JmpTo_MarkObjGone
    ; ===========================================================================
        jmp    DeleteObject
    ; ===========================================================================
        jmp    MarkObjGone
    ; ===========================================================================
    Don't build! Just because we added the code doesn't mean the curtains will close and you can enjoy yourself. We need to make the code compatible with Sonic 1 as the disassembly doesn't recognize the plus branches and the constants.

    In Obj79_MakeSpecialStars replace - (minus) with Obj79_MakeStarsLoop (this will be a new branch)
    Below replace bsr.w SingleObjLoad2 with jsr SingleObjLoad2
    Below _move.b 0 (a0), 0 (a1) by move.b 0 (a0), 0 (a1)
    Before return_1F534 replace dbf d1, - with dbf d1,Obj79_MakeStarsLoop
    In Obj79_Star replace the pluses with loc_1F553
    In loc_1F554 replace the pluses with loc_1F555
    In loc_1F594 replace the pluses with loc_1F595
    In loc_1F5D6 replace the pluses with loc_1F5D7

    This should fix the issues with the branches. Now to finish off doing the constants. The symbol ">" should tell you what to replace what with.

    Obj79_MapUnc_1F4A0 > Map_obj79b
    mappings > $04
    art_tile > $02
    render_flags > $01
    routine > $24
    x_pos > $08
    y_pos > $0C
    objoff_30 > $30
    objoff_32 > $32
    priority > $18
    width_pixels > $19
    mapping_frame > $ 1A
    x_vel > $10
    y_vel > $12
    objoff_34 > $ 34
    collision_property > $21
    Game_Mode > $FFFFF600
    objoff_36 > $36
    collision_flags > $20
    anim_frame > $1B

    Check and see if everything builds. If so, well done. If not, check for any mistakes made. Now you are thinking about the art and the mappings right. You can do the art and mappings yourself if you want or you can download this zip which should have the art and mappings ready.

    Add the lamppost art to artnem and obj79 and obj79b to _maps. Add the monitors art if you added the spindash dust, I'll explain why later.

    We need to include obj79b to our mappings so after map_obj79, insert this:

        include "_maps\obj79b.asm"
    Now we have two things left. Loading the stars art into VRAM and fixing the collision. Let's do the VRAM issue first. Go to this line in Obj79_MakeStarsLoop:

           move.w #$47C,$02(a1)
    The VRAM location, $47C is loaded by another art so we need to replace that with another location that is free. Replace $47C with $7A0. This will make sure the stars load into VRAM.


    If you added the spindash dust, you would know that the dust overwrites the lamppost so rather than inserting ($D800/$20), replace $47C with #($D740/$20). You need to also do that in Obj79_Main. You may ask why I added the monitors art into the zip file. It is because since we merged the stars art with the lamppost art into one file, it starts to overwrites the monitors' VRAM thus giving you a corrupt looking lamppost. We don't want that, so we just moved the art's VRAM a bit back to give room to the monitors' art. Speaking of the monitors. You may or may not notice but the file is smaller comparing to the original Sonic 1 monitor art. That is because I had to use ReadySonic's optimized monitor art and free tiles so there'll be less tiles to deal with and the art wouldn't overwrite with the lamppost art if you are adding new art to the lamppost which needs more tiles or it just overwrites for an unspecified reason.


    So the last thing is to fix the collision since if you go through the rings, you cannot go into the special stage. That is an easy fix actually and shouldn't take a lot of time. Replace Obj79_Star with this:

            tst.b    $21(a0)
            beq.s    loc_1F554
            move.b    #$10,($FFFFF600).w
    And go to loc_1F5BE and replace the only line there to this:

            move.b    #$D7,$20(a0)
    The reason why we did this is because Sonic 2 handles the collision to the lamppost differently comparing to Sonic 1 and that game doesn't understand how to handle the collision the way Sonic 2 does.

    And with all that, you should have the starposts fully working in Sonic 1. Grab 50 rings and fly to the special stage. Enjoy collecting those emeralds easily.


    In Knuckles in Sonic 2, when returning from a starpost from a special stages, you keep your rings. If you would like that to happen in Sonic 1, here is what you do. In Obj79_LoadInfo, delete this line:

            clr.w    ($FFFFFE20).w
    This should keep your rings you collected in the special stage and when you've died. I don't why you would want that but here you go.

    This tutorial was written by me
    Original creator of the tutorial by Shooter

    OG tutorial:туториалы-по-хакингу-sonic-genesismega-drive/?page=9

    ReadySonic by Mecury
    RandomName helped me with a few bugs so credits to him

    Attached Files:

    Last edited: Aug 17, 2020
    Samey and RandomName like this.