How To: Reversing the Timer in Sonic 2 (ASM)

Discussion in 'Tutorials Archive' started by DLloyd, May 7, 2013.

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

    DLloyd Newcomer Member

    Joined:
    May 3, 2013
    Messages:
    9
    Location:
    Massachusetts, USA
    As I mentioned in my intro post, I've been noodling around with a disasm of Sonic 2 for a few weeks, learning how all the parts to go together. While trying to think of some cool things to do with the game, I stumbled across this post by XenoSonic (from July 2005) over on Retro which explains how to edit the binary to make the timer start at 9:59 and count down, instead of starting at 0:00 and counting up. I looked around, but didn't see any ASM tutorials that did a similar thing. Seeing this as a great opportunity to practice assembly editing, I worked to convert that tutorial from a hex editing one, and I believe I've succeeded, and I'd like to share the results so that others who may want to make this change can do so. This was done using the Xenowhirl disassembly.

    I've mirrored the format of XenoSonic's post for those of you who want to follow along with that one.

    Step 1: Setting things up

    This is the part where we make the timer flash when time is running out, and where we set the point in time at which the "TIME OVER" occurs. 

    First, let's take care of that timer flashing. In each of loc_40804:, loc_40820:, loc_4085A:, and loc_40876:, you'll see the following line:


    cmpi.b #9,(Timer_minute).w

    In all four cases, change that line to:


    cmpi.b #0,(Timer_minute).w
    Next, we need to change the TIME OVER, which typically occurs the instant before the clock ticks over to ten minutes. Now, we want to make it so it happens when the clock hits zero.

    In Hud_ChkTime:, you'll see the line:


    cmpi.l #$93B3B,(a1)+ ; is the time 9:59?

    and change it to:


    cmpi.l #$0,(a1)+ ; is the time 0:00?

    Step 2: Starting the timer at 9:59

    In this step, we're going to initialize the timer to start at 9:59, instead of 0:00, and also set the timer to set correctly after restarting from death but having previously hit a starpost.

    In the label Level_FromCheckpoint:, we need to alter some of the middle of that code. Take these lines:


    move.b #1,(Update_HUD_score).w
    move.b #1,(Update_HUD_rings).w
    move.b #1,(Update_HUD_timer).w
    move.b #1,(Update_HUD_timer_2P).w
    jsr (loc_17AA4).l
    jsr (RingsManager).l
    jsr (SpecialCNZBumpers).l
    jsr (RunObjects).l
    jsr (BuildSprites).l
    bsr.w JmpTo_loc_3FCC4
    bsr.w SetLevelEndType

    and make some changes so that part now looks like this:


    move.b #1,(Update_HUD_score).w
    move.b #1,(Update_HUD_rings).w
    move.b #1,(Update_HUD_timer).w
    move.b #1,(Update_HUD_timer_2P).w
    jmp (Reverse_Timer).l ; jump to code newly setting timer to 9:59
    ; notice the loc_17AA4 label is now gone from this section
    loc_423A:
    jsr (RingsManager).l
    jsr (SpecialCNZBumpers).l
    jsr (RunObjects).l
    jsr (BuildSprites).l
    bsr.w JmpTo_loc_3FCC4
    bsr.w SetLevelEndType

    Now we need to add the Reverse_Timer subroutine. After byte_1795E:, there is a nop command and then a few blank lines before the comments to subroutine loc_17AA4. In that blank area, place the following subroutines.


    Reverse_Timer:
    jsr (loc_17AA4).l ; loc_17AA4 subroutine call moved here
    cmpi.w #$0, (Timer).w
    bne CP_Return
    cmpi.w #$0, (Timer_second).w
    bne CP_Return
    move.l #$93C3C, (Timer).w ; set the timer to maximum possible time

    CP_Return:
    jmp (loc_423A).l ; jump back to continue checkpoint loading

    Step 3: The countdown

    Lastly, we need our timer to count down instead of up. The last few lines of the subroutine Hud_ChkTime: are


    bcs.s Hud_ChkLives
    move.b #0,(a1)
    addq.b #1,-(a1)
    cmpi.b #60,(a1)
    bcs.s loc_40E18
    move.b #0,(a1)
    addq.b #1,-(a1)
    cmpi.b #9,(a1)
    bcs.s loc_40E18
    move.b #9,(a1)

    loc_40E18:

    We're going to do two things at once here. First, we need to add code to make the seconds count go from "00" to "59" when appropriate, at the end of the minute. Second, we need to switch it so that things count down, instead of up. Here's how we do it:


    bcs.s Hud_ChkLives
    move.b #0,(a1)
    subq.b #1,-(a1) ; subtract instead of add 1
    cmpi.b #60,(a1)
    bcs.s loc_40E18
    move.b #0,(a1)
    subq.b #1,-(a1) ; subtract instead of add 1
    cmpi.b #9,(a1)
    bcs.s Timer_CD ; new subroutine to make 00 come after 59
    move.b #9,(a1)

    Timer_CD:
    cmpi.w #$0, (Timer_second).w ; if the current seconds value is 00
    move.w #$3C3C, (Timer_second).w ; then the next one should be 59
    jmp loc_40E18 ; continue (this jump might not be needed)

    loc_40E18:

    Step 4: Ironing out one final bug

    When you get a TIME OVER, the clock typically resets back to giving full time. This can prove particularly problematic if the death occurs after a starpost has been hit. To make it so that after a TIME OVER, the game returns to the time on the clock as of the last time a starpost was hit, delete this line in loc_14034:


    clr.l (Saved_Timer).w

    And that does it! Your game timer should now count down instead of up. I explain fixing the Time Bonus below, in a separate post.

    If you have any problems, questions, comments, etc., let me know.
     
    Last edited by a moderator: May 7, 2013
    Nat The Porcupine likes this.
  2. ThomasThePencil

    ThomasThePencil resident psycho Member

    Joined:
    Jan 29, 2013
    Messages:
    910
    Location:
    the united states. where else?
    This seems like a pretty interesting guide. I might give this a go later.
     
  3. nineko

    nineko I am the Holy Cat Member

    Joined:
    Mar 24, 2008
    Messages:
    1,902
    Location:
    italy
    One thing I noticed, you can probably replace these two lines with a single move.l:

    move.w #$3C3C, (Timer_second).w ; place 59 seconds into Timer_second
    move.w #$9, (Timer).w ; place 9 minutes into TimerI don't know which address you should use with that, though, because of those fancy names used by the new disassemblies. In Sonic 1 I'd do
    Code:
    move.l	#$093C3C,($FFFFFE22).w
    Also: I moved the topic to the Tutorials section.

    Also²: $3C is 60, not 59, so your comments are misleading.
     
    Last edited by a moderator: May 7, 2013
  4. DanielHall

    DanielHall Well-Known Member Member

    Joined:
    Jan 18, 2010
    Messages:
    860
    Location:
    North Wales
    Ooh. BTW, does the HUD flash red when you get to the final minute?

    Good work!
     
  5. ThomasThePencil

    ThomasThePencil resident psycho Member

    Joined:
    Jan 29, 2013
    Messages:
    910
    Location:
    the united states. where else?
    Erm, I just realized something. In Sonic 2, "loc_17AA4" actually points to the objects manager. So essentially, you delete that line, your level won't start the next time you load it. Make sense?

    EDIT: Wait, never mind. It seems you've put it at Reverse_Timer. However, you could alternatively do this:


    move.b #1,(Update_HUD_score).w
    move.b #1,(Update_HUD_rings).w
    move.b #1,(Update_HUD_timer).w
    move.b #1,(Update_HUD_timer_2P).w
    jsr (ObjectsManager).l
    jmp (Reverse_Timer).l ; jump to code newly setting timer to 9:59

    loc_423A:
    jsr (RingsManager).l
    jsr (SpecialCNZBumpers).l
    jsr (RunObjects).l
    jsr (BuildSprites).l
    bsr.w JmpTo_loc_3FCC4
    bsr.w SetLevelEndType 
    And delete the call from Reverse_Timer so it looks like this:


    Reverse_Timer:
    cmpi.w #$0, (Timer).w
    bne CP_Return
    cmpi.w #$0, (Timer_second).w
    bne CP_Return
    move.l #$93C3C, (Timer).w ; put 9:59 into the timer

    CP_Return:
    jmp (loc_423A).l ; jump back to continue checkpoint loading

    Right? (ObjectsManager is the Hg disassembly equivalent of loc_17AA4)
     
    Last edited by a moderator: May 7, 2013
  6. DLloyd

    DLloyd Newcomer Member

    Joined:
    May 3, 2013
    Messages:
    9
    Location:
    Massachusetts, USA
    Thank you for moving this, Nineko; sorry for putting it in the wrong place originally. I've incorporated your suggestion for the single move above.

    And Dandaman, yes it does! That's what Step 1 is for.

    Thomas, yes that works as well. I was doing it the other way more because that's how it went in Xenosonic's hex tutorial of the same, but they seem to be interchangeable.
     
    Last edited by a moderator: May 7, 2013
  7. DanielHall

    DanielHall Well-Known Member Member

    Joined:
    Jan 18, 2010
    Messages:
    860
    Location:
    North Wales
    Shows how much attention I pay. lol
     
  8. DLloyd

    DLloyd Newcomer Member

    Joined:
    May 3, 2013
    Messages:
    9
    Location:
    Massachusetts, USA
    (Based on XenoSonic's followup hex-editing post)

    So fixing the time bonuses isn't too bad, either. Head to loc_19452:, where you'll find this pair of lines:


    divu.w #$F,d0
    moveq #$14,d1

    You'll need to change the second of these, but some comments won't hurt.


    divu.w #$F,d0 ; time bonuses intervals are 15 (F) seconds wide
    moveq #$28,d1 ; we need 40 intervals to cover all times from 0 to 10 mins

    Then, in TimeBonuses: you'll have this:


    TimeBonuses:
    dc.w 5000, 5000, 1000, 500, 400, 400, 300, 300
    dc.w 200, 200, 200, 200, 100, 100, 100, 100
    dc.w 50, 50, 50, 50, 0

    and since our timing is in reverse, you'll need to reverse the order of these as well as pad the front of the list with empty intervals for 0:00 to 5:00 remaining, so change it to this:

    Code:
    TimeBonuses:
    	dc.w      0,   0,   0,   0,   0,    0,    0,    0	; 0 to 2 minutes remaining
    	dc.w	  0,   0,   0,   0,   0,    0,    0,    0	; 2 to 4 minutes remaining
    	dc.w	  0,   0,   0,   0,  50,   50,   50,   50	; 4 to 6 minutes remaining
    	dc.w	100, 100, 100, 100, 200,  200,  200,  200	; 6 to 8 minutes remaining
    	dc.w	300, 300, 400, 400, 500, 1000, 5000, 5000	; 8 to 10 minutes remaining
    
     
    Last edited by a moderator: May 7, 2013
  9. ThomasThePencil

    ThomasThePencil resident psycho Member

    Joined:
    Jan 29, 2013
    Messages:
    910
    Location:
    the united states. where else?
    Ah, now I understand why you did it the way you did. Still, it shouldn't cause any problems to do what I suggested, but really, it all comes down to personal preference.

    Concerning the guide itself, it's well written and well thought out.

    *starts clapping*

    Well done. Well done indeed.
     
  10. DanielHall

    DanielHall Well-Known Member Member

    Joined:
    Jan 18, 2010
    Messages:
    860
    Location:
    North Wales
    You hit member on four posts. Nice job!
     
Thread Status:
Not open for further replies.