Sonic and Sega Retro Message Board: MoDule - Viewing Profile - Sonic and Sega Retro Message Board

Jump to content

Hey there, Guest!  (Log In · Register) Help

Group:
Tech Member: Tech Members
Active Posts:
308 (0.11 per day)
Most Active In:
Engineering & Reverse Engineering (189 posts)
Joined:
03-October 07
Profile Views:
2568
Last Active:
User is offline Nov 26 2014 09:17 PM
Currently:
Offline

My Information

Age:
27 years old
Birthday:
July 5, 1988
Gender:
Male Male

Contact Information

E-mail:
Private
Website:
Website  http://

Previous Fields

Project:
Procrastinating from writing bug-fix guides
National Flag:
de
Wiki edits:
52

Latest Visitors

Topics I've Started

  1. MoDule's bugfix guides Vol. 1

    15 May 2011 - 07:27 PM

    During my hacking escapades I tend to be reminded of bugs I've known for a long time. Sometimes I can find their sources and try to fix them. Here's a few guides I put together for common bugs I haven't seen fixed anywhere.

    How-to:Fix bugs relating to Super Sonic
    This is a big one. Here you'll find fixes for a whole bunch of Super Sonic related bugs, the most important one being the one where you get stuck in the air after completing a level. Most of the others are mostly cosmetic, but do make the game behave the way it was supposed to.

    How-to:Use correct height when roll jumping
    I posted about this before. Basically, when you jump while rolling Sonic might jerk upward a few pixels upon landing. When this happens you can't jump until he lands again. This one has been annoying me for a while.

    How-to:Fix jump height bug when exiting water
    This one's been bothering me ever since I was a kid. Sometimes when you try to jump out of water Sonic only jumps really low. After I finally figured out what causes it, fixing it was pretty easy.

    I also added explanations of every bug for your reading pleasure.
    This should hopefully be educational.

    There's more to come, I've just spent multiple hours writing these and it's really late.

    Edit: Next batch:

    How-to:Fix camera y position for Tails
    This one's always really bothered me and I don't even use Tails that often. The camera's vertical position is pretty jerky for Tails. The way this fix is done, no special code will ever be necessary to handle the character changing height, at least as far as the camera is concerned.

    How-to:Fix bug in ARZ Boss arrow's platform behavior
    Not that noticeable or problematic, but it's still nice to have it fixed. The arrows that the ARZ boss fires trigger the walking in air bug that happens when Sonic doesn't get released from a platform properly.

    How-to:Collide with water after being hurt
    When Sonic gets hurt he doesn't interact with the water surface. I put this one down as a design choice, since it's debatable weather the original behavior was intentional or not. The Advance series indicates that at least one person thought it wasn't.
  2. Roll-jumping has always bothered me

    27 December 2009 - 06:41 PM

    So has anyone else ever noticed something odd when you jump while rolling? Not the fact that you can't control your horizontal movement, that's intentional. What I mean is that sometimes upon landing Sonic jerks up slightly, delaying your ability to jump right afterward. Lets look at the relevant code:
    Syntax Highlighted Code: ASM
    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
     
    ; loc_1AA38:
    Sonic_Jump:
    move.b (Ctrl_1_Press_Logical).w,d0
    andi.b #button_B_mask|button_C_mask|button_A_mask,d0 ; is A, B or C pressed?
    beq.w return_1AAE6 ; if not, return
    ;snip
    move.b #$13,y_radius(a0) ; set to standing height ;<-- I mean this
    move.b #9,x_radius(a0) ; and width
    btst #2,status(a0) ; is Sonic rolling?
    bne.s Sonic_RollJump ; if yes, branch
    move.b #$E,y_radius(a0) ; set to rolling height
    move.b #7,x_radius(a0) ; and width
    move.b #2,anim(a0) ; use "jumping" animation
    bset #2,status(a0)
    addq.w #5,y_pos(a0)
     
    return_1AAE6:
    rts
    ; ---------------------------------------------------------------------------
    ; loc_1AAE8:
    Sonic_RollJump:
    bset #4,status(a0) ; set the rolling+jumping flag
    rts
    ; End of function Sonic_Jump

    So, when Sonic is already rolling and he jumps his height is reset to standing height, even though his size shouldn't change at all. Then we have the part:

    Syntax Highlighted Code: ASM
    Sonic_ResetOnFloor_Part2:
    ;snip
    btst #2,status(a0)
    beq.s Sonic_ResetOnFloor_Part3
    bclr #2,status(a0)
    move.b #$13,y_radius(a0) ; this increases Sonic's collision height to standing
    move.b #9,x_radius(a0)
    move.b #0,anim(a0) ; use running/walking/standing animation
    subq.w #5,y_pos(a0) ; move Sonic up 5 pixels so the increased height doesn't push him into the ground

    This will push him up by 5 pixels whether he's roll-jumping or not.

    My question here is: which part is wrong? Is it not supposed to reset his height when he jumps, or should it check for roll-jumping before he gets moved up? I personally tend to the former. What is your stance on this? Why would Sonic need to have his height reset?
    I'd also like to point out that this was never fixed in any main-series 16-bit game. It existed all the way into s3k.
  3. s3k objects manager in s2

    24 December 2009 - 06:16 PM

    Hello y'all. Title says all. Note to all those who saw this in the Techies lounge: Since a lot of people were having trouble expanding the SST, what with long running projects based on older disassemblies and stuff, I've provided a workaround that should work for everyone.

    Commencing mostly copy-paste from Techies lounge:

    Hi everyone,
    um...

    Oh well, since I'm bad at introductions let's cut right to the chase. A (long) while back I made a post explaining how to achieve those nice under water ripple effects in Sonic 2. It was neat and all, but it slowed the game down quite a bit. A few users suggested porting s3k's objects manager over. At the time it seemed a very daunting task, even with the partially commented file shobiz gave me (thanks, btw. Helped me name a lot of things). So, as a first step I tried to wrap my head around s2's objects manager. After that I started comparing the two and noting any differences. From there all I needed to do was adjust the registers to each other and comment some of the new code for good measure. Since it's been sitting around for weeks and I'm not doing much with it anymore, here it is.
    Huh, guess I wrote a long introduction after all.

    ==The important part starts here==

    s3k Objects Manager in s2
    by MoDule

    Description:
    This is the objects manager used in Sonic 3 & Knuckles ported to Sonic 2 with minor adjustments for compatibility.
    In Sonic 2 an object is loaded whenever the camera reaches a certain position relative to it and is deleted when at a certain distance. An area in RAM called the respawn table is used to keep track of certain object's states such as monitors which can only be broken once. Only objects that have this property ,indicated by the highest bit in the second word of the object's definition being set, have an entry in the respawn table. It was modified slightly in Sonic 3 & Knuckles.
    The main difference between the two is that s3k does an additional y-range check (optional) before loading an object. This leads to less objects being loaded at one time and gives a slight improvement to performance. The other difference is that it is now required that every object gets an entry in the object respawn table. This means the respawn table needs more space in RAM which needs to be found somewhere.

    Target users:
    Anyone hacking s2 who can spare the space. I've seen quite a few hacks that have had slowdown in some areas because there were branching paths above or below with lots of objects in them. This can help and it isn't very hard to implement, requiring only minor changes to some objects.

    Usage:
    Attention: Just to be on the safe side, any running projects should be backed up before attempting to use this guide, but you knew that already, didn't you?
    Before we begin I'd like to point out that all work is based on SVN revision 125. I will try to note any information that will be useful to those using older disassemblies. This will be an ongoing process.

    In the s2 disassembly, replace everything from "ObjectsManager:" (loc_17AA4 in older disassemblies) up to, but not
    including, "SingleObjLoad:" with the contents of the following code box:

    Syntax Highlighted Code: ASM
    ; ---------------------------------------------------------------------------
    ; Objects Manager
    ; Subroutine to load objects whenever they are close to the screen. Unlike in
    ; normal s2, in this version every object gets an entry in the respawn table.
    ; This is necessary to get the additional y-range checks to work.
    ;
    ; input variables:
    ; -none-
    ;
    ; writes:
    ; d0, d1, d2
    ; d3 = upper boundary to load object
    ; d4 = lower boundary to load object
    ; d5 = #$FFF, used to filter out object's y position
    ; d6 = camera position
    ;
    ; a0 = address in object placement list
    ; a3 = address in object respawn table
    ; a6 = object loading routine
    ; ---------------------------------------------------------------------------
     
    ; loc_17AA4
    ObjectsManager:
    moveq #0,d0
    move.b (Obj_placement_routine).w,d0
    jmp ObjectsManager_States(pc,d0.w)
     
    ; ============== JUMP TABLE =============================================
    ObjectsManager_States:
    bra.w ObjectsManager_Init ; 0
    bra.w ObjectsManager_Main ; 2
    bra.w ObjectsManager_Main ; 4
    rts
    ; ============== END JUMP TABLE =============================================
     
    ObjectsManager_Init:
    addq.b #4,(Obj_placement_routine).w
     
    lea (Object_Respawn_Table).w,a0
    moveq #0,d0
    move.w #bytesToLcnt(Object_Respawn_Table_End-Object_Respawn_Table),d1 ; set loop counter
    -
    move.l d0,(a0)+
    dbf d1,-
     
    move.w (Current_ZoneAndAct).w,d0
    ;
    ; ror.b #1,d0 ; this is from s3k
    ; lsr.w #5,d0
    ; lea (Off_Objects).l,a0
    ; movea.l (a0,d0.w),a0
    ;
    ror.b #1,d0
    lsr.w #6,d0
    lea (Off_Objects).l,a0 ; load the first pointer in the object layout list pointer index,
    adda.w (a0,d0.w),a0 ; load the pointer to the current object layout
     
    tst.w (Two_player_mode).w ; skip if not in 2-player vs mode
    beq.s +
    cmpi.b #casino_night_zone,(Current_Zone).w ; skip if not Casino Night Zone
    bne.s +
    lea (Objects_CNZ1_2P).l,a0 ; CNZ 1 2-player object layout
    tst.b (Current_Act).w ; skip if not past act 1
    beq.s +
    lea (Objects_CNZ2_2P).l,a0 ; CNZ 2 2-player object layout
    +
    ; initialize each object load address with the first object in the layout
    move.l a0,(Obj_load_addr_right).w
    move.l a0,(Obj_load_addr_left).w
    lea (Object_Respawn_Table).w,a3
     
    move.w (Camera_X_pos).w,d6
    subi.w #$80,d6 ; look one chunk to the left
    bcc.s + ; if the result was negative,
    moveq #0,d6 ; cap at zero
    + andi.w #$FF80,d6 ; limit to increments of $80 (width of a chunk)
     
    movea.l (Obj_load_addr_right).w,a0 ; get first object in layout
     
    - ; at the beginning of a level this gives respawn table entries to any object that is one chunk
    ; behind the left edge of the screen that needs to remember its state (Monitors, Badniks, etc.)
    cmp.w (a0),d6 ; is object's x position >= d6?
    bls.s + ; if yes, branch
    addq.w #6,a0 ; next object
    addq.w #1,a3 ; respawn index of next object going right
    bra.s -
    ; ---------------------------------------------------------------------------
     
    + move.l a0,(Obj_load_addr_right).w ; remember rightmost object that has been processed, so far (we still need to look forward)
    move.w a3,(Obj_respawn_index_right).w ; and its respawn table index
     
    lea (Object_Respawn_Table).w,a3 ; reset a3
    movea.l (Obj_load_addr_left).w,a0 ; reset a0
    subi.w #$80,d6 ; look even farther left (any object behind this is out of range)
    bcs.s + ; branch, if camera position would be behind level's left boundary
     
    - ; count how many objects are behind the screen that are not in range and need to remember their state
    cmp.w (a0),d6 ; is object's x position >= d6?
    bls.s + ; if yes, branch
    addq.w #6,a0
    addq.w #1,a3 ; respawn index of next object going left
    bra.s - ; continue with next object
    ; ---------------------------------------------------------------------------
     
    + move.l a0,(Obj_load_addr_left).w ; remember current object from the left
    move.w a3,(Obj_respawn_index_left).w ; and its respawn table index
     
    move.w #-1,(Camera_X_pos_last).w ; make sure ObjectsManager_GoingForward is run
     
    move.w (Camera_Y_pos).w,d0
    andi.w #$FF80,d0
    move.w d0,(Camera_Y_pos_last).w ; make sure the Y check isn't run unnecessarily during initialization
    ; ---------------------------------------------------------------------------
     
    ObjectsManager_Main:
    ; get coarse camera position
    move.w (Camera_Y_pos).w,d1
    subi.w #$80,d1
    andi.w #$FF80,d1
    move.w d1,(Camera_Y_pos_coarse).w
     
    move.w (Camera_X_pos).w,d1
    subi.w #$80,d1
    andi.w #$FF80,d1
    move.w d1,(Camera_X_pos_coarse).w
     
    tst.w (Camera_Min_Y_pos).w ; does this level y-wrap?
    bpl.s ObjMan_Main_NoYWrap ; if not, branch
    lea (ChkLoadObj_YWrap).l,a6 ; set object loading routine
    move.w (Camera_Y_pos).w,d3
    andi.w #$FF80,d3 ; get coarse value
    move.w d3,d4
    addi.w #$200,d4 ; set lower boundary
    subi.w #$80,d3 ; set upper boundary
    bpl.s + ; branch, if upper boundary > 0
    andi.w #$7FF,d3 ; wrap value
    bra.s ObjMan_Main_Cont
    ; ---------------------------------------------------------------------------
     
    + move.w #$7FF,d0
    addq.w #1,d0
    cmp.w d0,d4
    bls.s + ; branch, if lower boundary < $7FF
    andi.w #$7FF,d4 ; wrap value
    bra.s ObjMan_Main_Cont
    ; ---------------------------------------------------------------------------
     
    ObjMan_Main_NoYWrap:
    move.w (Camera_Y_pos).w,d3
    andi.w #$FF80,d3 ; get coarse value
    move.w d3,d4
    addi.w #$200,d4 ; set lower boundary
    subi.w #$80,d3 ; set upper boundary
    bpl.s +
    moveq #0,d3 ; no negative values allowed
     
    + lea (ChkLoadObj).l,a6 ; set object loading routine
     
    ObjMan_Main_Cont:
    move.w #$FFF,d5 ; this will be used later when we load objects
    move.w (Camera_X_pos).w,d6
    andi.w #$FF80,d6
    cmp.w (Camera_X_pos_last).w,d6 ; is the X range the same as last time?
    beq.w ObjectsManager_SameXRange ; if yes, branch
    bge.s ObjectsManager_GoingForward ; if new pos is greater than old pos, branch
     
    ; if the player is moving back
    move.w d6,(Camera_X_pos_last).w ; remember current position for next time
     
    movea.l (Obj_load_addr_left).w,a0 ; get current object going left
    movea.w (Obj_respawn_index_left).w,a3 ; and its respawn table index
     
    subi.w #$80,d6 ; look one chunk to the left
    bcs.s ObjMan_GoingBack_Part2 ; branch, if camera position would be behind level's left boundary
     
    jsr (SingleObjLoad).l ; find an empty object slot
    bne.s ObjMan_GoingBack_Part2 ; branch, if there are none
    - ; load all objects left of the screen that are now in range
    cmp.w -6(a0),d6 ; is the previous object's X pos less than d6?
    bge.s ObjMan_GoingBack_Part2 ; if it is, branch
    subq.w #6,a0 ; get object's address
    subq.w #1,a3 ; and respawn table index
    jsr (a6) ; load object
    bne.s + ; branch, if SST is full
    subq.w #6,a0
    bra.s - ; continue with previous object
    ; ---------------------------------------------------------------------------
     
    + ; undo a few things, if the object couldn't load
    addq.w #6,a0 ; go back to last object
    addq.w #1,a3 ; since we didn't load the object, undo last change
     
    ObjMan_GoingBack_Part2:
    move.l a0,(Obj_load_addr_left).w ; remember current object going left
    move.w a3,(Obj_respawn_index_left).w ; and its respawn table index
    movea.l (Obj_load_addr_right).w,a0 ; get next object going right
    movea.w (Obj_respawn_index_right).w,a3 ; and its respawn table index
    addi.w #$300,d6 ; look two chunks beyond the right edge of the screen
     
    - ; subtract number of objects that have been moved out of range (from the right side)
    cmp.w -6(a0),d6 ; is the previous object's X pos less than d6?
    bgt.s + ; if it is, branch
    subq.w #6,a0 ; get object's address
    subq.w #1,a3 ; and respawn table index
    bra.s - ; continue with previous object
    ; ---------------------------------------------------------------------------
     
    + move.l a0,(Obj_load_addr_right).w ; remember next object going right
    move.w a3,(Obj_respawn_index_right).w ; and its respawn table index
    bra.s ObjectsManager_SameXRange
    ; ---------------------------------------------------------------------------
     
    ObjectsManager_GoingForward:
    move.w d6,(Camera_X_pos_last).w
     
    movea.l (Obj_load_addr_right).w,a0 ; get next object from the right
    movea.w (Obj_respawn_index_right).w,a3 ; and its respawn table index
    addi.w #$280,d6 ; look two chunks forward
    jsr (SingleObjLoad).l ; find an empty object slot
    bne.s ObjMan_GoingForward_Part2 ; branch, if there are none
     
    - ; load all objects right of the screen that are now in range
    cmp.w (a0),d6 ; is object's x position >= d6?
    bls.s ObjMan_GoingForward_Part2 ; if yes, branch
    jsr (a6) ; load object (and get address of next object)
    addq.w #1,a3 ; respawn index of next object to the right
    beq.s - ; continue loading objects, if the SST isn't full
     
    ObjMan_GoingForward_Part2:
    move.l a0,(Obj_load_addr_right).w ; remember next object from the right
    move.w a3,(Obj_respawn_index_right).w ; and its respawn table index
    movea.l (Obj_load_addr_left).w,a0 ; get current object from the left
    movea.w (Obj_respawn_index_left).w,a3 ; and its respawn table index
    subi.w #$300,d6 ; look one chunk behind the left edge of the screen
    bcs.s ObjMan_GoingForward_End ; branch, if camera position would be behind level's left boundary
     
    - ; subtract number of objects that have been moved out of range (from the left)
    cmp.w (a0),d6 ; is object's x position >= d6?
    bls.s ObjMan_GoingForward_End ; if yes, branch
    addq.w #6,a0 ; next object
    addq.w #1,a3 ; respawn index of next object to the left
    bra.s - ; continue with next object
    ; ---------------------------------------------------------------------------
     
    ObjMan_GoingForward_End:
    move.l a0,(Obj_load_addr_left).w ; remember current object from the left
    move.w a3,(Obj_respawn_index_left).w ; and its respawn table index
     
    ObjectsManager_SameXRange:
    move.w (Camera_Y_pos).w,d6
    andi.w #$FF80,d6
    move.w d6,d3
    cmp.w (Camera_Y_pos_last).w,d6 ; is the y range the same as last time?
    beq.w ObjectsManager_SameYRange ; if yes, branch
    bge.s ObjectsManager_GoingDown ; if the player is moving down
     
    ; if the player is moving up
    tst.w (Camera_Min_Y_pos).w ; does the level y-wrap?
    bpl.s ObjMan_GoingUp_NoYWrap ; if not, branch
    tst.w d6
    bne.s ObjMan_GoingUp_YWrap
    cmpi.w #$80,(Camera_Y_pos_last).w
    bne.s ObjMan_GoingDown_YWrap
     
    ObjMan_GoingUp_YWrap:
    subi.w #$80,d3 ; look one chunk up
    bpl.s ObjectsManager_YCheck ; go to y check, if camera y position >= $80
    andi.w #$7FF,d3 ; else, wrap value
    bra.s ObjectsManager_YCheck
     
    ; ---------------------------------------------------------------------------
     
    ObjMan_GoingUp_NoYWrap:
    subi.w #$80,d3 ; look one chunk up
    bmi.w ObjectsManager_SameYRange ; don't do anything if camera y position is < $80
    bra.s ObjectsManager_YCheck
    ; ---------------------------------------------------------------------------
     
    ObjectsManager_GoingDown:
    tst.w (Camera_Min_Y_pos).w ; does the level y-wrap?
    bpl.s ObjMan_GoingDown_NoYWrap ; if not, branch
    tst.w (Camera_Y_pos_last).w
    bne.s ObjMan_GoingDown_YWrap
    cmpi.w #$80,d6
    bne.s ObjMan_GoingUp_YWrap
     
    ObjMan_GoingDown_YWrap:
    addi.w #$180,d3 ; look one chunk down
    cmpi.w #$7FF,d3
    bcs.s ObjectsManager_YCheck ; go to check, if camera y position < $7FF
    andi.w #$7FF,d3 ; else, wrap value
    bra.s ObjectsManager_YCheck
    ; ---------------------------------------------------------------------------
     
    ObjMan_GoingDown_NoYWrap:
    addi.w #$180,d3 ; look one chunk down
    cmpi.w #$7FF,d3
    bhi.s ObjectsManager_SameYRange ; don't do anything, if camera is too close to bottom
     
    ObjectsManager_YCheck:
    jsr (SingleObjLoad).l ; get an empty object slot
    bne.s ObjectsManager_SameYRange ; branch, if there are none
    move.w d3,d4
    addi.w #$80,d4
    move.w #$FFF,d5 ; this will be used later when we load objects
    movea.l (Obj_load_addr_left).w,a0 ; get next object going left
    movea.w (Obj_respawn_index_left).w,a3 ; and its respawn table index
    move.l (Obj_load_addr_right).w,d7 ; get next object going right
    sub.l a0,d7 ; d7 = number of objects between the left and right boundaries * 6
    beq.s ObjectsManager_SameYRange ; branch if there are no objects inbetween
    addq.w #2,a0 ; align to object's y position
     
    - ; check, if current object needs to be loaded
    tst.b (a3) ; is object already loaded?
    bmi.s + ; if yes, branch
    move.w (a0),d1
    and.w d5,d1 ; get object's y position
    cmp.w d3,d1
    bcs.s + ; branch, if object is out of range from the top
    cmp.w d4,d1
    bhi.s + ; branch, if object is out of range from the bottom
    bset #7,(a3) ; mark object as loaded
    ; load object
    move.w -2(a0),x_pos(a1)
    move.w (a0),d1
    move.w d1,d2
    and.w d5,d1 ; get object's y position
    move.w d1,y_pos(a1)
    rol.w #3,d2
    andi.w #3,d2 ; get object's render flags and status
    move.b d2,render_flags(a1)
    move.b d2,status(a1)
    move.b 2(a0),id(a1)
    move.b 3(a0),subtype(a1)
    move.w a3,respawn_index(a1)
    jsr (SingleObjLoad).l ; find new object slot
    bne.s ObjectsManager_SameYRange ; brach, if there are none left
    +
    addq.w #6,a0 ; address of next object
    addq.w #1,a3 ; and its respawn index
    subq.w #6,d7 ; subtract from size of remaining objects
    bne.s - ; branch, if there are more
     
    ObjectsManager_SameYRange:
    move.w d6,(Camera_Y_pos_last).w
    rts
    ; ===========================================================================
    ; ---------------------------------------------------------------------------
    ; Subroutines to check if an object needs to be loaded,
    ; with and without y-wrapping enabled.
    ;
    ; input variables:
    ; d3 = upper boundary to load object
    ; d4 = lower boundary to load object
    ; d5 = #$FFF, used to filter out object's y position
    ;
    ; a0 = address in object placement list
    ; a1 = object
    ; a3 = address in object respawn table
    ;
    ; writes:
    ; d1, d2, d7
    ; ---------------------------------------------------------------------------
    ChkLoadObj_YWrap:
    tst.b (a3) ; is object already loaded?
    bpl.s + ; if not, branch
    addq.w #6,a0 ; address of next object
    moveq #0,d1 ; let the objects manager know that it can keep going
    rts
    ; ---------------------------------------------------------------------------
     
    + move.w (a0)+,d7 ; x_pos
    move.w (a0)+,d1 ; there are three things stored in this word
    move.w d1,d2 ; does this object skip y-Checks?
    bmi.s + ; if yes, branch
    and.w d5,d1 ; y_pos
    cmp.w d3,d1
    bcc.s LoadObj_YWrap
    cmp.w d4,d1
    bls.s LoadObj_YWrap
    addq.w #2,a0 ; address of next object
    moveq #0,d1 ; let the objects manager know that it can keep going
    rts
    ; ---------------------------------------------------------------------------
     
    + and.w d5,d1 ; y_pos
     
    LoadObj_YWrap:
    bset #7,(a3) ; mark object as loaded
    move.w d7,x_pos(a1)
    move.w d1,y_pos(a1)
    rol.w #3,d2 ; adjust bits
    andi.w #3,d2 ; get render flags and status
    move.b d2,render_flags(a1)
    move.b d2,status(a1)
    _move.b (a0)+,id(a1) ; load obj
    move.b (a0)+,subtype(a1)
    move.w a3,respawn_index(a1)
    bra.s SingleObjLoad ; find new object slot
     
    ;loc_17F36
    ChkLoadObj:
    tst.b (a3) ; is object already loaded?
    bpl.s + ; if not, branch
    addq.w #6,a0 ; address of next object
    moveq #0,d1 ; let the objects manager know that it can keep going
    rts
    ; ---------------------------------------------------------------------------
     
    + move.w (a0)+,d7 ; x_pos
    move.w (a0)+,d1 ; there are three things stored in this word
    move.w d1,d2 ; does this object skip y-Checks? ;*6
    bmi.s ++ ; if yes, branch
    and.w d5,d1 ; y_pos
    cmp.w d3,d1
    bcs.s + ; branch, if object is out of range from the top
    cmp.w d4,d1
    bls.s LoadObj ; branch, if object is in range from the bottom
    +
    addq.w #2,a0 ; address of next object
    moveq #0,d1
    rts
    ; ---------------------------------------------------------------------------
     
    + and.w d5,d1 ; y_pos
     
    LoadObj:
    bset #7,(a3) ; mark object as loaded
    move.w d7,x_pos(a1)
    move.w d1,y_pos(a1)
    rol.w #3,d2 ; adjust bits
    andi.w #3,d2 ; get render flags and status
    move.b d2,render_flags(a1)
    move.b d2,status(a1)
    _move.b (a0)+,id(a1) ; load obj
    move.b (a0)+,subtype(a1)
    move.w a3,respawn_index(a1)
    ; continue straight to SingleObjLoad
    ; End of function ChkLoadObj
    ; ===========================================================================


    Note: This will disable two player mode's objects manager.

    And now a list of all changes needed to be applied to a clean disassembly as of SVN revision 125:

    1: The respawn table needs to be extended. In Sonic 2 there are two zones that use more than 255 objects and I'm sure most hackers would like to be relatively free of such a limitation as well. S3k uses $300 bytes for its respawn table, allowing for a total of 768 objects per level. The problem is finding free space. Porting s3k's rings manager frees some space, I believe and there's a tutorial for that. Alternatively, one could use the RAM area used by s1's sound driver.

    Achtung! This is the part that has been causing people some trouble. If you are using an older disassembly or just don't feel like screwing around with the SST, an alternative Solution is below.
    2: The SST entry for respawn_index needs to be made a word. This can (and will, if not relocated) cause conflicts with other overlapping SST variables. Either a conflict free allocation must be found or the SST must be extended. For the latter, adding four bytes is easier than just adding two (trust me). If this is done the amount of objects in Dynamic_Object_RAM needs to be reduced. Doing just this can cause some unnamed RAM addresses to become shifted and overlap with other named addresses. Adding a few empty bytes after Object_RAM_End can help avoid this. To find out exactly how many bytes need to be added, compiling the ROM now would give an error message stating the RAM definitions are too long by a certain number of bytes. Reduce the amount of objects in dynamic object RAM so that the number of bytes used is less than what the error message says (but be sure to use even numbers). Any number of bytes left unused need to be defined (ds.b) after Object_RAM_End. If this is done correctly, all other RAM variables should be in the right place.
    Additionally, all references to respawn_index need to be made word length (so all ".b"s need to be made ".w"s).
    Old disassemblies: the addresses for Dynamic_Object_RAM and Dynamic_Object_RAM_End might not exist, yet.
    Dynamic_Object_RAM = $B000 + object_size * 16 (object_size = next_object).
    Dynamic_Object_RAM_End = Dynamic_Object_RAM + ($28 + $48) * object_size.
    Download the latest disassembly, check where these are used and adapt your code accordingly. we do this so the game knows how many objects are in the dynamic object Ram are, since we changed it.

    Alternative solution: Based on a suggestion by qiuu, an other way to get by is find an additional $70 words in RAM and use that as a respawn index table. Whenever a respawn index is needed, the address in the table can be calculated based on the object's address in the object table. Here's what you need to do:
    -Add a Ram address Object_Respawn_Indices somewhere and make it $70 words long.
    -Change the code after the label LoadObj to this:
    Syntax Highlighted Code: ASM
    LoadObj:
    bset #7,(a3) ; mark object as loaded
    move.w d7,x_pos(a1)
    move.w d1,y_pos(a1)
    rol.w #3,d2 ; adjust bits
    andi.w #3,d2 ; get render flags and status
    move.b d2,render_flags(a1)
    move.b d2,status(a1)
    _move.b (a0)+,id(a1) ; load obj
    move.b (a0)+,subtype(a1)
    ; move.w a3,respawn_index(a1)
    move.w a1,d2
    subi.w #Object_RAM,d2
    lsr.w #5,d2
    andi.w #$7F,d2
    lea (Object_Respawn_Indices).w,a1
    adda.w d2,a1
    move.w a3,(a1)
    ; continue straight to SingleObjLoad

    -Apply the same changes to ObjectsManager_YCheck and LoadObj_YWrap, also.
    -Replace anything that looks like this:
    Syntax Highlighted Code: ASM
    	lea	(Object_Respawn_Table).w,a2	; get respawn table's address
    moveq #0,d0
    move.b respawn_index(a0),d0 ; get object's respawn index
    beq.s + ; if it's zero, don't remember object
    bclr #7,2(a2,d0.w) ; clear respawn table entry so object can be loaded again
    +

    with this:
    Syntax Highlighted Code: ASM
    	move.w	a0,d0		; get object's address
    subi.w #Object_RAM,d0 ; get object's index * $40
    lsr.w #5,d0 ; get object's index * 2
    andi.w #$7F,d0
    lea (Object_Respawn_Indices).w,a1
    adda.w d0,a1 ; calculate address in table
    move.w (a1),d0 ; get address in respawn table
    beq.s + ; if it's zero, don't remember object
    movea.w d0,a2 ; load address into a2
    bclr #7,(a2) ; clear respawn table entry, so object can be loaded again
    +

    Be careful with which registers you use. Sometimes you can't use exactly these. Also, sometimes the beq is missing. In such a case, the bclr will be different, too. Search for "respawn_index".
    You can skip step 3, since the code above replaces what we would have done there. You should however still define the markObj_gone macro as follows:
    Syntax Highlighted Code: ASM
    markObj_gone macro
    move.w a0,d0 ; get object's address
    subi.w #Object_RAM,d0 ; get object's index * $40
    lsr.w #5,d0 ; get object's index * 2
    andi.w #$7F,d0
    lea (Object_Respawn_Indices).w,a1
    adda.w d0,a1 ; calculate address in table
    move.w (a1),d0 ; get address in table
    beq.s mdebugObj ; if it's zero, don't remember object
    movea.w d0,a2 ; load address into a2
    bclr #7,(a2) ; clear respawn table entry, so object can be loaded again
    mdebugObj
    endm

    That should do the trick.
    And now to the rest of the guide.
    Remember, skip step 3.

    3: Since respawn_index now contains the address of the object's respawn table entry instead of its index, the address calculation done by some objects is unnecessary. For comparison's sake:
    Taken from MarkObjGone:
    Syntax Highlighted Code: ASM
    	lea	(Object_Respawn_Table).w,a2	; get respawn table's address
    moveq #0,d0
    move.b respawn_index(a0),d0 ; get object's respawn index
    beq.s + ; if it's zero, don't remember object
    bclr #7,2(a2,d0.w) ; clear respawn table entry so object can be loaded again
    +

    Should now be:
    Syntax Highlighted Code: ASM
    	move.w	respawn_index(a0),d0	; get address in respawn table
    beq.s + ; if it's zero, don't remember object
    movea.w d0,a2 ; load address into a2
    bclr #7,(a2) ; clear respawn table entry, so object can be loaded again
    +

    This code clears an object's respawn table entry. Refer to the wiki to see how object respawning works.
    This piece of code will be used quite frequently, so it might be preferable to write a short macro for this. Search for "Object_Respawn_Table" to find all these locations.
    Macro:
    Syntax Highlighted Code: ASM
    markObj_gone macro
    move.w respawn_index(a0),d0 ; get address in respawn table
    beq.s mdebugObj ; if it's zero, object was placed in debug mode
    movea.w d0,a2 ; load address into a2
    bclr #7,(a2) ; clear respawn entry, so object can be loaded again
    mdebugObj
    endm


    4: Any object that does not call any version of MarkObjGone somewhere at the end of its code handles off screen deletion on its own and needs to be modified in a similar way as above. These are mostly objects that did not get a respawn table entry in s2. The code is very similar to MarkObjGone, search for "Camera_X_pos_coarse". Clearing bit 7 of the object's respawn table entry needs to be done somewhere before object deletion. For comparison's sake:
    Before:
    Syntax Highlighted Code: ASM
    	tst.w	(Two_player_mode).w
    bne.s + ; rts
    move.w x_pos(a0),d0 ; get object's x position
    andi.w #$FF80,d0 ; get chunk object is in
    sub.w (Camera_X_pos_coarse).w,d0 ; compare to camera position
    cmpi.w #$280,d0 ; is object out of range?
    bhi.w DeleteObject ; if yes, delete object
    + ; else, do nothing
    rts

    After:
    Syntax Highlighted Code: ASM
    	tst.w	(Two_player_mode).w
    bne.s + ; rts
    move.w x_pos(a0),d0 ; get object's x position
    andi.w #$FF80,d0 ; get chunk object is in
    sub.w (Camera_X_pos_coarse).w,d0 ; compare to camera position
    cmpi.w #$280,d0 ; is object out of range?
    bls.w + ; if not, branch
    markObj_gone ; else, clear respawn table entry
    bra.w DeleteObject ; and delete object
    +
    rts

    Note: most objects use register a2 for the respawn address, but there is at least one instance where a3 is used, instead. The markObj_gone macro shouldn't be used there.
    Note also: Collapsing platforms will stay destroyed unless they are modified to clear their entry in the respawn table upon destruction.
    To those using the alternate solution to step 2: You need to get the respawn address from the respawn index table similar to how it was done before. It shoud be clear how to do this. I can post some example code, if the demand exists.

    5: Since it is now assumed respawn_index is word length, the RAM addresses Obj_respawn_index_right and Obj_respawn_index_left need to be extended to word length, as well. The two player mode variables can be overwritten for this.
    Note: Older disassemblies will have different names: Obj_load_addr_0 and Obj_load_addr_1 are Obj_load_addr_right and Obj_load_addr_left, respectively. Obj_load_addr_2 and Obj_load_addr_3 are the 2P equivalents.

    6: A few additional RAM addresses are needed: Camera_Y_pos_coarse and Camera_Y_pos_last. Both word length. If the SST was expanded, there should be some free space after Object_RAM_End. Otherwise, there should still be a few two player mode specific variables no longer used by the objects manager.

    7: The high bit in the 2nd word of an object's definition in the object layout no longer indicates that the object should get an entry in the object respawn table. Instead, it now means the object should skip the y-check when being loaded (in other words, it will behave like in s2). This should be used for tall objects, like the elevators in CNZ. Any existing object layouts should be adjusted to reflect this.
    Btw, it would be nice if someone could write a small program to do this.

    Note: The following points only apply to those who decided to expand the SST.
    8: Expanding the SST causes Sonic and Tails' interaction with some objects to break, because it is still assumed an object is $40 bytes long, causing an address calculation to go wrong. This needs to be fixed wherever Sonic or Tails's interact is read or written. Relocating interact to $42 and resizing it to a word is the easiest fix. For comparison:
    In s2 with object size = $40:
    Syntax Highlighted Code: ASM
    	move.b	interact(a0),d0
    lsl.w #6,d0
    lea (MainCharacter).w,a1 ; a1=character
    lea (a1,d0.w),a1 ; a1=object

    For object size != $40:
    Syntax Highlighted Code: ASM
    	movea.w	interact(a0),a1 ; a1=object

    Note: Most of the time "Object_RAM" is used, instead of "MainCharacter". Both are the same address. Search for both to find all instances of this or similar code.
    Note: A few objects write something back to interact. Here it should suffice to comment out the address calculation (recognizable by shifting register dx right instead of left. It's basically the above code backwards).
    Note: Changing the SST can cause problems with the special stages, as they use different SST variables but still have the same names in the disasm. The easiest solution is to rename the old location of any SST value that was moved and replace all instances in the special stage code.

    9: SingleObjLoad2 needs to be modified somewhat. Originally it calculated the difference between the current object's address and the end of Dynamic_Object_RAM, then divided the result by $40 using a shift operation to get how many object slots are left after a0. Assuming the new SST has object_size = $44, division through shifting is impossible, but an actual division is also undesirable. S3k uses a table that maps the results of the division by $40 to the result of a division by $4A. Here's what it needs to be with object_size = $44:
    Syntax Highlighted Code: ASM
    SingleObjLoad2:
    movea.l a0,a1
    move.w #Tails_Tails,d0 ; $D000
    sub.w a0,d0 ; subtract current object location
    lsr.w #6,d0 ; divide by $40
    move.b Find_First_Sprite_Table(pc,d0.w),d0
    bmi.s +
     
    -
    lea next_object(a1),a1 ; load obj address ; goto next object RAM slot
    tst.b id(a1) ; is object RAM slot empty?
    dbeq d0,- ; if yes, branch
    +
     
    return_18014:
    rts
    ; ===========================================================================
    Find_First_Sprite_Table: ; map n*64/64 to n*68/64
    dc.b $FF, $0, $1, $2, $3, $4, $5, $6, $7, $8, $9, $A, $b
    dc.b $C, $D, $E, $F, $F, $10, $11, $12, $13, $14, $15, $16, $17
    dc.b $18, $19, $1A, $1B, $1C, $1D, $1E, $1F, $1F, $20, $21, $22, $23
    dc.b $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F, $2f
    dc.b $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3c
    dc.b $3D, $3E, $3F, $3F, $40, $41, $42, $43, $44, $45, $46, $47, $48
    dc.b $49, $4A, $4B, $4C, $4D, $4E, $4F, $4F, $50, $51, $52, $53, $54
    dc.b $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F, $5F, $60
    dc.b $61, $62, $63, $64, $65
    ; ===========================================================================

    The table was generated by a small python program I just wrote. It creates a formatted, copy-and-paste-able table for your convenience and you just need to change the constants at the beginning to adapt it to any size.
    CODE
    ob2 = 64        # object size in s2
    ob3 = 68        # new object size
    obn = 102        # objects in dynamic object ram
    obr = obn * ob3        # size of dynamic object ram

    I = obr
    n = 0
    while (I > 0):
      I = I - ob2
      if (n % 13 == 0):        # does assembler directive need to be printed?
        print "    dc.b    ",
      if (n % 13 != 12 and I > 0):    # print with comma?
        print (' $'+hex(((obr - I)/ob3 - 1)%256)[2:].upper()+',')[-4:],
      else:                # else print without
        print (' $'+hex(((obr - I)/ob3 - 1)%256)[2:])[-3:],
      n = n + 1

    I think this program is correct, but I'm not entirely sure my math is right.

    ==Done==

    Wow, that was long. This was all taken from my notes, which I hope are complete. Off the top of my head, I can't think of anything that would be missing.
    Sorry it's not all just copy and paste, but the effort is worth it. Also, it can be applied to a work in progress with close to no trouble.
    I apologize for any typos, poor wording or grammar errors.

    ==Thanks==
    Stealth - for making the s&k disasm
    jman - for making me aware of the object manager's existence
    shobiz - for his help in getting me started
    qiuu - for forwarding a request that I get it done and for giving me an alternate solution to expanding the SST

    Note there are some minor issues:
    Even though everything works, one of Super Sonic's transformation frames has garbled tiles in it. I don't know what exactly causes this, but if I add a certain amount of padding in front of Sonic's tile data the problem gets fixed. It's kind of hit and miss right now, so I'd appreciate if someone could provide a definitive solution that works every time. Btw, I can reproduce this in an unmodified s2 by shifting Sonic's tile data slightly.

    Other than that, I can't think of anything else that needs saying. Enjoy!

    Edit: Btw, if there's any issues feel free to ask. I had to go through a lot of trouble before I could get this to work for the first time, so I don't demand anyone else get it right the first time, either.
  4. LZ water ripple in s2

    21 June 2008 - 05:52 PM

    I will now show you how to add the water ripple effect from LZ to theoretically any zone in s2.
    Don't get too excited yet, though. If you've played the betas you might remember the effect being there. So why was it taken out of the final? Because things can get really slow, that's why.

    First, let me begin by explaining how the ripple effect is achieved. All you really need to know is that horizontal scrolling is handeled by an array of longwords beginning at $FFFFE000. Each entry holds the scroll factor of a single horizontal line of pixels going from top to bottom whereas the high word is for the foreground and the low word for the background. This is explained in more detail by qiuu in the S2:AE thread (thanks, btw). The water ripple basically just manipulates the array so that some lines are shifted slightly to one side or the other. How far each line is shifted is stored in a table. For the foreground it's stored in 'Deform_LZ_Data1' and the background uses the wobble data from the bubble object.

    So let's begin, shall we?
    First, just so you know, im using Xenowhirl's 2007 disassembly.
    Now, we'll be adding to the software scrolling engine, so it would make sense to put our code in there. Since CPZ and ARZ are the only two zones that use water in s2 it would be best to put it somewhere inbetween 'SwScrl_CPZ' and 'SwScrl_ARZ'.

    SwScrl_Water:
    	; this adds the LZ water ripple effect to any level
    	lea	(Deform_LZ_Data1).l,a3
    	lea	(Obj0A_WobbleData).l,a2
    	move.b	($FFFFF7D8).w,d2
    	move.b	d2,d3
    	addi.w	#$80,($FFFFF7D8).w ; '€'
    	add.w	(Camera_Bg_Y_pos).w,d2
    	andi.w	#$FF,d2
    	add.w	(Camera_Y_pos).w,d3
    	andi.w	#$FF,d3
    	lea	(Horiz_Scroll_Buf).w,a1
    	move.w	#$DF,d1	; 'ß'
    	move.w	(Water_Level_1).w,d4
    	move.w	(Camera_Y_pos).w,d5
    
    

    I'll stop here real quick because I need to tell you that I added an equate to the disassembly to enhance readability. More specifically, it's 'Camera_Bg_Y_pos' which is located at $FFFFEE0C. You'l need to add that equate for it to work. best put it right underneath 'Camera_Y_pos' so it looks something like this:
    Camera_RAM =		ramaddr( $FFFFEE00 )
    Camera_X_pos =		ramaddr( $FFFFEE00 )
    Camera_Y_pos =		ramaddr( $FFFFEE04 )
    Camera_Bg_Y_pos =	ramaddr( $FFFFEE0C )
    
    

    So, what does this first segment do? We load the two tables containing the deformation data into a3 and a2. $FFFFF7D8 stores the 'phase' of the the ripple. Without this there'd still be a ripple, but it wouldn't move. We add $80 to it every frame which means the phase increases every second frame (level coordinates are stored as words and $80 is 1/2 of $100). The phase is copied once to d2 which is for the background and once to d3 which is for the foreground. These two registers will be used later for vertical position of the ripple plus it's phase. Both our tables of ripple data are 256 bytes long, so to avoid flowing into other data we need to use something like x mod 256. Since 256 is a power of 2 we can just and with $FF. We store the Hscroll buffer in a1. If we didn't do that all would be for nothing as we couldn't even make the ripple show without it. We move $DF (223) to d1 which is how many lines there are minus 1 (consider it a do-while statement, where the code is always executed at least once even when the condition isn't true).

    Moving on
    -	; as long as the camera is above the water
    	cmp.w	d4,d5			; is camera below water?
    	bge.s	SwScrl_Water_doRipple	; if yes, branch
    	addq.w	#4,a1		; increment pointer
    	addq.w	#1,d5		; increment camera y pos
    	addq.b	#1,d2
    	addq.b	#1,d3
    	dbf	d1,-
    	rts
    
    

    Here we start checking all 224 lines from to to bottom. As long as the line we're checking isn't under water we just increment the the pointer in a1 (to the Hscroll buffer. This basically means we're not changing the horizontal position of the current line and moving on to the next). d4 and d5 are the current water level and the camera y position, respectively. We increment all of our y position counters so that in the next loop we'll be looking at the line below the current one. If there's no water in sight we've basically done nothing (except waste valuable processor time). Once we reach a line that's below the water surface we branch to the next part of the program.

    	; does the LZ water ripple effect once the camera is below the water
    SwScrl_Water_doRipple:
    	move.b	(a3,d3.w),d4	; FG ripple effect
    	ext.w	d4
    	add.w	d4,(a1)+
    
    	move.b	(a2,d2.w),d4	; BG ripple effect
    	ext.w	d4
    	add.w	d4,(a1)+
    	addq.b	#1,d2
    	addq.b	#1,d3
    	dbf	d1,SwScrl_Water_doRipple
    	rts
    

    Here we finally make the water ripple happen. Using a3 and a2 as indexes for our tables we retrieve the appropriate values for the current line (remember how we added 1/2 of $100 to the phase earlier? That becomes significant here, because we're just using the high byte of the y position, which only increases every second frame). These values are then added to the current scroll factor of the line we're focusing on. We then increment our counters like before and repeat until we reach the last line.

    Deform_LZ_Data1:
    	dc.b   1,  1,  2,  2,  3,  3,  3,  3,  2,  2,  1,  1,  0,  0,  0,  0; 0
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 16
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 32
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 48
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 64
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 80
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 96
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 112
    	dc.b $FF,$FF,$FE,$FE,$FD,$FD,$FD,$FD,$FE,$FE,$FF,$FF,  0,  0,  0,  0; 128
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 144
    	dc.b   1,  1,  2,  2,  3,  3,  3,  3,  2,  2,  1,  1,  0,  0,  0,  0; 160
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 176
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 192
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 208
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 224
    	dc.b   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0; 240
    

    Here's the ripple data so you don't have to look for it in s1.

    There, now that you understand how the water ripple effect works you might be wondering how to use it. Simple. All you have to do is locate the code for the zone you want to add the ripple to (they all start with 'SwScrl') and replace all the rts with branches to our code (bra, not bsr, jmp if it's too far away). Done.
    At least, I think that's all you have to do. There might be some zones that branch to somewhere outside the main code and don't return.
    Note that CPZ only has water in act 2, so you'll need to add a short test for that.

    I'd like to warn you again that this will slow down your hack noticably if you have lots of objects on screen. As long as you are playing alone it should be okay in s2, but if you're playing as Sonic and Tails the slowdown will be pretty bad.
    I have no idea how s3 manages to do that with collapsing ledges, brakable floors, animated tiles, Sonic and Tails and generally lots of sprites on screen without any slowdown at all.

    Btw, does anyone have the AIZ ripple data? I tried to find it but came up empty handed.

    If this is satisfactory, I'd like to ask one of the wiki guys to add this to the how-tos section. That is, if there aren't any objections.

    Here's some images for demonstrational purposes:
    Posted Image CPZ
    Posted Image ARZ

    Download
  5. Asm stuff I've done so far...

    04 October 2007 - 02:28 PM

    As I said in the introduction topic I've recently started asm hacking Sonic 2. What I've done so far is fix a few small bugs that have always bothered me. I'd like to show some of the edits I have made and maybe get some feedback on what I might be doing wrong as well as a few explanations of things I don't quite understand.

    I'm using the 2007 disasm.
    First, there's transforming to Super Sonic under water. What would happen is you'd get his above water stats. While it's fun to be fast under water it's just wrong. Strangely enough, they thought about transforming back to Sonic.
    Sonic_CheckGoSuper:
    	tst.b	(Super_Sonic_flag).w	; is Sonic already Super?
    	bne	return_1ABA4		; if yes, branch	;+;had to make bigger jump
    	cmpi.b	#7,(Emerald_count).w	; does Sonic have exactly 7 emeralds?
    	bne	return_1ABA4		; if not, branch	;+;make jump bigger
    	cmpi.w	#50,(Ring_count).w	; does Sonic have at least 50 rings?
    	bcs.s	return_1ABA4		; if not, branch
    
    	move.b	#1,(Super_Sonic_palette).w
    	move.b	#$F,(Palette_frame_count).w
    	move.b	#1,(Super_Sonic_flag).w
    	move.b	#$81,obj_control(a0)
    	move.b	#$1F,anim(a0)			; use transformation animation
    	move.b	#$7E,(Object_RAM+$2040).w	; load Obj7E (super sonic stars object) at $FFFFD040
    	move.w	#$A00,(Sonic_top_speed).w
    	move.w	#$30,(Sonic_acceleration).w
    	move.w	#$100,(Sonic_deceleration).w
    	btst	#6,status(a0)
    	beq	+
    	move.w	#$500,(Sonic_top_speed).w
    	move.w	#$18,(Sonic_acceleration).w
    	move.w	#$80,(Sonic_deceleration).w
    +
    	move.w	#0,invincibility_time(a0)
    	bset	#1,status_secondary(a0)	; make Sonic invincible
    	move.w	#$5F+$80,d0
    	jsr	(PlaySound).l	; Play transformation sound effect.
    	move.w	#$16+$80,d0
    	jmp	(PlayMusic).l	; load the Super Sonic song and return
    
    


    All I did was add
    	btst	#6,status(a0)
    	beq	+
    	move.w	#$500,(Sonic_top_speed).w
    	move.w	#$18,(Sonic_acceleration).w
    	move.w	#$80,(Sonic_deceleration).w
    +
    


    What it does is it checks for status bit 6 which determines wheather Sonic is under water or not. If he is his stats are changed to his Super Sonic under water stats. My only question here is what the (a0) is for after status. Sometimes it's a1, so I'm a little confused.

    Then there's removing the air speed cap which is still present in Sonic 2.
    I'm not going to post the entire code for this here, because there's a guide (which I followed) by Tweaker and Puto for doing this in Sonic 1. It's the same thing for Sonic 2, so it was an easy edit.
    Code:
    	add.w	d5,d0	; remove this frame's acceleration change
    	cmp.w	d1,d0	; compare speed with top speed
    	ble.s	+	; if speed was already greater than the maximum, branch
    

    OK, here I'm not sure about the first line, but the second and third lines are what bypass the speed cap.

    The next one is from Sonic CD, but it's actually not a bugfix. In Sonic_Jump comment out these two lines
    	btst	#2,status(a0)
    	bne.s	Sonic_RollJump
    

    and you will regain control if you jump while rolling. I always thought the "rolljump", as it's called in the disassembly, was a side-effect of how they programmed Sonic's rolling, but they actually have code to specifically deal with rolljumping. That code can probably be removed after commenting out those two lines.

    The last thing I've got for now is an addition to debug mode. Well, actually it's just restoring a debug feature that was removed for Sonic 2 Final.
    In Level_InitWater:
    	btst	#5,(Ctrl_1_Held).w	;hold c to activate "night mode"
    	beq.s	+
    	move.w	#$8C89,(a6)
    +
    	btst	#4,(Ctrl_1_Held).w	;hold b to activate split screen
    	beq.s	+
    	move.w	#1,(Two_player_mode).w
    +
    	btst	#6,(Ctrl_1_Held).w	;+;hold a to activate debug mode
    	beq.s	loc_4012
    	move.b	#1,(Debug_mode_flag).w
    

    I added the lines
    	btst	#4,(Ctrl_1_Held).w	;hold b to activate split screen
    	beq.s	+
    	move.w	#1,(Two_player_mode).w
    +
    

    They check if the player is holding down the B button during level initiation and set the Two_Player_Mode flag. The only level that really works, except for the normal 2p stages, is HTZ act 1. The others will have screwed up mappings and/or missing/not working objects. While I mostly did this for fun, I do plan on extending 2p mode once I understand some more asm. I know, I know, 2p mode and water don't mix, but I was thinking, maybe you could get it to work if you disabled the water palettes for 2p mode. There's also HTZ's earthquake areas and MTZ's vertical looping, but they both kind of work. There's most likely no fixing either one of them, but let's wait and see.

    Whew, that's it for now. I might have more tomorrow or on the weekend.

Friends