don't click here

Some changes/fixes for Sonic 1

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

  1. Brainulator

    Brainulator

    Regular garden-variety member Member
  2. Specified that both shouldn't be too hard.

    Of note, you could also remove the checks between music/sfx and sfx/specsfx by changing sfx__First to bgm__Last+1, and spec__First to sfx__Last+1. I do plan on updating other guides (Different Songs in Sonic 1) with other, less hackish implementations.
     
    Last edited: Oct 18, 2021
  3. nineko

    nineko

    I am the Holy Cat Tech Member
    6,351
    509
    93
    italy
  4. LuigiXHero

    LuigiXHero

    Member
    45
    34
    18
    Sonic 1 Character Pak
    Sonic 1 has a bug where if you pause the game on a certain frame the water is shifted over unintentionally causing a gap at the left side, this is in all revision since it's also in all revisions of Sonic 2 (to fix it there go here):
    [​IMG]

    So in Git Sonic 1 or in Hivebrain 2021 the code is as follows in _incOBJ/1B Water Surface.asm
    Code (Text):
    1. Surf_Action:    ; Routine 2
    2.         move.w    (v_screenposx).w,d1
    3.         andi.w    #$FFE0,d1
    4.         add.w    surf_origX(a0),d1
    5.         btst    #0,(v_framebyte).w
    6.         beq.s    @even        ; branch on even frames
    7.         addi.w    #$20,d1
    8.  
    9.     @even:
    What we need to do is add a press start check between "add.w surf_origX(a0),d1" and btst #0,(v_framebyte).w like so:
    Code (Text):
    1. Surf_Action:    ; Routine 2
    2.         move.w    (v_screenposx).w,d1
    3.         andi.w    #$FFE0,d1
    4.         add.w    surf_origX(a0),d1
    5.         btst    #bitStart,(v_jpadpress1).w ; is Start button pressed?
    6.         bne.s    @even    ; if yes, branch
    7.         btst    #0,(v_framebyte).w
    8.         beq.s    @even        ; branch on even frames
    9.         addi.w    #$20,d1
    10.  
    11.     @even:
    This will fix the gap.
    For the old hivebrain disasm go to Obj1B_Action and do the same thing but worse:
    Code (Text):
    1. Obj1B_Action:                ; XREF: Obj1B_Index
    2.         move.w    ($FFFFF700).w,d1
    3.         andi.w    #$FFE0,d1
    4.         add.w    $30(a0),d1
    5.         btst    #7,($FFFFF605).w ; is Start button pressed?
    6.         bne.s    loc_11114    ; if yes, branch
    7.         btst    #0,($FFFFFE05).w
    8.         beq.s    loc_11114
    9.         addi.w    #$20,d1
    10.  
    11. loc_11114:
    and there you go. Enjoy the finer water surfaces in life.
     
  5. DeltaW

    DeltaW

    Originally a Wooloo Member
    Fix some graphical bubble glitch when drowning in Sonic 1

    I want you to take a look at this image:

    S1WOOLOOFIED000.png

    Now see where I circled the issue in pink, you may believe it's intentional, but that little graphic hangs there when you drown, which is quite unlogical. Looking at the code in a standard Hivebrain disassembly appals me:

    Code (Text):
    1. Obj0A_ChkWater:                ; XREF: Obj0A_Index
    2.         move.w    ($FFFFF646).w,d0
    3.         cmp.w    $C(a0),d0    ; has bubble reached the water surface?
    4.         bcs.s    Obj0A_Wobble    ; if not, branch
    5.         move.b    #6,$24(a0)
    6.         addq.b    #7,$1C(a0)
    7.         cmpi.b    #$D,$1C(a0) ; DW: look here
    8.         beq.s    Obj0A_Display ; DW: here
    9.         bra.s    Obj0A_Display; DW: and here

    The issue is they set a check to animation ID D for it to branch instantly to Obj0A_Display, but at the same time, you'll still be branching there, nevertheless making the code pretty redundant if you ask me. So if we check out what animation ID D does in obj0A:

    Code (Text):
    1. byte_14148:    dc.b $E, $FC

    It sets a frame that blanks out the art from loading when you drown to 0. To fix it, we would need to set a check, so the animation ID loads in so in Obj0A_ChkWater (or Drown_ChkWater for GitHub/Hivebrain 2021 users), replace:

    Code (Text):
    1.         beq.s    Obj0A_Display ; that would be "bcs.s    Drown_Display" for Git/Hive2021 users

    with this:

    Code (Text):
    1.         bcs.s    Obj0A_Display ; that would be "bcs.s    Drown_Display" for Git/Hive2021 users
    2.         move.b    #$D,$1C(a0)      ; change $1C to obAnim if you using the disassembly I mentioned above

    This makes sure it loads animation ID D well, so the art will be blank the next time you drown. From there, save, build and test it, and you should get this:

    S1WOOLOOFIED001.png

    Well done! You have done and dusted another bug in Sonic 1.
     
    Last edited: Jun 27, 2022
  6. DeltaW

    DeltaW

    Originally a Wooloo Member
    Earlier last month, I've had a conversation with vladikcomper to find the best methods to optimize Sonic 1. He came across how it is possible to optimize the BossMove subroutine, which moves the bosses. And after doing my quick analysis by playing GHZ3, I've noticed that the platforms tend to glitch for about a frame or two every time the boss moves. However, upon looking at the code, I saw how quick and easy it was to optimize the code as it shares similar footsteps to two other subroutines: SpeedToPos and ObjectMove.

    So this mini-tutorial will show you how to optimize the BossMove code in vein to S3K. It won't look fast, but it takes less processing time which matters if you intend to have custom bosses with different objects. This will work under any Sonic 1 disassembly. (Sonic 1 Git will be referenced, but it shouldn't be challenging to cross-port the code).

    For Sonic 2 users, I've made a guide for you, so please check it out here.

    Firstly, let's go and check it out:

    Code (Text):
    1. BossMove:
    2.         move.l    $30(a0),d2
    3.         move.l    $38(a0),d3
    4.         move.w    obVelX(a0),d0
    5.         ext.l    d0
    6.         asl.l    #8,d0
    7.         add.l    d0,d2
    8.         move.w    obVelY(a0),d0
    9.         ext.l    d0
    10.         asl.l    #8,d0
    11.         add.l    d0,d3
    12.         move.l    d2,$30(a0)
    13.         move.l    d3,$38(a0)
    14.         rts
    15. ; End of function BossMove
    The way I optimized it was I compared how redhotsonic shortened the subroutine and applied it here. The result you should get is this:

    Code (Text):
    1. BossMove:
    2.         move.w    obVelX(a0),d0        
    3.         ext.l    d0
    4.         lsl.l    #8,d0                
    5.         add.l    d0,$30(a0)  
    6.         move.w    obVelY(a0),d0          
    7.         ext.l    d0
    8.         lsl.l    #8,d0                
    9.         add.l    d0,$38(a0)
    10.         rts  
    11. ; End of function BossMove
    And that's it. Every time the boss moves, it will do the coding quicker in each frame.
     
  7. OrionNavattan

    OrionNavattan

    Tech Member
    174
    171
    43
    Oregon
    A simple fix that I came up with for a silly oversight in Sonic 1. In debug mode, the fans in Star Light Zone will blow around whatever object you're trying to place making it somewhat difficult to navigate. Thankfully, this is a ridiculously easy fix.

    In the GitHub disassembly, open _incObj/5D Fan.asm, and look for the label @blow:

    Code (Text):
    1.  
    2. @blow:
    3.        tst.b   fan_switch(a0)   ; is fan switched on?
    4.        bne.w   @chkdel       ; if not, branch
    5.        lea   (v_player).w,a1
    6.        move.w   obX(a1),d0
    7.        ...
    8.  
    In between the bne.w and lea, insert this:
    Code (Text):
    1.  
    2.        tst.w   (v_debuguse).w       ; is debug mode active?
    3.        bne.s   @animate               ; if yes, branch so fan physics aren't applied
    4.  
    This will skip the code that applies the fan physics to Sonic if debug placement is active, eliminating this little issue.
     
  8. Devon

    Devon

    DROWN, DROWN, DROWN MYSELF! Tech Member
    1,396
    1,690
    93
    your mom
    I haven't seen anyone else bring this up, but I thought this was a tad interesting.

    So, you might know of the change to make special stages rotate smoother, which is done by removing the "andi.b #$FC,d0" line in SS_ShowLayout that limits the rotation value. Did you know that angle value limitation is also found in Sonic's special stage object and subtley affects his physics for jumping and gravity?

    In Obj09_Jump, you can find:
    Code (Text):
    1.         move.b    (v_ssangle).w,d0
    2.         andi.b    #$FC,d0
    3.         neg.b    d0
    4.         subi.b    #$40,d0
    5.         jsr    (CalcSine).l

    Which calculates the angle to jump at. Note the "andi" instruction, that same line that limits the sprite rotation.

    You can also find a similar line here in Obj09_Fall:
    Code (Text):
    1.         move.b    (v_ssangle).w,d0
    2.         andi.b    #$FC,d0
    3.         jsr    (CalcSine).l

    Which affects how gravity is calculated. Again, that "andi" instruction is there.

    So, if you wanna enable slightly smoother jumping and gravity, comment out those "andi" lines.

    Also, if you followed this guide, then there's 2 additional instances in Obj09_JumpHeight.
     
    Last edited: Jun 24, 2022
  9. OrionNavattan

    OrionNavattan

    Tech Member
    174
    171
    43
    Oregon
    Does the "bcs.s Drown _Display" replace the "beq.s Drown _Display" in the original code? Your post isn't very clear on that.
     
  10. DeltaW

    DeltaW

    Originally a Wooloo Member
    I said to insert those lines below the second last line below to ensure the correct animation is set correctly, which worked, but after what you said, that also works. Thank you for the update.

    I updated my post to fix that little issue.
     
  11. DeltaW

    DeltaW

    Originally a Wooloo Member
    How to install UltraDMAQueue into Sonic 1 (ASM68K)

    I'm not going to go into detail technical specs of the new DMA queue besides the fact that it's heavily optimized which surpasses Sonic 2's and Sonic and Knuckles's DMA queue by a long shot. If you want to know more about what it does, click on this link. This tutorial targets the ASM68K GitHub disassembly but should work with any other disassembly if you know what you're doing. This has been tested on Hivebrain 2005 however idk about Hivebrain's modern disassemblies at this time of writing. If the guide works with that disassembly in mind, let me know. AS assemblers may follow this guide, but it's best if you use the link above instead of the one below.

    Before we get started, if you already have Sonic 2's DMA queue installed because you ported the spindash, then you'll have to replace the code with what I'm about to give you. There are a few steps that you can skip if you already changed a few lines here and there, but it's best if you double-check if you added the code in. (trust me, it's worth checking along the way because you'll have to change a few lines here and there)

    Firstly, we're going to add the DMA Queue itself. Let's create a file called DMA Queue.asm. Here's the version that'll work under ASM68K disassemblies in mind. (click me). I think it's based on a slightly older version of the DMA Queue however I'm not entirely sure at the moment.

    From there, you can save the file to your disassembly. I'll place it in the "_inc" folder in my case. The next step is to include the file in your disassembly so let's open up sonic.asm and make a line that includes the aforementioned .asm file. it doesn't matter where you put it exactly although I recommend placing it above the line that includes the Nemesis Decompression file like so:

    Code (Text):
    1.         include    "_inc\DMA Queue.asm"
    2.         include    "_inc\Nemesis Decompression.asm"
    Before we do anything else in sonic.asm, we need to understand the fact that flamewing's DMA queue is only recognized by AS assemblers and since we're dealing with an ASM68K assembler, we need to make a few changes to get the file working. If you're using an AS or Hivebrain 2022 disassembly, you can skip this section. Above your added line, insert this:

    Code (Text):
    1.     pusho    ; buffer local label symbol config
    2.     opt ws+  ; change local label symbol to '.'
    and below it:

    Code (Text):
    1.     popo    ; buffer local label symbol config    
    So you should get this:

    Code (Text):
    1.     pusho    ; buffer local label symbol config
    2.     opt ws+  ; change local label symbol to '.'
    3.         include    "_inc\DMA Queue.asm"
    4.     popo    ; buffer local label symbol config        

    The first two lines of code that we added will make sure the default labelling scheme would use dots as prefixes than @. "popo" will revert the local label to @ so no, it won't affect the rest of your disassembly.

    (AS disassembly users, come back) If you check the code, you can see that VDP_Command_Buffer and VDP_Command_Buffer_Slot aren't defined. So let's define them, shall we? =V

    Go to Variables.asm and insert this:

    Code (Text):
    1. VDP_Command_Buffer:    equ    $FFFFC800
    2. VDP_Command_Buffer_Slot:    equ    VDP_Command_Buffer+7*$12*2
    If you done this correctly, the ROM should build although we need to initialise the DMA queue and put it into good use. Firstly, let's go to GameInit and above:

    Code (Text):
    1.         bsr.w    VDPSetupGame
    insert this:

    Code (Text):
    1.         bsr.w    InitDMAQueue
    This will load the DMA queue upon startup. Next, let's replace a bunch of VBlank junk that makes the game load Sonic's sprites using a shit-ton of RAM ($200 of RAM between $FFFFC900 to $FFFFCAFF).

    Find these four lines of code:

    Code (Text):
    1.         tst.b    (f_sonframechg).w ; has Sonic's sprite changed?
    2.         beq.s    @nochg        ; if not, branch
    3.         writeVRAM    v_sgfx_buffer,$2E0,vram_sonic ; load new Sonic gfx
    4.         move.b    #0,(f_sonframechg).w
    and replace it with:

    Code (Text):
    1.         jsr     ProcessDMAQueue(pc)
    You can find these instances at VBla_08, VBla_0A, VBla_0C and VBla_16. Now let's load the DMA every time the game loads a new game mode. Inset this line:

    Code (Text):
    1.         ResetDMAQueue
    at the following:

    At GM_Sega, GM_Title, GM_Continue, GM_Credits, TryAgainEnd => insert this line under:

    Code (Text):
    1.         bsr.w    ClearScreen
    At Level_ClrVars3 and End_ClrRam3, under:

    Code (Text):
    1.         move.w    (v_hbla_hreg).w,(a6)
    And at loc_47D4 under:

    Code (Text):
    1.         jsr    Hud_Base
    Caution: if you ported Sonic 2's DMA queue (which is the code used from that spindash guide), replace these lines:
    Code (Text):
    1.         clr.w    ($FFFFC800).w
    2.         move.l    #$FFFFC800,($FFFFC8FC).w
    with ResetDMAQueue. They can be found at loc_47D4 and Level_ClrVars3.

    Finally, let's make the game load Sonic's art via DMA. Let's head over to "_incObj/Sonic LoadGfx.asm" and replace everything with this:

    Code (Text):
    1.  
    2. ; ---------------------------------------------------------------------------
    3. ; Sonic    graphics loading subroutine
    4. ; ---------------------------------------------------------------------------
    5.  
    6. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    7.  
    8.  
    9. Sonic_LoadGfx:                ; XREF: Obj01_Control; et al
    10.         moveq    #0,d0
    11.         move.b    obFrame(a0),d0    ; load frame number
    12.         cmp.b    (v_sonframenum).w,d0 ; has frame changed?
    13.         beq.s    @nochange    ; if not, branch
    14.  
    15.         move.b    d0,(v_sonframenum).w
    16.         lea    (SonicDynPLC).l,a2 ; load PLC script
    17.         add.w    d0,d0
    18.         adda.w    (a2,d0.w),a2
    19.         moveq    #0,d5
    20.         move.b    (a2)+,d5    ; read "number of entries" value
    21.         subq.w    #1,d5
    22.         bmi.s    @nochange    ; if zero, branch
    23.         move.w    #$F000,d4
    24.         move.l    #Art_Sonic,d6
    25.  
    26. @readentry:
    27.         moveq    #0,d1
    28.         move.b    (a2)+,d1
    29.         lsl.w    #8,d1
    30.         move.b    (a2)+,d1
    31.         move.w    d1,d3
    32.         lsr.w    #8,d3
    33.         andi.w    #$F0,d3
    34.         addi.w    #$10,d3
    35.         andi.w    #$FFF,d1
    36.         lsl.l    #5,d1
    37.         add.l    d6,d1
    38.         move.w    d4,d2
    39.         add.w    d3,d4
    40.         add.w    d3,d4
    41.         jsr    (QueueDMATransfer).l
    42.         dbf    d5,@readentry    ; repeat for number of entries
    43.  
    44. @nochange:
    45.         rts
    46.  
    47. ; End of function Sonic_LoadGfx
    48.  
    49.  

    From there, you can save, build and test your hack and it should load just fine. If there are any questions related to this, then please let me know.
     

    Attached Files:

    Last edited: Apr 4, 2023
  12. Brainulator

    Brainulator

    Regular garden-variety member Member
    Did you mean this?
    Code (Text):
    1.     pusho    ; buffer local label symbol config
    2.     opt l+  ; change local label symbol to '.'
    3.         include    "_inc\DMA Queue.asm"
    4.     popo    ; buffer local label symbol config        
    What opt ws+ does is allow whitespace in operands, according to the ASM68K manual.
     
  13. DeltaW

    DeltaW

    Originally a Wooloo Member
    I avoided using that for a reason as I received errors related to
    Code (Text):
    1. movep.l d1,c
    being a illegal addressing mode
     
  14. OrionNavattan

    OrionNavattan

    Tech Member
    174
    171
    43
    Oregon
    Small clarification, "opt l+" would chance the local label symbol to a plus sign; for a period, you would want "opt l.".

    I think removing the whitespace after the 'c' in that one instruction would resolve the issue? Anyhow, not changing the local label symbol with "opt l." isn't a good idea, as ASM68K will treat all of the labels in in the queue code as global labels. It'd probably be better to change all of the local label symbols to "@", the default symbol and the one used in Sonic 1 ASM68K.

    Regarding compatibility with Hivebrain's modern disassembly, it has "opt l. and opt ws+" set by default, so there shouldn't be any need to change the options. (Same applies to my Sonic 2 disasm.)
     
    • Informative Informative x 1
    • List
  15. DeltaW

    DeltaW

    Originally a Wooloo Member
    Thanks for the info. I was a little skeptical when writing this seeing as I've mainly learnt how 'opt' mainly handles local symbol syntaxes (And I didn't want to change every label to @ seeing as I know for a fact that it wouldn't work given the code structure for the DMA queue being much more suited to AS assemblers than ASM68K). I only added 'opt ws+' seeing as it was a safety measure given that there'd be a lot of white spaces I had to deal with (and eventually got too lazy and just not altered every line to remove it :p)

    I'll test your theory out when I have time and if successful, will update the guide alongside adding extra notes for the Hivebrain 2022 disassembly. :)
     
    Last edited: Mar 25, 2023
  16. OrionNavattan

    OrionNavattan

    Tech Member
    174
    171
    43
    Oregon
    You're welcome. :>

    FWIW, I went ahead and made my own ASM68K port of Flamewing's DMA queue to get rid of any issues with compatibility. It has a bunch of label changes to target my own Sonic 2, but should be portable to Sonic 1 HB 2022 (but NOT Sonic 1 Squared, as that has a custom DMA queue of its own).
     

    Attached Files:

  17. Brainulator

    Brainulator

    Regular garden-variety member Member
    Not according to the aforementioned manual (page 132 of PDF):
    I've tried this for myself, and L+ does result in periods being used, not plus signs.
     
  18. OrionNavattan

    OrionNavattan

    Tech Member
    174
    171
    43
    Oregon
    Ah. That's a feature I was unaware of. xD
     
  19. Devon

    Devon

    DROWN, DROWN, DROWN MYSELF! Tech Member
    1,396
    1,690
    93
    your mom
    There is a possible slight oversight with how the push sensor is positioned. So, if Sonic is moving along a flat enough ground (not a wall or ceiling), then Sonic's push sensor will be moved down 8 pixels. This is so snapping on top of steeper steps can only really be done is you're walking up a slope, and not on flat ground, which would look strange.

    [​IMG]

    The problem is that this lowering of the push sensor doesn't account for when Sonic's smaller hitbox when he starts rolling. As a result, the threshold for the largest step that Sonic can snap on top of lowers.

    [​IMG]

    This is particularly not ideal, because it increases the likely hood of Sonic getting stopped by a decently shallow slope while rolling, due to how low that threshold is.

    [​IMG]

    If you want the push sensor's position to be consistent with the normal hitbox height when on flat ground, the fix is easy.

    In sonic.asm, go to loc_14D24, and add this right under addq.w #8,d2:
    Code (Text):
    1.         btst    #2,obStatus(a0)    ; Is Sonic rolling?
    2.         beq.s   loc_14D3C          ; If not, branch
    3.         subq.w  #5,d2              ; If so, move push sensor up a bit

    And you'll get this:

    [​IMG]

    You can adjust the numbers to your liking if you please. I will also note that if you make it so that the push sensor is enabled for other directions (like S3K does when running straight up/down on a wall or running on a flat ceiling), then modifications to account for that will need to be made.
     
    Last edited: Aug 10, 2023
    • Like Like x 5
    • Useful Useful x 3
    • Informative Informative x 1
    • List
  20. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,237
    491
    63
    Japan
    This is really interesting stuff, I look forward to further fixes involving collision~