don't click here

Some changes/fixes for Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by RetroKoH, Sep 4, 2012.

  1. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,214
    459
    63
    Japan
    In fairness, the correct fix to the original problem is to ensure the SEGA PCM sample does not cross an $8000 boundary.

    As long as you align the SEGA PCM such that it doesn't cross, and change the bankswitch instructions (which if my memory doesn't fail me is right at the beginning during init of the Z80 driver in general), then you don't have to use the 68k method.

    Not that it matters much either way, we're talking minute benefits of both, so your solution is still more than welcome, just putting it out there while we're on topic ;)
     
  2. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Might as well go with the 68k solution rather than being concerned with boundaries (They're already a pain with DMA boundaries) and it's not like much happens on the Sega screen in the first place to worry about the chant's CPU usage. But you're definitely right it is worth pointing out that as an option.
     
  3. Brainulator

    Brainulator

    Regular garden-variety member Member
    For Mega Games 10 at least, Sonic 1 was slightly modified to add an instruction that corrects the bank number immediately after decompressing the Z80 driver. This isn't the cleanest way, but if you can't be bothered to recompress the code, it might work. Bear in mind you'd also have to make sure the location within the bank is correct, too.
     
    • Like Like x 1
    • Informative Informative x 1
    • List
  4. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,214
    459
    63
    Japan
    Well that's what this is for d=
    Ideally you'd need something similar for the bank switch instructions, specifically these:
    Code (Text):
    1.        ld   a,(SegaPCM>>00Fh)&001h       ; set bit 15 (+8000 address)
    2.        ld   (06000h),a               ; ''
    3.        ld   b,008h                   ; set remaining number of bits to write
    4.        ld   a,(SegaPCM>>010h)&03Fh       ; prepare 68k "SEGA" upper byte address
    But then, you'll have to hope the two bytes of the address are not part of an offset/length sequence in the kosinski compressed data (i.e. hope they're not a copy of a previous necessary byte, or to be copied to another necessary byte). The location didn't, by dumb luck on 2005 Hivebrain's part. It would be a dirty fix, but a fix none the less and would preserve the ROM 1:1, if of course; you didn't want a disassembly of the Z80 and to have to reassemble/recompress at build time.

    None of this is an issue for us now. We've got multiple methods, the 68k one Kilo is talking about (with their cleanup) and I suspect the newer GIT probably has the Z80 portion reassembled/recompressed correctly, possibly with Clownacy's research on accurate SEGA compressions. So this is mearly a thought experiment for a solution we're not desperate for.
     
  5. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Saving a Couple Tiles With Monitors
    Monitors are pretty unoptimized in Sonic 1. They use 3 frames of noise for the static frames, which in my opinion, is excessive. And duplicate Sonic's life icon, when it's already loaded in VRAM! Let's reduce this and save some tiles.
    Target disasm: Hivebrain 2005
    Advantages: Saves 12 tiles in VRAM, which is enough for 3 more power ups (Most likely you'd want that for the elemental shields), saves a small bit of ROM space since we're reducing tile, mapping, and animation data. 1-up monitors will now use the HUD's life icon allowing it to dynamically change if you add extra characters without any extra work.
    Disadvantages: Static frames are less unique since it's reduced to 1 frame.

    First, download the Optimized Monitor Assets zip provided and merge it with the root of your disassembly.
    Then open sonic1.asm and head to Obj26:.
    Change
    Code (Text):
    1.         move.b    #$B,$1A(a0)    ; use broken monitor frame
    To
    Code (Text):
    1.         move.b    #9,$1A(a0)    ; use broken monitor frame
    Next at Obj2E_Main: remove
    Code (Text):
    1.         addq.b    #2,d0
    Included as well is a zip with an updated Monitor.xml for SonLVL, just replace the one in the Common folder within your project folder with this.

    And it's as simple as that! Enjoy your free 12 tiles!
    upload_2024-4-3_6-43-43.png
     

    Attached Files:

  6. Brainulator

    Brainulator

    Regular garden-variety member Member
    Funnily enough, the two Sonic icons are slightly different around the ear area.

    Here's the frames, for reference (colors may not be accurate):
    upload_2024-4-3_18-8-36.png
    upload_2024-4-3_18-8-51.png
    Sonic 1 suu.gif

    Which one is the "real" or "correct" one, I'm not certain.
     
    • Informative Informative x 4
    • Agree Agree x 2
    • Like Like x 1
    • List
  7. nineko

    nineko

    I am the Holy Cat Tech Member
    6,317
    489
    63
    italy
    I never noticed that :O

    (in fact, even I made them share art in my hack back in the day)
     
  8. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    I personally believe the life icon from the HUD to be the intended one, as the developers would've been looking at it more often than the monitors making them more likely to feel the need to update it without considering the fact that the monitor needed to be changed too, especially if it's only a 2 pixel difference.
     
  9. Brainulator

    Brainulator

    Regular garden-variety member Member
    I had that feeling, too, if only because the HUD's art is slightly more advanced in its shading.
     
  10. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Nineko brought up a great point just now about the centiseconds timer in Sonic CD only being calculated properly at 60 FPS. Which made me realize, the timer as a whole only works at 60 FPS, it's a pretty easy fix so let's do it!
    Under Hud_ChkTime, just after the branch to TimeOver, replace this line
    Code (Text):
    1.         cmpi.b    #60,(a1)
    With this
    Code (Text):
    1.         moveq   #0,d0
    2.         moveq   #0,d1
    3.         move.b  (a1),d1
    4.         move.b  #60,d0              ; Set amount of frames needed to tick over a second.
    5.         btst    #6,($FFFFFFF8).w    ; Is the console PAL?
    6.         beq.s   @Cont               ; If not continue.
    7.         move.b  #50,d0              ; Otherwise, use 50 frames.
    8.  
    9. @Cont:
    10.         cmp.b   d0,d1
    11.  
    If you have the centiseconds counter from Sonic CD too you'll want to update this too. Nineko also suggested using a LUT instead of using the slow mulu and divu instructions.
    After TimeOver, place the table.
    Code (Text):
    1. HUD_CsTimesNTSC:
    2.         dc.b    0, 1, 3, 5, 6, 8, 10, 11, 13, 15, 16
    3.         dc.b    18, 20, 21, 23, 25, 26, 28, 30, 31, 33
    4.         dc.b    35, 36, 38, 40, 41, 43, 45, 46, 48, 50
    5.         dc.b    51, 53, 55, 56, 58, 60, 61, 63, 65, 66
    6.         dc.b    68, 70, 71, 73, 75, 76, 78, 80, 81, 83
    7.         dc.b    85, 86, 88, 90, 91, 93, 95, 96, 98, 0
    8.         even
    9.  
    10. HUD_CsTimesPAL:
    11.         dc.b    0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20
    12.         dc.b    22, 24, 26, 28, 30, 32, 34, 36, 38, 40
    13.         dc.b    42, 44, 46, 48, 50, 52, 54, 56, 58, 60
    14.         dc.b    62, 64, 66, 68, 70, 72, 74, 76, 78, 80
    15.         dc.b    82, 84, 86, 88, 90, 92, 94, 96, 98, 0
    16.         even
    Then replace your existing centiseconds code, which looks like this
    Code (Text):
    1.         moveq    #0,d1
    2.         move.b    ($FFFFFE25).w,d1 ; Load frames
    3.         mulu.w    #100,d1            ; Convert to centiseconds
    4.         divu.w    #60,d1
    5.         swap    d1
    6.         move.w    #0,d1
    7.         swap    d1
    8.         cmpi.l    #$93B3B,($FFFFFE22).w    ; Are we at the max time?
    9.         bne.s    @NotMax        ; If not, branch
    10.         move.w    #99,d1                ; If so, set centiseconds to 99
    11.  
    12. @NotMax:
    13.         bra.w    Hud_Secs
    With our new code that uses the LUT
    Code (Text):
    1.         moveq    #0,d1
    2.         move.b    ($FFFFFE25).w,d1 ; Load frames
    3.         btst    #6,($FFFFFFF8).w    ; Is the console PAL?
    4.         beq.s    @NTSC                ; If not load NTSC values
    5.         move.b    HUD_CsTimesPAL(pc,d1.w),d1
    6.         bra.s    @Cont2
    7.  
    8. @NTSC:
    9.         move.b    HUD_CsTimesNTSC(pc,d1.w),d1
    10.  
    11. @Cont2:
    12.         cmpi.l    #$93B3B,($FFFFFE22).w    ; Are we at the max time?
    13.         bne.s    @NotMax        ; If not, branch
    14.         move.w    #99,d1                ; If so, set centiseconds to 99
    15.  
    16. @NotMax:
    17.         bra.w    Hud_Secs
     
    Last edited: Apr 10, 2024
  11. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,458
    93
    your mom
    You should also update the check that checks if the timer is at the maximum value. Otherwise time overs won't actually happen in PAL mode, since it'll just perpetually wait until the frame counter reaches 59, which will never happen.

    EDIT: Also, if you want a cleaner way of picking a LUT, try starting off with a default pointer in an address register, check the condition, and then swap out the pointer for when the condition is true (if there's a register clash with a0 here, you can just pick another register to use instead).

    Code (ASM):
    1.         lea     HUD_CsTimesNTSC(pc),a0          ; Use the NTSC table by default
    2.         btst    #6,($FFFFFFF8).w                ; Is this actually a PAL console?
    3.         beq.s   @GetCentisecond                 ; If not, branch
    4.         lea     HUD_CsTimesPAL(pc),a0           ; If so, use the PAL table
    5.  
    6. @GetCentisecond:
    7.         move.b  (a0,d1.w),d1                    ; Get centisecond value

    Also, for deciding between checking 9:59:49 (for PAL) or 9:59:59 (for NTSC) when checking for the maximum timer value, you can do something like this:

    Code (ASM):
    1.         move.l  #(9<<16)|(59<<8)|(59),d0        ; Check for 9:59:59 by default
    2.         btst    #6,($FFFFFFF8).w                ; Is this actually a PAL console?
    3.         beq.s   @CheckMaxTime                   ; If not, branch
    4.         move.b  #49,d0                          ; If so, check the PAL frame counter value
    5.  
    6. @CheckMaxTime:
    7.         cmp.l   ($FFFFFE22).w,d0                ; Are we at the max time?

    That should be implemented for the time over check, and the centisecond calculation.
     
    Last edited: Apr 10, 2024
  12. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    There are 2 checks for a time over, so I'd actually put the time over value in a register less likely to be used by the drawing subroutines that will happen in between the first and second check, something like d5, and that should make things a little quicker too.
    So at Hud_ChkTime you'd replace
    Code (Text):
    1.         lea    ($FFFFFE22).w,a1
    2.         cmpi.l    #$93B3B,(a1)+    ; is the time 9.59?
    3.         beq.s    TimeOver    ; if yes, branch
    with
    Code (Text):
    1.         lea    ($FFFFFE22).w,a1        ; Load the current time.
    2.         move.l    #$93B3B,d5            ; Set the max time to 9:59:59
    3.         move.l    (a1)+,d1
    4.         btst    #6,($FFFFFFF8).w    ; Is the console PAL?
    5.         beq.s    @Check959            ; If not continue.
    6.         move.b    #$31,d5                ; Otherwise, set it to 9:59:49.
    7.  
    8. @Check959:
    9.         cmp.l    d5,d1                ; Does the current time match our max time?
    10.         beq.w    TimeOver            ; If so, then we've time overed.
    Then when it's the centiseconds turn at checking for a timeover you can just use this
    Code (Text):
    1.         cmp.l    ($FFFFFE22).w,d5    ; Have we time over'd?
    2.         bne.s    @NotMax    ; If not, continue.
    3.         move.w    #99,d1    ; Max out digits.
     
  13. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,458
    93
    your mom
    Yeah I was thinking about something like that, but I just wanted to throw some example code in to get my point across. Regardless, nice.
     
  14. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Uncle Kilo's back again to fix another oversight.
    Anyone who's played Sonic 1 for a long time knows about this secret platform in LZ1 to skip most of the level:
    upload_2024-4-16_0-58-33.png
    Problem: It's supposed to give the illusion of being a raft that's floating on the water's surface. However, it doesn't actually sync it's Y position to the water. That's lazy! Let's fix it.

    At Obj52_Type07 insert this line after the label
    Code (Text):
    1.         move.b    #1,$3F(a0)        ; Use $3F as an "align to water surface" flag.
    Then at both Obj52_Type02 and Obj52_Type05 insert this after the label.
    Code (Text):
    1.         tst.b    $3F(a0)                    ; Is the align to water surface flag set?
    2.         beq.s    @Cont                    ; If not, continue.
    3.         move.w    ($FFFFF646).w,$C(a0)    ; Otherwise, align to the water's surface!
    4.  
    5. @Cont:
    And ya done.
    ezgif-6-d8a727ac50.gif

    I should note that if you use object $52 with a type of 7 outside of Labyrinth it can cause issues. But I'd imagine if you're using the guide, then you're not making crazy modifications to the layouts that specifically needs this object type.
     
    Last edited: Apr 16, 2024
  15. nineko

    nineko

    I am the Holy Cat Tech Member
    6,317
    489
    63
    italy
    That's such a small detail, and yet, it does look much nicer with the wobble, great work!
     
    • Like Like x 2
    • Agree Agree x 1
    • List
  16. penBorefield

    penBorefield

    Living in my best life Member
    129
    21
    18
    Basement
    Patching things up
    Isn't that "fix" also included in Sonic Delta hack or something?
     
  17. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    422
    437
    63
    Canada
    S1 - Metal Sonic's Challenge, Sonic 1 Rev01 ASMX Disasm
    Properly properly removing the roll-jump lock
    After seeing like 3 attempts including one of my own, I think I've got the definitive version to removing the rolling-jump lock.
    First let's look at exactly what the jump code is doing, which can be found at what's commonly called Sonic_Jump.
    Code (Text):
    1.         move.b    #38/2,obj_yhitbox(a0)            ; Set Sonic's hitbox to standing.
    2.         move.b    #18/2,obj_xhitbox(a0)
    3.         btst    #STATUS_INBALL,obj_status(a0)    ; Is Sonic already in a ball?
    4.         bne.s    @RollJump                        ; If so set the roll jump lock.
    Now this is an issue, Sonic's hitbox being set to his standing height was a remnant of the prototype's victory function. However, even there doing a rolling jump would result in Sonic using his standing hitbox, here's the prototype's logic:
    Code (Text):
    1.         move.b    #38/2,obj_yhitbox(a0)            ; Set Sonic's hitbox to standing.
    2.         move.b    #18/2,obj_xhitbox(a0)
    3.         tst.b    (player_victory).w                ; Should we use the victory animation?
    4.         bne.s    @Victory                        ; If so, go off to play the victory animation.
    5.         btst    #STATUS_INBALL,obj_status(a0)    ; Is Sonic already in a ball?
    6.         bne.s    @RollJump                        ; If so set the roll jump lock.
    Then after this, it just sets Sonic to be in his ball state. And importantly lower him to the ground so he doesn't jump from his origin (Somehow Sonic Mania failed this, as well as many fangames).
    Code (Text):
    1.         move.b    #28/2,obj_yhitbox(a0)            ; Set Sonic's hitbox to ball.
    2.         move.b    #14/2,obj_xhitbox(a0)
    3.         move.b    #ANIID_ROLL,obj_anim(a0)        ; Set animation to rolling.
    4.         bset    #STATUS_INBALL,obj_status(a0)    ; Mark Sonic as in a ball.
    5.         addq.w    #5,obj_ypos(a0)                    ; Lower Sonic so he jumps from the floor rather than his origin.
    6.  
    7. @Return:
    8.         rts
    What I believe the intention was, was to simply just skip this code if you were rolling because you'd already have the ball hitbox, animation, and you'd already be aligned with the floor so no need to waste cycles on that. But due to the victory animation and it's improper removal it all got mucked up, so not only does your controls get locked, but you also end up jumping from Sonic's center mid air because now he's using his standing hitbox.
    So the way to properly fix the roll-jump lock and it's hitbox is to replace the lower part of Sonic_Jump (Just after we play the sound) with this:
    Code (Text):
    1.         btst    #STATUS_INBALL,obj_status(a0)    ; Is Sonic already in a ball?
    2.         bne.s    @Return                            ; If so, we're rolling and we don't have to do any of the below.
    3.         move.b    #28/2,obj_yhitbox(a0)            ; Set Sonic's hitbox to ball.
    4.         move.b    #14/2,obj_xhitbox(a0)
    5.         move.b    #ANIID_ROLL,obj_anim(a0)        ; Set animation to rolling.
    6.         bset    #STATUS_INBALL,obj_status(a0)    ; Mark Sonic as in a ball.
    7.         addq.w    #5,obj_ypos(a0)                    ; Lower Sonic so he jumps from the floor rather than his origin.
    8.  
    9. @Return:
    10.         rts
    That's it. No more roll-jump lock, no more standing hitbox. Then just get rid of anything involving
    #STATUS_ROLLJUMP,obj_status(a0), or #4,$22(a0) or whatever your disassembly of choice refers to it as, and you also free up a status bit for whatever purpose you may need. It looks much cleaner, doesn't leave any dead code or unnecessary checks.

    An alternative fix, if you want to keep the jump lock but fix Sonic's hitbox is to either delete the first 2 instructions that set Sonic's hitbox. Or if you have the victory animation set up then move them into the function that actually plays the animation rather than in the main jump routine.
     
    Last edited: Apr 28, 2024
  18. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,458
    93
    your mom
    Mind you, you still would need to remove the air speed cap to prevent pressing left or right from destroying your momentum when jumping after rolling at a fast speed, as shown in these GIFs:

    [​IMG] [​IMG]
    [​IMG] [​IMG]

    I am pretty sure the lock the introduced as a hack to prevent this from happening, so when removing it, you'll wanna make sure to properly fix this as well.
     
    Last edited: Apr 28, 2024
  19. Brainulator

    Brainulator

    Regular garden-variety member Member
  20. Devon

    Devon

    A̸ ̴S̴ ̵C̵ ̷E̶ ̸N̸ ̴D̶ ̵E̶ ̸D̶ Tech Member
    1,274
    1,458
    93
    your mom
    I still think the air speed cap stuff is a far more glaring and obvious thing that honestly speaks for itself. It just makes more sense to me compared to a bunch of theories from an ancient thread, which might I add, the people who made those posts were definitely not really aware of the air speed cap thing that the lock was preventing from happening.
     
    Last edited: May 3, 2024