Some changes and fixes for Sonic 2

Discussion in 'Engineering & Reverse Engineering' started by Esrael, Jun 7, 2012.

  1. flamewing

    flamewing

    Emerald Hunter Tech Member
    1,138
    0
    16
    France
    Sonic Classic Heroes; Sonic 2 Special Stage Editor; Sonic 3&K Heroes (on hold)
    The second bit of code did its job correctly in the case of even logs only: subtype would be always even, so multiplying by 3 would be enough to get the correct offset. When you edited to allow an odd number of logs you broke that assumption.

    The low bit of subtype does not seem to be used anywhere, so there should not be any issues.
     
  2. Novedicus

    Novedicus

    Peace is better than chicken and rice Tech Member
    338
    2
    18
    Assville, Shittucky
    Dr. Yundong
    So, a small update. So, with the new calculation I added is actually incorrect. The multiplication by 3 is intentional, since 2 segments is supposed to use sub3_x_pos and 4 segments use sub4_x_pos, etc, rather than 2 using sub4_x_pos, 4 using sub6_x_pos, etc. Here's a fixed version of it that handles odd numbered segments, by rounding down the offset:
    [68k]
    move.w d4,d0
    add.w d0,d0
    add.w d4,d0 ; d0*3
    btst #0,d0 ; align the offset for odd numbered segments
    beq.s .getX
    subq.w #3,d0
    .getX:
    move.w sub2_x_pos(a1,d0.w),d0
    [/68k]
     
  3. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    MCZ boss fix
    This is a correction of sorts for this guide.

    In particular, I found the actual cause for Eggman not laughing unless he hits AI Tails... but even that bit's not correct. He doesn't only laugh if he hits AI Tails, he only laughs if he hits a character, and they don't drop any rings. This means in Sonic Alone or Tails Alone, if you have a shield, getting hit by Eggman will actually make him laugh.

    That's not just it though, I also found what causes it, which should make a chunk of that above guide pointless:

    Eggman detects when he's hit Sonic or Tails based on the boss_hurt_sonic SST flag. So why does this not get set most of the time? Well, as BossCollision_MCZ would tell you, the pointer to boss_hurt_sonic is stored in a1... which gets overwritten by Boss_DoCollision, but only if the player drops rings. This is because, long down the chain of code, Boss_DoCollision eventually reaches HurtCharacter, where the 'dropped rings' object is loaded in a1.

    So how should that be fixed? Well, BossCollision_MCZ already saves d7 on the stack, so just do the same with a1, preferably with a 'movem'.

    End-of-level fix
    I've known about this bug for a long time, but because I like it so much I never actually tried fixing it. When you hit the signpost, you have a few seconds to run around before the game locks your controls and makes you run off to the right. However, if you jump while at the right edge of the screen, just as the controls are about to lock, the lock won't happen, and you're free to walk around during the score tally. It sure made it easier to get the hidden bonus points in S1.

    The code you want to look for is Obj0D_Main_State3. Here, you can see the code that locks Sonic's controls... which is skipped over if the character is in the air. No, I don't mean the function returns, I mean just that bit of code is skipped. The game still checks if Sonic has reached the right side of the screen, and, if so, triggers the score tally. Thus, if you're in the air, and also on the right side of the screen, you can avoid having your controls lock.

    I'd say the reason the code is a weird mess like this is because of Sonic 1, where you had to take the Big Ring into account: Sonic disappears when he goes into the big ring, so of course he'll never touch the ground, and it makes sense to just keep going... but still, this edgecase creeps through. I believe the correct way for this code to work would have been this:

    Code (ASM):
    1. ; loc_19418:
    2. Obj0D_Main_State3:
    3.     tst.w   (Debug_placement_mode).w
    4.     bne.w   return_194D0
    5.     ; This check here is for S1's Big Ring, which would set Sonic's Object ID to 0
    6.     tst.b   (MainCharacter+id).w
    7.     beq.s   loc_1944C
    8.     btst    #1,(MainCharacter+status).w
    9.     bne.w   return_194D0
    10.     move.b  #1,(Control_Locked).w
    11.     move.w  #(button_right_mask<<8)|0,(Ctrl_1_Logical).w
    12.     move.w  (MainCharacter+x_pos).w,d0
    13.     move.w  (Camera_Max_X_pos).w,d1
    14.     addi.w  #$128,d1
    15.     cmp.w   d1,d0
    16.     blo.w   return_194D0
    17.  
    18. loc_1944C:
    Here, the in-air check causes the function to terminate, and it's been moved to after the check for if Sonic entered the big ring. This should solve this issue.

    Super Sonic transformation fix
    I've noticed that if you transform into Super Sonic by performing a roll-jump, Sonic's controls will be locked until he lands. To me, this doesn't make any sense since Sonic is in his walking animation, not his rolling one, so the roll-jump logic shouldn't apply anymore.

    Anyway, looking around the transformation code, I noticed that it doesn't look like anything is done to take Sonic out of his 'curled up into a ball' state, even though his sprite clearly shows him walking. Here are a few things that shouldn't be:
    • Sonic's controls remain locked if he roll-jumped
    • Sonic's 'in ball' status bit won't be cleared
    • Sonic will still be using his smaller hitbox

    These all suck, so let's fix them.

    The solution is simple enough: just stick this blob of code above Sonic_CheckGoSuper's write to Super_Sonic_palette.

    Code (ASM):
    1.     bclr    #2,status(a0)
    2.     bclr    #4,status(a0)
    3.     move.b  #$13,y_radius(a0)
    4.     move.b  #9,x_radius(a0)
     
  4. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Another day, another stupid bug...

    The limit on Blocks in Sonic 2 is 0x300... but ARZ has 0x320. Those last 0x20 are completely unused, so go ahead and delete them.

    The problem with the level having too many blocks is that, when it gets decompressed to RAM, it overwrites whatever comes after the buffer. In the vanilla game, this is TempArray_LayerDef. Another big problem with this is that it causes SonLVL to make 'ARZ primary 16x16 collision index.bin' and 'ARZ secondary 16x16 collision index.bin' larger than they should be - 0x320 bytes intead of 0x300 - because it's trying to account for every block. This one's slightly more dangerous, as Secondary_Collision will overflow into VDP_Command_Buffer, which can have disasterous results when using the likes of flamewing's DMA queue, causing the game to explode into a garbled mess upon trying to load the level.
     
  5. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    I know for a fact flamewing posted a fix for this before, but it's nowhere in this thread. It must have been the Q&A thread, I guess.

    In the Level Select, when you go from highlighting one zone to another, the new icon briefly shows the wrong colours. The issue here is that the icon itself is loaded right after V-Int, but the palette only updates on the *next* V-Int. This gives the VDP time to draw the new icon before the palette gets a chance to load.

    If I remember right, flamewing suggested two fixes: transfer the icon data (actually its mappings) via DMA, or update the palette manually, instead of waiting for V-Int to do it. Transferring the mappings via DMA will sync it with V-Int, just like the palette, and manually transferring the palette with sync it with the code, just like the icon. I think the latter option is a simpler change to make, so that's the one I'll be using.

    In 'LevelSelect_DrawIcon', add this line before the loop at the end:

    Code (ASM):
    1. move.l  #vdpComm(2*16*2,CRAM,WRITE),VDP_control_port-VDP_data_port(a6)
    Also, before the 'move.l (a1)+,(a2)+' instruction (but after the loop's label), add this:

    Code (ASM):
    1. move.l  (a1),(a6)
    And that's it.
     
  6. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Ever notice how rings disappear while still onscreen if you move them offscreen slightly, but only off the top of the screen? I sure didn't until my last playthrough. Anyhow, it looks like this was fixed in S3K, so let's just do what that does.

    The code that causes this is under BuildRings:

    Code (ASM):
    1.     move.w  4(a0),d2    ; get ring Y pos
    2.     sub.w   4(a3),d2    ; subtract camera Y pos
    3.     andi.w  #$7FF,d2
    4.     addi_.w #8,d2
    5.     bmi.s   BuildRings_NextRing ; dunno how this check is supposed to work
    6.     cmpi.w  #240,d2
    7.     bge.s   BuildRings_NextRing ; if the ring is not on-screen, branch
    What's important to note is how d2 is AND'd, and then 8 is added to it. The reason this is a problem is, consider that the ring is slightly offscreen: d2 will be $FFFF or so, which, after ANDing, becomes $7FF. Now, when adding 8 to it, it won't wrap back around to $0007, but $0807. It's also worth noting that the 'bmi' there is broken, also because of the position of this AND.

    That said, the solution is pretty obvious: just move the 'andi' to before the 'cmpi' instead.

    EDIT: Bonus change - Rings don't shake with the rest of the screen in places like the HTZ earthquake sequencies. If you want to correct this (like S3K looks like it did), just change the 'lea (Camera_X_pos).w,a3' to 'lea (Camera_X_pos_copy).w,a3'.
     
  7. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    ...It's seriously been a year? Anyway, there's this one really annoying bug where if you enter Debug Mode underwater, and then exit it above-water, Sonic will still have his underwater physics.

    This is because Sonic will only trigger his 'exiting water' code if he's above the water's Y-coordinate and his 'is underwater' flag is set. The problem is, when Sonic exits Debug Mode, it clears his 'is underwater' flag. This happens in sub_41CB8:

    Code (ASM):
    1.  
    2. ; sub_41CB8:
    3. Debug_ResetPlayerStats:
    4.     move.b  d0,anim(a1)
    5.     move.w  d0,2+x_pos(a1) ; subpixel x
    6.     move.w  d0,2+y_pos(a1) ; subpixel y
    7.     move.b  d0,obj_control(a1)
    8.     move.b  d0,spindash_flag(a1)
    9.     move.w  d0,x_vel(a1)
    10.     move.w  d0,y_vel(a1)
    11.     move.w  d0,inertia(a1)
    12.     ; note: this resets the 'is underwater' flag, causing the bug where
    13.     ; if you enter Debug Mode underwater, and exit it above-water, Sonic
    14.     ; will still move as if he's underwater
    15.     move.b  #2,status(a1)
    16.     move.b  #2,routine(a1)
    17.     move.b  #0,routine_secondary(a1)
    18.     rts
    19. ; End of function Debug_ResetPlayerStats
    20.  
    As the comment points out, it's that 'move.b #2,status(a1)' line. To fix the bug, the 6th bit of Sonic's status needs to be preserved, like this:

    Code (ASM):
    1.  
    2.     andi.b  #1<<6,status(a1)
    3.     ori.b   #2,status(a1)
    4.  
    Funnily enough, this bug doesn't exist in Sonic 1 since Debug Mode resets way fewer variables.
     
  8. biggestsonicfan

    biggestsonicfan

    Model2wannaB Oldbie
    948
    4
    18
    Formerly Sonic the Fighters
    Since I just found this thread, can this be converted into a GameGenie code for use on hardware? I have an itching to try it out but I've never compiled S2 from scratch and feel that the time and effort spent would decrease my overall enjoyment levels.

    I plan to go through this whole thread because it's absolutely fascinating. I'd love to do stuff to Sonic the Fighters like this once I understand it a little better (and I finish my decompile).

    EDIT1: Actually it would make more sense to have it implemented as Esrael had it, but the download link is broken. :argh:
     
  9. Wafer

    Wafer

    Member Member
    33
    0
    6
    Disassembling n00bs, WIP
    Honestly, don't be intimidated by the prospect of building from assembly. It's just downloading the source from Github and running build.bat. You could do it in like 5 minutes, and then you'll be able to look at doing other fixes yourself, and maybe move onto your own ideas.
     
  10. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    This vine switch.

    [​IMG]

    I'm sure some of you know exactly what bug I'm about to describe. Normally, when you press a button to release yourself from a vine, you jump. But not this one. If you try to jump off too soon, you drop like a rock.

    So why? Well, believe it or not, the issue isn't with the vine object, but that drawbridge next to it.

    This object's interesting: it uses the multi-sprite system to draw all of its sprites using only one object. Sonic 1 lacked this feature, meaning the bridges in GHZ are actually multiple objects, one for each segment.

    The multi-sprite system works by turning most of the object's RAM into an array of sprite metadata. But the drawbridge object needs that RAM for other things. Its solution? Make the bridge use two objects instead: one master object that controls everything, and one sprite-mule object whose only purpose is to store sprite data.

    So if the drawbridge is made up of multiple sprites, does that mean its collision is made up of numerous small hitboxes as well?

    No. The object only uses one hitbox. Depending on what angle the drawbridge is pointing, the hitbox is either a thin vertical box, or a thin horizontal one. This means the drawbridge's appearance doesn't reflect its hitbox at all: if the bridge is at 45 degress, the hitbox will actually behave as if it's at 0.

    So when does the hitbox swap? Well, it's set according to this code...

    Code (ASM):
    1. loc_2A18A:
    2.     move.w  #$13,d1 ; width
    3.     move.w  #$40,d2 ; height
    4.     move.w  #$41,d3 ; other height
    5.     move.b  angle(a0),d0
    6.     beq.s   loc_2A1A8
    7.     cmpi.b  #$40,d0
    8.     beq.s   loc_2A1B4
    9.     cmpi.b  #-$40,d0
    10.     bhs.s   loc_2A1B4
    11.  
    12. loc_2A1A8:
    13.     move.w  #$4B,d1 ; width
    14.     move.w  #8,d2   ; height
    15.     move.w  #9,d3   ; other height
    16.  
    17. loc_2A1B4:
    angle(a0) is... the angle, only the range is 0-256 instead of 0-360, so $40 is 90 and -$40 ($C0) is 270.

    At 0 degrees, the bridge is pointing to the right, at 90, it's pointing down. So, if the bridge is pointing straight down, or between up and right, it uses its vertical hitbox. Otherwise, it uses its horizontal one.

    The problematic drawbridge spawns pointing down, and moves to point right after you pull the switch. So, in effect, the moment the bridge moves just one degree, it should change its hitbox, basically allowing you to walk straight through the drawbridge if you're fast enough.

    So where is this hitbox? It actually belongs to the invisible master object, so the hitbox should be whereever that is. But it's invisible... at least until I do a little hackery. So I change that, and...

    [​IMG]

    Oh dear.

    Like I said, the moment the drawbridge starts moving, the hitbox switches from a tall one to a wide one, but the object where the hitbox applies to is still in the tall position. That's why you drop like a rock: the vine object releases you, only for the game to realise you're embedded in a solid object's hitbox, and push you down.

    It's not until the drawbridge finishes moving that the master object updates its position, and applies collision to the correct area. The reason it waits is because of a different drawbridge, which spawns pointing up, and points right after you pull the switch. In that case, if the master object moves right away, then you'd be able to walk on an invisible bridge before the real one even gets there.

    Oddly enough, this doesn't apply to the bridges that point up and move to the left instead: those change collision instantly. Or at least, they try to. You know what bridge points up, moves left, and has this same dropping-like-a-rock bug? The one above The Pit. But yeah, this one actually does change its collision and position immediately, but for some reason it only updates the object's X coordinate, meaning the hitbox still floats in the air, ready to bump Sonic's head. It doesn't even set the object's collision width properly, meaning even if it was positioned properly, Sonic would still fall through it if he tried walking on it.

    This object really does feel broken and unfinished. Why would the up-to-left version change collision right away, but not the up-to-right one?

    Here's my best attempt at fixing the "intended" behaviour:

    Go to Obj81_BridgeUp, and replace this:

    Code (ASM):
    1.     cmpi.b  #$81,status(a0)
    2.     bne.s   +
    3.     move.w  objoff_30(a0),x_pos(a0)
    4.     subi.w  #$48,x_pos(a0)
    5. +
    With this:

    Code (ASM):
    1.     move.b  status(a0),d0
    2.     andi.b  #3,d0
    3.     beq.s   +
    4.     move.b  #$40,width_pixels(a0)
    5.     move.w  objoff_32(a0),y_pos(a0)
    6.     move.w  objoff_30(a0),x_pos(a0)
    7.     moveq   #$48,d0
    8.     btst    #1,status(a0)
    9.     beq.s   .notyflipped
    10.     neg.w   d0
    11. .notyflipped:
    12.     btst    #0,status(a0)
    13.     beq.s   .notxflipped
    14.     neg.w   d0
    15. .notxflipped:
    16.     add.w   d0,x_pos(a0)
    17. +
    But yeah, I don't think this behaviour's right at all. Being able to walk on a bridge that isn't there yet just doesn't feel right. Meanwhile, being blocked by an upside-down bridge that isn't even in the way isn't right either. So in my opinion, the normal bridges shouldn't change collision instantly, but the upside-down ones should.

    To correct this, replace the above block of code with this instead:

    Code (ASM):
    1.     btst    #1,status(a0)
    2.     beq.s   +
    3.     move.b  #$40,width_pixels(a0)
    4.     move.w  objoff_32(a0),y_pos(a0)
    5.     move.w  objoff_30(a0),x_pos(a0)
    6.     moveq   #$48,d0
    7.     btst    #0,status(a0)
    8.     beq.s   .notxflipped
    9.     neg.w   d0
    10. .notxflipped:
    11.     add.w   d0,x_pos(a0)
    12. +
    This changes the master object repositioning. To change the hitbox-swapping, go to loc_2A18A and replace this:

    Code (ASM):
    1.     move.b  angle(a0),d0
    2.     beq.s   loc_2A1A8
    3.     cmpi.b  #$40,d0
    4.     beq.s   loc_2A1B4
    5.     cmpi.b  #-$40,d0
    6.     bhs.s   loc_2A1B4
    With this:

    Code (ASM):
    1.     move.b  angle(a0),d0
    2.     cmpi.b  #$40,d0
    3.     beq.s   loc_2A1B4   ; Straight down
    4.     cmpi.b  #$80,d0
    5.     bhi.s   loc_2A1B4   ; Anywhere upwards between left and right
     
  11. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Fix 1

    Thanks to djohe for pointing this out. The 'X rings to go' prompt is bugged in the Special Stage: if you need 101 or more rings, the prompt will show the wrong number.

    The cause is a buggy BCD converter, found just above loc_3577A:

    Code (ASM):
    1.     moveq   #0,d0
    2.     cmpi.w  #100,d1
    3.     blt.s   +
    4.  
    5. -   addi.w  #$100,d0
    6.     subi.w  #100,d1
    7.     bgt.s   -
    8. +
    The correct version goes like this:

    Code (ASM):
    1.     moveq   #0,d0
    2.     cmpi.w  #100,d1
    3.     blt.s   +
    4.  
    5. -   addi.w  #$100,d0
    6.     subi.w  #100,d1
    7.     cmpi.w  #100,d1
    8.     bge.s   -
    9. +
    How about another bug?

    Fix 2

    At the very start of ARZ, near the first Grounder that's hiding in a wall, the water is shallow but just deep enough for Sonic to drown in it. However, if you do start drowning there, the countdown numbers are corrupted.

    Obviously it has something to do with the number reaching the surface of the water, but for the life of me I couldn't find what the actual cause was. On a whim, I decided to document the S3K version of the object, to find any differences that may contain a fix. Surprisingly, it did.

    For starters, the fix involves splitting Obj0A_DisplayNumber and Obj0A_Display, shown below for reference:

    Code (ASM):
    1. ; loc_1D40E:
    2. Obj0A_DisplayNumber:
    3.     movea.l objoff_3C(a0),a2 ; a2=character
    4.     cmpi.b  #$C,air_left(a2)
    5.     bhi.s   JmpTo5_DeleteObject
    6.  
    7. ; loc_1D41A:
    8. Obj0A_Display:
    9.     bsr.s   Obj0A_ShowNumber
    10.     lea (Ani_obj0A).l,a1
    11.     jsr (AnimateSprite).l
    12.     jmp (DisplaySprite).l
    What's interesting about Obj0A is that it has two Display functions. Here's the other one:

    Code (ASM):
    1. ; loc_1D452:
    2. Obj0A_Display2:
    3.     lea (Ani_obj0A).l,a1
    4.     jsr (AnimateSprite).l
    5.     bsr.w   Obj0A_LoadCountdownArt
    6.     tst.b   render_flags(a0)
    7.     bpl.s   JmpTo6_DeleteObject
    8.     jmp (DisplaySprite).l
    As you might guess, the fix involves that call to Obj0A_LoadCountdownArt.

    What S3K does is split Obj0A_DisplayNumber and Obj0A_Display, and add a call to Obj0A_LoadCountdownArt to Obj0A_Display, like this:

    Code (ASM):
    1. ; loc_1D40E:
    2. Obj0A_DisplayNumber:
    3.     movea.l objoff_3C(a0),a2 ; a2=character
    4.     cmpi.b  #$C,air_left(a2)
    5.     bhi.s   JmpTo5_DeleteObject
    6.     bsr.s   Obj0A_ShowNumber
    7.     lea (Ani_obj0A).l,a1
    8.     jsr (AnimateSprite).l
    9.     jmp (DisplaySprite).l
    10. ; ===========================================================================
    11.  
    12. ; loc_1D41A:
    13. Obj0A_Display:
    14.     bsr.s   Obj0A_ShowNumber
    15.     lea (Ani_obj0A).l,a1
    16.     jsr (AnimateSprite).l
    17.     bsr.w   Obj0A_LoadCountdownArt
    18.     jmp (DisplaySprite).l
    And that's it. I did some reading, to figure out what was going on, and as it turns out, when the bubble reaches the surface of the water, it switches from Display2 to Display1. Since Display1 doesn't update the art, the number corrupts. Kind of simple in hindsight.

    How about a third fix?

    Fix 3

    If you die in ARZ, chances are you'll see a bunch of question mark sprites appear. This is the actually the sprite for the invisible hitbox that makes those falling leaves appear.

    This is caused by some quirky behaviour: when you die, the game pauses. But it doesn't actually pause: it just stops updating most of the objects. But it still needs to draw those objects, and objects need to update to display themselves. So the game does something weird: it displays every object on its own. But this is where the root of the problem is: the game is displaying an object that isn't meant to be displayed.

    So how does the game determine what objects should be displayed? Well, it's actually quite a bit more clever than I was expecting. I was expecting it to check if the object's mappings pointer was set. That would explain why it shows the leaves spawner. But no. There are actually other invisible objects in the game that do the same thing, without this problem: take for example the invisible solid block object, or the MTZ wall spring object (the sprites are actually part of the level). They don't display when you die at all.

    So how does it actually determine what to show? Well, it checks the high-bit of the object's render_flags. This bit is set by BuildSprites if the object is on-screen. Here's the catch: BuildSprites is only called if the object displays. Since the invisible objects never display, this makes sense.

    So then what's the leaves spawner doing differently to the others?

    Code (ASM):
    1. Obj74_Init:
    2.     [...]
    3.     ori.b   #4,render_flags(a0)
    Code (ASM):
    1. Obj66_Init:
    2.     [...]
    3.     ori.b   #4,render_flags(a0)
    Code (ASM):
    1. Obj2C_Init:
    2.     [...]
    3.     move.b  #$84,render_flags(a0)
    Oops.

    So the object accidentally sets that bit when it spawns, meaning when Sonic dies, the game notices it, assumes that means the object has displayed before, and draws it.

    So how do we fix it? Well, the obvious answer is to just change the above line to match the others. And that'd be the correct answer too.

    But not to Sonic Team. Check out Obj31, for its own dumb fix: this is another object that accidentally sets that bit, but instead of fixing that, Sonic Team just did this:

    Code (ASM):
    1.     move.l  #Obj31_MapUnc_20E6C,mappings(a0)
    2.     tst.w   (Debug_placement_mode).w
    3.     beq.s   +
    4.     move.l  #Obj31_MapUnc_20E74,mappings(a0)
    5. +
    Obj31_MapUnc_20E6C is actually blank, so nothing displays. This means you'll only be able to see this object in Debug Mode if it spawns while you're in Debug Mode.

    Hey, how about a fourth fix?

    Fix 4

    Even though the leaves spawner has everything set up so it can display, it actually doesn't appear in Debug Mode. That's right: dying is the only way to make it appear. The problem is that the object never calls DisplaySprite, even in Debug Mode, so the fix is pretty straightforward:

    Just go to Obj2C_Main, and under 'bhi.w JmpTo29_DeleteObject', add this:

    Code (ASM):
    1.     tst.w   (Debug_placement_mode).w
    2.     beq.s   +
    3.     jsr (DisplaySprite).l
    4. +
    Whew, I think that's enough fixes for one day.
     
  12. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Sod it, here's another one. Once again, thanks to djohe for pointing it out: in 2-player mode, there's an option to replace all monitors with either teleport monitors or random monitors. Yet, no matter which option you choose, the monitors will still show their random icon. Fixing this one's simple - just add this code above Obj26_Main:

    Code (ASM):
    1.     tst.w   (Two_player_items).w    ; are monitors set to 'teleport only'?
    2.     beq.s   Obj26_Main      ; if not, branch
    3.     subq.b  #1,anim(a0)     ; use teleport icon
     
  13. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Here's a fun one: if you get caught by a Grabber while you're charging a spin dash, Sonic/Tails won't actually leave their spin dash state: the dust graphic will still appear, just floating in the air, and when you touch the ground, you'll dash off.

    To fix this, just go to loc_38EEE and add a 'clr.b spindash_flag(a1)` with the other instructions that use a1.
     
  14. Clownacy

    Clownacy

    Tech Members Tech Member
    775
    0
    16
    Here's a dumb one. I found it by complete accident while reading the code.

    When you get a Game Over, the game stops letting you pause. This also happens in 2-player... but only when Sonic gets the Game Over. If Tails gets it, the game will still let you pause.

    Even weirder, getting a Time Over won't stop you from pausing either, even if it's Sonic that gets it.

    To fix this, just add these lines before the check for Game_paused at the start of PauseGame:

    Code (ASM):
    1.     tst.b   (Life_count_2P).w
    2.     beq.w   Unpause
    3.     tst.b   (Time_Over_flag).w
    4.     bne.w   Unpause
    5.     tst.b   (Time_Over_flag_2P).w
    6.     bne.w   Unpause
     
  15. Advanced?

    Advanced?

    Member Member
    15
    0
    0
    Sonic 1 Hack
    Wow, five month bump. These are just various guides.

    Sonic CD Style Roll-Jump

    In Sonic 1, 2 and 3K, jumping after rolling won't let the player control their direction. But in Sonic CD, the player can.

    Now, onto the guide. Firstly, at Sonic_ChgJumpDir, you'll see this code.

    [68k]; loc_1A8E8:
    Sonic_ChgJumpDir:
    move.w (Sonic_top_speed).w,d6
    move.w (Sonic_acceleration).w,d5
    asl.w #1,d5
    btst #4,status(a0) ; did Sonic jump from rolling? <--
    bne.s Obj01_Jump_ResetScr ; if yes, branch to skip midair control <--
    etc...[/68k]

    See those two lines highlighted? Delete them. Then at Tails_ChgJumpDir, you'll see the same code, just with different pointers. Delete them too.

    Now we might've done it, but we might as-well free-up a status. Long story short, search #4,status and delete them, except for ones at ObjC5 and ObjC7.

    Retain Rings Between Acts

    At Level_ClrHUD, you'll see this line.

    [68k] move.w d0,(Ring_count).w ; clear rings[/68k]

    Delete it, then, at KillCharacter, you'll see this line:

    [68k] clr.b status_secondary(a0)[/68k]

    Insert the line you just deleted after this.

    Fix Debug Mode Crash

    Now you're probably thinking, but Esrael already fixed this! Well, yes, but here's a quicker method.

    At BuildSprites_ObjLoop, you'll see this block of code.

    [68k] if gameRevision=0
    ; the additional check prevents a crash triggered by placing an object in debug mode while dead
    ; unfortunately, the code it branches *to* causes a crash of its own
    tst.b id(a0) ; is this object slot occupied?
    beq.w BuildSprites_Unknown ; if not, branch
    tst.l mappings(a0) ; does this object have any mappings?
    beq.w BuildSprites_Unknown ; if not, branch
    else
    ; REV01 uses a better branch, but removed the useful check
    tst.b id(a0) ; is this object slot occupied?
    beq.w BuildSprites_NextObj ; if not, check next one
    endif[/68k]

    Change it to this.

    [68k] tst.b id(a0) ; is this object slot occupied?
    beq.w BuildSprites_NextObj ; if not, check next one
    tst.l mappings(a0) ; does this object have any mappings?
    beq.w BuildSprites_NextObj ; if not, check next one[/68k]

    This crash happens due to a strange command in BuildSprites_Unknonw, this was a simple "JmpTo" command in the Simon Wai Prototype. But later versions added this line.

    [68k] move.w (1).w,d0[/68k]

    This causes the game to read off an odd address, as the Genesis can't do that, the game crashes. REV01 tries to fix this, by using a better branch, but removes a useful check.

    Fix Super Sonic in 2 Player Mode

    To fix this, at MenuScreen_LevSel2P:, add this line below clr.w (Game_Over_2P).w

    [68k] clr.b (Emerald_Count).w[/68k]

    Fix Erratic Left Wall Collision

    Colliding with left walls can cause some weird stuff sometimes. Here's how you fix it.

    At ObjCheckLeftWallDist, add this line below move.w y_pos(a0),d2.

    [68k] eori.w #$F,d3[/68k]

    Fix Lives Monitor

    If you're wondering what I'm talking about, if you break a 1up monitor while having 99 lives, the lives counter will glitch out.

    To fix this, change sonic_1up to this:

    [68k]sonic_1up:
    addq.w #1,(Monitors_Broken).w
    addq.b #1,(Life_count).w
    cmpi.w #99,(Life_count).w ; does Sonic have 99 or more lives?
    bhs.s + ; if yes, branch
    addq.b #1,(Update_HUD_lives).w
    move.w #MusID_ExtraLife,d0
    jsr (PlayMusic).l ; Play extra life music
    +
    rts[/68k]

    Then change tails_1up to this:

    [68k]tails_1up:
    addq.w #1,(Monitors_Broken_2P).w
    addq.b #1,(Life_count_2P).w
    cmpi.w #99,(Life_count).w ; does Sonic have 99 or more lives?
    bhs.s + ; if yes, branch
    addq.b #1,(Update_HUD_lives_2P).w
    move.w #MusID_ExtraLife,d0
    jsr (PlayMusic).l ; Play extra life music
    +
    rts[/68k]

    This also prevents the rings moniter giving you 100+ lives.

    Restore Path Swappers in Debug Mode

    Earlier builds of Sonic 2 used to include a feature that allowed path-swappers to be visible.

    To restore this, change Obj03 to this:

    [68k]Obj03:
    moveq #0,d0
    move.b routine(a0),d0
    move.w Obj03_Index(pc,d0.w),d1
    jsr Obj03_Index(pc,d1.w)
    tst.w (Debug_mode_flag).w
    beq.w +
    jmp (DisplaySprite).l
    +
    jmp (MarkObjGone3).l[/68k]

    Fix Tails' Rolling Deceleration

    This is one of the stranges oddities, Tails' Deceleration code is just Sonic 1's, to fix this replace this:

    [68k] move.w (Tails_deceleration).w,d4
    asr.w #2,d4 ; controlled roll deceleration...
    ; interestingly, Tails is much worse at this than Sonic when underwater[/68k]

    With this:

    [68k] move.w #$20,d4[/68k]

    This allows Tails to decelerate while rolling at the same speed as Sonic underwater.

    EDIT: Added "Fix Super Sonic in 2 Player Mode" and "Fix Erratic Left Wall Collision" guide.
    EDIT2: Added "Fix Lives Monitor" guide.
    EDIT3: Added "Restore Path Swappers in Debug Mode" guide.
    EDIT4: Added "Fix Tails' Rolling Deceleration"
     
  16. Esrael

    Esrael

    Tech Members Tech Member
    187
    0
    0
    Brazil, São Paulo, Guarulhos
    Neto Assembler Editor / Sonic 2 Delta / Neto Boot Loader

    Fixed broken links

    Best regards:
    ----------------------
    Neto.