- 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:
Nov 26 2014 09:17 PM- Currently:
- Offline
My Information
- Age:
- 27 years old
- Birthday:
- July 5, 1988
- Gender:
-
Male
Contact Information
- E-mail:
- Private
- Website:
-
http://
Previous Fields
- Project:
- Procrastinating from writing bug-fix guides
- National Flag:
- de
- Wiki edits:
- 52
Latest Visitors
-
Varion Icaria 
30 May 2015 - 23:52 -
Hitaxas 
17 Apr 2015 - 00:58 -
snkenjoi 
17 Nov 2014 - 21:18 -
Xeta 
20 Sep 2014 - 10:56 -
KingofHarts 
09 Sep 2014 - 02:27
Topics I've Started
-
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. -
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: ASMSonic_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. -
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: ASMLoadObj:
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: ASMlea (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: ASMmove.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: ASMmarkObj_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: ASMlea (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: ASMmove.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: ASMmarkObj_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: ASMtst.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: ASMtst.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: ASMmove.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: ASMmovea.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: ASMSingleObjLoad2:
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.
CODEob2 = 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. -
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:
CPZ
ARZ
Download -
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 addbtst #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.

Find My Content
Nov 26 2014 09:17 PM
Male