Adding a CD-style level restart to Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by luluco, Aug 11, 2017.

  1. luluco


    I have been learning m68k assembly recently with some help from the nice guides in the wiki. I'm starting with Sonic 1, generally implementing the bug fixes and learning as I go.

    I thought of a relatively simple feature I'd like to add, the ability to restart a level by pressing a button when paused, like in CD. I managed to track down the specific code that handles the button inputs in the pause loop and moved instructions around so that the A button can be pressed even with debug mode off. Having done that I started to dig deep into the disassembly looking for how the game restarts a level when you die. After a while of trying out different labels like 'KillSonic', 'Sonic_Death' and other similar-sounding instructions I realized it wasn't going to be as easy as "calling a function".

    So TL;DR: where and how does Sonic 1 restart levels? How can I replicate it in my own label? (by 'label' I mean where you begin 'functions', like "Pause_Reset:", with the colon at the end, but no '@' sign to the left)

    Here's the code I have right now, please excuse the mess, I like to comment out things before I'm certain they're working:

    Code (Text):
    2. Pause_Loop:
    3.         move.b  #$10,(v_vbla_routine).w
    4.         bsr.w   WaitForVBla
    5.         ; now the A button will reset even outside of debug mode
    6.         btst    #bitA,(v_jpadpress1).w ; is button A pressed?
    7.         bne.s   Pause_Reset
    8.         tst.b   (f_slomocheat).w ; is slow-motion cheat on?
    9.         beq.s   Pause_ChkStart  ; if not, branch
    10.         ;btst   #bitA,(v_jpadpress1).w ; is button A pressed?
    11.         beq.s   Pause_ChkBC ; if not, branch
    12.         ;move.b #id_Title,(v_gamemode).w ; set game mode to 4 (title screen)
    13.         ;nop   
    14.         ;bra.s  Pause_EndMusic
    15. ; ===========================================================================
    17. ; TODO: make reset just reload the level
    18. Pause_Reset:
    19.         move.b  #id_Title,(v_gamemode).w    ; set game mode to 4 (title screen)
    20.         nop
    21.         bra.s   Pause_EndMusic

    As you can see all I have done for now was separate the game restart functionality, so I could work on it more clearly.

    Also I'm new to these forums, so please tell me if I did anything wrong.
  2. MarkeyJester


    ♡ ! Resident Jester
    Actually, you were very close. Calling "KillSonic" would have done the trick. The thing is, the subroutine "KillSonic" expects Sonic's object RAM address to be inside address register "a0".

    So simply do the following:

    Code (Text):
    1.         lea ($FFFFD000).w,a0
    2.         jsr KillSonic
    This will trigger Sonic's routines to perform the series of death based functions. But of course, the game is still paused, so you'll have force the game to unpause and return out of the pause loop:

    Code (Text):
    1.         move.b  #$80,($FFFFF003).w
    2.         move.w  #0,($FFFFF63A).w
    3.         rts
  3. luluco


    Okay, I'll try that, thanks a lot.

    By the way, I love your hack "Next Level", Sonic's rotating sprite and more animation frames are amazing.
  4. luluco


    Great, now Sonic has a self-destruct button :D

    This is the final code I ended up with:

    Code (Text):
    3. Pause_Reset:
    4.         lea (v_player).w,a0 ; 'KillSonic' expects Sonic's object RAM address in a0
    5.         jsr KillSonic
    6.         move.w  #0,(f_pause).w  ; unpause
    7.         nop
    8.         bra.s   Pause_EndMusic

    I've copied part of the original game-reset functionality to fix the music (it wouldn't play again after killing sonic).
    Without 'nop' the emulator (Kega) crashed upon loading the rom for some reason, but I didn't test twice before adding it, seems to be harmless though.

    Now what I'm gonna do is see if I can manually replicate only parts of the kill function, so that it doesn't look as if it's actually killing Sonic, just restarting the level.
    Also I'm going to make it so restarting doesn't take away a life, because I believe that's unfair, but to keep the game balanced I'll have to see some way to keep track of the last score when the level started or when Sonic really died, that way I can reset to said score and prevent players from restarting over and over to accumulate more points.
  5. Iso Kilo

    Iso Kilo

    Local Wolf-Fox Member
    Small Town, BC
    The Elementalists
    Bumping this extremely old thread, but I've got a way to actually make the level reset without outright killing Sonic (Because imo that's a bit of a slap in the face)
    It's pretty simple, just make the Pause_Reset function look like this:
    Code (Text):
    1. Pause_Reset
    2.         cmp.b    #1,($FFFFFE12).w    ; Check if you only have 1 life
    3.         beq.s    Reset_Return    ; If so branch (This way you don't get 0 lives and then underflow)
    4.         addq.b    #1,($FFFFFE1C).w ; update lives    counter
    5.         subq.b    #1,($FFFFFE12).w ; subtract 1 from number of lives
    6.         move.b    #0,($FFFFFE30).w ; clear lamppost counter
    7.         move.b    #1,($FFFFFE02).w    ; Set the level reset flag
    8.         move.b  #$80,($FFFFF003).w    ; Unpause music
    9.         move.w  #0,($FFFFF63A).w
    10. Reset_Return
    11.         rts    ; Return
    And this might have something to do with the original tutorial, but once you hit 1 life (AKA you can't reset) it will go into slow-motion mode. Not a massive issue, but it should be noted.
    Also if you're in debug, then it seems to force the slow-motion cheat and you can't get out of it?
    Last edited: Mar 3, 2020
  6. Cyber Axe

    Cyber Axe

    Following in the footsteps of Iso Kilo here's my version, it doesn't have the slow mo issue.

    In Pause_Loop: change beq.s Pause_ChkStart to beq.s Pause_Check_Reset

    Code (Text):
    2. Pause_Loop:
    3.         move.b    #$10,(v_vbla_routine).w
    4.         bsr.w    WaitForVBla
    5.         tst.b    (f_slomocheat).w                                                     ; is slow-motion cheat on?
    6.         beq.s    Pause_Check_Reset                                                 ; if not, branch
    Then add these below the Pause_Loop: Subroutine
    Code (Text):
    2. Pause_Check_Reset:
    3.         cmp.b #1,(v_lives).w                                                    ; Check if you only have 1 life
    4.         beq.s Pause_ChkStart                                                      ; If so branch (This way you don't get 0 lives and then underflow)
    5.         btst    #bitA,(v_jpadpress1).w                                         ; is button A pressed?
    6.         bne.s    Pause_Reset                                                                ; if so, branch
    7.         btst    #bitB,(v_jpadpress1).w                                         ; is button B pressed?
    8.         bne.s    Pause_Reset                                                                ; if so, branch
    9.         btst    #bitC,(v_jpadpress1).w                                         ; is button C pressed?
    10.         bne.s    Pause_Reset                                                                ; if so, branch
    11.         bra.s    Pause_ChkStart                                                        ; Check Start Buttom
    13. Pause_Reset:
    14.         lea (v_objspace).w,a0
    15.         jsr KillSonic                                                                        ; Kill Sonic
    16.     bra.s Pause_EndMusic                                                         ; Unpause