I've been looking at these types of threads for a while now, and I've got to ask something. Just what exactly do you guys do to discover these kinds of obscure bugs? I mean, I've played through these games a bazillion times, and I've never heard of that last one.
I've never heard of that last one either, and its well worthy of the SCHG. Nice find Markey. Anyway, speaking for myself at least and assuming for most others, we play through, but not in the traditional sense of playing through... We also try to look at what exactly everything does, and how the game works... and how it DOESN'T work. How it should work, and how it SHOULDN'T work. You start to just find shit. At the start of this year, I knew absolutely dick about the first thing about a disassembly. Now, while I'm far from good at hacking... I come in with some small level of understanding. and even have a hack, a tool and an engine of my own in the works. I assume, based on your post... and I apologize if I am mistaken, that you do not hack much. If that is the case, and you want to really get into this... and it's REALLY FUN, I suggest that you do what I did when I started. Grab the latest Sonic 1 disassembly, and start following some of the How To guides, all the while taking the time to try and understand how/why exactly the code works the way it does. Oh, and skim through this thread's history, and the archives... you'll find a gem or two. And ask questions! You can learn a lot from the Tech Members, they are a LOT more experienced with this stuff, and are willing to help, more often than not. Last but not least, take some time to fuck with random shit and see the result. I did that and found this little trick that I will show you now. This can be applied for anyone applying a Time Attack mode. Go to HUD_Update and find this bit of code: Code (ASM): @chktime: tst.b (f_timecount).w ; does the time need updating? beq.w @chklives ; if not, branch (.s -> .w) Mercury Centisecond HUD ; Remove this for a Time Attack mode... OR just remove the pause function. - KingofHarts mod tst.w (f_pause).w ; is the game paused? <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< bne.w @chklives ; if yes, branch (.s -> .w) Mercury Centisecond HUD <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ; Mod end lea (v_time).w,a1 Remove the two lines I noted. What this will do is make the time keep running, even when the game is paused. Now, you will only want this to happen when your Time Attack mode is active, so one thing you could do is implement a flag for Time Attack mode, and add a check for the Time Attack mode flag, and follow it with a bne.s branch to skip the lines I told you to delete. So if you are NOT in Time Attack, Time will stop as normal, otherwise, pausing will not save Time Attack players from a running clock. And to add to it, I'll re-post something I left on SSRG regarding a fix to a SMALL aesthetic bug I found with the Game Over object that caused it to flicker when the words conjoined. Go to _incObj/39 Game Over.asm and go to this block of code and add the branch to DisplaySprite. Code (ASM): ; =========================================================================== Over_SetWait: move.w #720,anim_frame_duration(a0) ; set time delay to 12 seconds addq.b #2,routine(a0) bra.w DisplaySprite ; KoH additional line to prevent blinking. ; ===========================================================================
I donno, I believe the bugs are mainly found by those who are making hacks themselves, because they make changes to the game, some of the bugs that were unnoticed in the original game tend to show up more, and hence, they go to check more carefully in the original game just in case it isn't a bug of their own, and because they know what they're looking for, they find it. I've been working on an in-game level editor, and the camera does not follow Sonic when in editor mode, and when coming out of editor mode, the camera withdrawals itself back to Sonic, thus, I found the bug...
Aha, I remember having that left horizontal scrolling bug in S1EE; I could've sworn jman put the solution up on SCHG too once he found it, but eh. Nicely done, Markey.
That's pretty much it. I discovered one of the Super Sonic transformation bugs in Sonic 2 (the one where the transformation palcycle only works properly the first time around because the RAM value isn't zeroed properly) a few years ago because I was playing around adding new features, double-jump transformation and the like. I might have even posted a quick fix for it here on the forums, though I don't remember very clearly. It's one of the easiest bug fixes in any Sonic game ever, but I still felt like a damn genius when I figured it out.
I still remember my first fix. I made it so that the objects don't freeze when you die, much similar to Sonic CD 2011. Really easy fix, though I DID need help with the second part, as some objects still stopped moving due to the oscillatory routines stopping when Sonic dies. I tend to find things a little differently, just seeing stuff in code as I comb through, and just screwing about figuring out what it all does... Then again, I also didn't have any real hack outside of Revision C going on... though I have started one now.
Got a question regarding the Large Spikeball Object from SYZ (Object $58) Let's compare the codes of subtypes horizontal and vertical... there is a difference that is slightly strange to me. One of these two is doing something it doesn't need to be doing... HORIZONTAL <asm> @type01: ; XREF: @index move.w #$60,d1 moveq #0,d0 move.b (v_oscillate+$E).w,d0 ; load osc value $E btst #0,status(a0) ; is object flipped? beq.s @noflip1 ; if not, branch neg.w d0 ; inverse the osc value add.w d1,d0 ; add $60 to osc value @noflip1: move.w bball_origX(a0),d1 sub.w d0,d1 ; subtract d0 from original x position move.w d1,x_pos(a0) ; move object horizontally rts </asm> VERTICAL <asm> @type02: ; XREF: @index move.w #$60,d1 ; <- WTF??? Why is this here? It's not even used moveq #0,d0 move.b (v_oscillate+$E).w,d0 ; load osc value $E btst #0,status(a0) ; is object flipped? beq.s @noflip2 ; if not, branch neg.w d0 ; inverse the osc value addi.w #$80,d0 ; add $80 to osc value <-HEY why is this not $60? @noflip2: move.w bball_origY(a0),d1 sub.w d0,d1 ; subtract d0 from original y position move.w d1,y_pos(a0) ; move object vertically rts </asm> Anyone want to clear up the difference with me? Why not move the flipped vertical spikeballs in SYZ up slightly, and making the vertical moving code work identically to the horizontal moving code...? I will try this tonight and report back. I'm not sure why they made this so different, especially since that $60 in d1 is not even used in the vertical moving object... and using $80 requires one to move a flipped vertical spikeball slightly lower... but that is simply nitpicking.
I don't know much about Sonic 1, but from the code you've posted, it looks like d1 is not being used (as $60 anyway). Sometimes, when they create the game, they just leave mistakes in there. Either because they were in a rush, or they changed the code a bit and forgot to take something out. I remember in Sonic 2's CNZ boss, there's a useless branch, it went something like this: Code (ASM): loc_31B46: bra.w + + addq.w #1,($FFFFF75C).w move.w ($FFFFF75C).w,d0 andi.w #$3F,d0 I took the branch out, no point in it. Sometimes, you'll find things like this. Everyone makes mistakes. Also, the "move.w #$60,d1" can be changed to "moveq #$60,d1" and by looking at it, it shouldn't mess it up. moveq is faster. Although, if it acts up, just put it back to move.w. Now that's being nit-picky =P
Well I'm thinking of changing the vertical moving object's code to $60 instead of $80, and if you look at the vertical moving object code, it just adds the immediate value to the osc. value, instead of loading it into d1 first... I'm going to just do the same to the horizontal moving object's code... THAT should be even faster, anyway, right? then you don't even need to take the extra step to load anything into d1... which all in all, seems awfully pointless. (Though now I gotta move the vertical mirrored spikeballs up a tad... oh well. That's fine. Looks better on SonLVL this way, anyway)
Adding/subtracting an immediate word value takes 8 cycles. e.g.: Code (ASM): addi.w #$0060,d0 Code (ASM): addi.w #$0080,d0 Moving a word/quick value to a register first takes 4 cycles, while adding/subtracting the register to another takes a further 4 cycles, totalling 8 cycles. e.g.: Code (ASM): moveq #$60,d1 add.w d1,d0 Code (ASM): moveq #$FFFFFF80,d1 sub.w d1,d0 No processing time is saved in this instance, if however, you intend to use the same value more than once in a similar fashion, then it would be recommended to use the register storage method.
Then I'll opt against using a register for this one... since that value is only used once. Thanks for the tip Markey!
Bump, PsychoRFG brought something to my attention which I never noticed before, it seems that if you pause while the music is fading in, and then unpause, the DAC does not resume once the music has fully faded in. After a quick look, it seems that the pause routine not only sets the keys off, but it also turns off the left and right speaker panning for each channel (which makes sense due to the release rate), and unpausing causes the system to reset all of the channel's panning/AMS/FMS values, of course, if the music is going through a fade in, then the DAC channel (a.k.a. FM 6) must remain mute until the music has fully faded, hence, the panning/AMS/FMS value for FM 6 does not get updated while DAC is running, until of course either a new track is played, or the game is paused and unpaused again after fading, or the DAC channel changes speakers during its script. To fix, we need to go to "loc_726D6:" Code (Text): bclr #2,$40(a6) clr.b $24(a6) rts The first instruction clears the fade out mute bit, which was set by the "E4" flag (fade out into previous track), the second instruction clears the fading out flag (telling the system that fading out is complete), the "key on" values are resumed by the system already through natural course, all that's left is to reset the L/R/AMS/FMS of FM6 on the YM2612, like so: Code (Text): loc_726D6: bclr #2,$40(a6) clr.b $24(a6) tst.b $40(a6) ; is the DAC channel running? bpl.s Resume_NoDAC ; if not, branch moveq #$FFFFFFB6,d0 ; prepare FM channel 3/6 L/R/AMS/FMS address move.b $4A(a6),d1 ; load DAC channel's L/R/AMS/FMS value jsr sub_72764(pc) ; write to FM 6 Resume_NoDAC: rts What it does should be obvious, but that's about it, my apologies if this has been solved somewhere else before, I didn't see it in the thread or on the wiki.
Wanted to point out two small bugs... god its been a little while since anyone's done this... not since that big bug-busting boom of last year died down in the fall... ANYWAY First can be found at this link: http://sonicresearch...?showtopic=3422 Not my bug OR fix, but I saw it and will link to it here. SECOND and this one IS one that I noticed and am reporting... There is a SMALL bug with the horizontal springs. If you run or spin into a horizontal spring facing away from it (which is not that likely to happen, though it does happen at times), Sonic will face towards the spring instead of away from it like he is supposed to. This is because when Sonic hits the horizontal spring, bit 0 (Facing direction) of his status variable is changed from 0 to 1, or vice versa. You can see the code here. Code (ASM): Spring_Flipped: move.w #$F,move_lock(a1) move.w x_vel(a1),inertia(a1) bchg #0,status(a1) ; <----- There's the culprit! btst #2,status(a1) bne.s loc_DC56 move.b #id_Walk,anim(a1) ; use running animation Here is my proposed fix. If there is a more efficient way of handling this please teach me. Code (ASM): Spring_BounceLR: addq.b #2,routine(a0) move.w spring_pow(a0),x_vel(a1) ; move Sonic to the left addq.w #8,x_pos(a1) btst #0,status(a0) ; is object flipped? bne.s Spring_Flipped ; if yes, branch subi.w #$10,x_pos(a1) neg.w x_vel(a1) ; move Sonic to the right Spring_Flipped: move.w #$F,move_lock(a1) move.w x_vel(a1),inertia(a1) ; KingofHarts spring direction fix cmp.w #0,x_vel(a1) bmi.s Face_Left ; if Sonic is running left, branch bclr #0,status(a1) bra.s Face_cont ;bchg #0,status(a1) ; Removed this (KingofHarts spring direction fix) Face_Left: bset #0,status(a1) Face_Cont: ; End of fix btst #2,status(a1) bne.s loc_DC56 move.b #id_Walk,anim(a1) ; use running animation Basically, my fix has the code set or clear the status bit, based on the sign of x speed, rather than simply flip-flopping it. This allows for Sonic to ALWAYS face the correct direction. Haven't bothered to see if the bug occurs in Sonic 2 or 3K yet BTW. and AGAIN, please let me know if there is a better way to do this.
Noticed a small omission with the following fix: The walk-jump set of animation fixes Spoiler Adding the following 2 lines as noted will prevent Sonic dying in the walking animation (likely due to crushing): Code (Text): Solid_Ignore: btst #staPush,status(a0) ; is Sonic pushing? beq.s Solid_Debug ; if not, branch ; Walking animation fixes (REV C Edit) cmp.b #id_Roll,anim(a1) ; check if in jumping/rolling animation beq.s Solid_NotPushing cmp.b #id_Drown,anim(a1) ; check if in drowning animation beq.s Solid_NotPushing cmp.b #id_Hurt,anim(a1) ; check if in hurt animation beq.s Solid_NotPushing cmp.b #id_Death,anim(a1) ; check if in death animation <--- NEW KoH addition beq.s Solid_NotPushing ;<--- NEW KoH addition Also add those two lines to the Monitor code (follow the guide I linked to, to know where to place these 2 new lines) Disregard this note I made above. Taking a note from ReadySonic, there is a much quicker and easier way to do this, than the complicated mess that currently sits in the SCHG. Remove these lines in _incObj\sub SolidObject.asm, Solid_Ignore... Code (Text): btst #5,obStatus(a0) ; is Sonic pushing? beq.s Solid_Debug ; if not, branch ;move.w #id_Run,obAnim(a1) ; use running animation REMOVE THIS LINE ... _incObj\26 Monitor.asm, loc_A25C... Code (Text): btst #5,obStatus(a0) beq.s Mon_Animate ;move.w #1,obAnim(a1) REMOVE THIS LINE ...and sonic.asm, loc_8AA8... Code (Text): btst #5,obStatus(a0) beq.s locret_8AC2 ;move.w #id_Run,obAnim(a1) REMOVE THIS LINE Afterwards, simply need to fix one more thing, and that is the pushing while walking bug. If Sonic rolls or moves too fast into a wall or solid object and then quickly turns around, he'll often move away from it while using his "push" animation instead of the proper "walk" animation. The Solution: Sonic 2 solves this by clearing Sonic's "pushing" status bit whenever his animation is supposed to reset. In _incObj\Sonic Animate.asm, Sonic_Animate: you'll find this code. Add the noted line: Code (Text): move.b d0,obNextAni(a0) ; set to "no restart" move.b #0,obAniFrame(a0) ; reset animation move.b #0,obTimeFrame(a0) ; reset frame duration bclr #5,obStatus(a0) ; clear pushing flag ; ADD THIS LINE @do: NOTE: Please excuse any <div>'s... AND credit to Mercury's ReadySonic for this fix
Bump for a new fix to an old bug. The S-tunnel bug in GHZ 1. Go too fast and it kills you. Why? Because... the bottom boundary collision routine is checking the current boundary instead of the intended one... Well, that fixes that... BUT WAIT, we've caused another new, and unnecessary problem! We've got to move around a bunch of DLE's because Sonic is instantly dying in midair for no reason. (Also happens in Sonic 3.) WHY? Because the intended lower boundary got moved up... and THAT's the one that is being checked... that's just not right either... My solution: Code (Text): @chkbottom: ; Recoded to suit my own preference. Allow Sonic to outrun camera, ALSO prevent sudden deaths. (REV C Edit) move.w (v_limitbtm2).w,d0 ; current bottom boundary=d0 cmp.w (v_limitbtm1).w,d0 ; is the intended bottom boundary lower than the current one? bcc.s @notlower ; if not, branch move.w (v_limitbtm1).w,d0 ; intended bottom boundary=d0 @notlower: addi.w #$E0,d0 cmp.w y_pos(a0),d0 ; has Sonic touched the bottom boundary? blt.s @bottom ; if yes, branch rts Now it only checks the intended bottom boundary if the boundary is moving down... allowing for Sonic to outrun the camera with no issues... If the boundary is moving up, or just not moving, then it acts as it originally intended, aka no more dying in midair. IMHO THIS should be the bottom boundary routine for all 3 games. Lemme know what you think.
One instance of going past the rocks in AIZ 1 into Knuckles path... now to be fair, it is up to the user to fix the DLE's so that doesn't happen, if you wanna have Sonic be able to go down there... BUT if it's gonna kill me, I wanna see it coming, and not have the shit scared outta me. (For some reason, dying unexpectedly in a Sonic game scares me. It's something that has scarred me for life since I was a tot.) I just think this is a better route to take, especially for players that like to go into debug and mess about or wander around. Like I said, it's my own preference, maybe people won't feel it is necessary.
Just wondering, why would you want to prevent players from padding their score by dying? If I considered that to be cheating, I'd just not do it, not find/make a ROM that doesn't allow it.
It's really more for how to do it if one wanted to... as I pointed out back in this thread. If one has a hack that would maybe mandate such a thing, such as Endri's hack, for instance, you might want something like this... For vanilla Sonic 1, I agree... it's not necessary. Those posts were really more a result of me experimenting and toying around, trying to learn... than anything to be taken seriously for vanilla Sonic 1 use. That said, its there for anyone to try if they want.
I don't know if this belongs here, but it is possible to jump off to your death in Final Zone after defeating the boss (if you jump at the right moment before the game locks your movement). Though I believe this is known already, what would be the best way to go about correcting this? Personally, I 'fixed' it in my hack by just making the bottom boundary in Final Zone send you to the ending: Code (Text): @bottom: cmpi.w #(id_SBZ<<8)+2,(v_zone).w ; is level FZ ? beq.s @next cmpi.w #(id_SBZ<<8)+1,(v_zone).w ; is level SBZ2 ? bne.w @killsonic ; if not, kill Sonic cmpi.w #$2000,(v_player+obX).w bcs.w @killsonic clr.b (v_lastlamp).w ; clear lamppost counter move.w #1,(f_restart).w ; restart the level move.w #(id_LZ<<8)+3,(v_zone).w ; set level to SBZ3 (LZ4) rts @next: move.b #id_Ending,(v_gamemode).w rts But what would be the 'ideal' way of fixing this problem?