I've noticed that certain palette indexes are crashing the game; either I've discovered a new bug, or my ROM hack is trying to fight back against me; whatever the case, here's the index, in case someone figures out why the issue occurs. Code (Text): PalPointers: PalPtr_SEGA: palptr Pal_SEGA, 0 PalPtr_Title: palptr Pal_Title, 1 PalPtr_BGND: palptr Pal_BGND, 0 PalPtr_EHZ: palptr Pal_EHZ, 1 PalPtr_EHZ2: palptr Pal_EHZ2, 1 PalPtr_OWZ1: palptr Pal_OWZ1, 1 PalPtr_OWZ2: palptr Pal_OWZ2, 1 PalPtr_WZ: palptr Pal_WZ, 1 PalPtr_SSZ1: palptr Pal_SSZ1, 1 PalPtr_SSZ2: palptr Pal_SSZ2, 1 PalPtr_MTZ: palptr Pal_MTZ, 1 PalPtr_MTZ2: palptr Pal_MTZ, 1 PalPtr_WFZ: palptr Pal_WFZ, 1 PalPtr_HTZ: palptr Pal_HTZ, 1 PalPtr_HPZ: palptr Pal_HPZ, 1 PalPtr_RWZ: palptr Pal_RWZ, 1 PalPtr_OOZ: palptr Pal_OOZ, 1 PalPtr_MCZ: palptr Pal_MCZ, 1 PalPtr_CNZ: palptr Pal_CNZ, 1 PalPtr_CPZ: palptr Pal_CPZ, 1 PalPtr_CPZ2: palptr Pal_CPZ2, 1 PalPtr_DEZ: palptr Pal_DEZ, 1 PalPtr_ARZ: palptr Pal_ARZ, 1 PalPtr_ARZ2: palptr Pal_ARZ2, 1 palptr Pal_EHZ, 1 ; for some reason, this specific entry crashes the game... PalPtr_SCZ: palptr Pal_SCZ, 1 PalPtr_OWZ1_U: palptr Pal_OWZ1_U, 1 PalPtr_HPZ_U: palptr Pal_HPZ_U, 0 PalPtr_CPZ_U: palptr Pal_CPZ_U, 0 PalPtr_SS: palptr Pal_SS, 0 PalPtr_ARZ_U: palptr Pal_ARZ_U, 0 PalPtr_ARZ2_U: palptr Pal_ARZ2_U, 0 PalPtr_MCZ_B: palptr Pal_MCZ_B, 1 PalPtr_CNZ_B: palptr Pal_CNZ_B, 1 PalPtr_SS1: palptr Pal_SS1, 3 PalPtr_SS2: palptr Pal_SS2, 3 PalPtr_SS3: palptr Pal_SS3, 3 PalPtr_SS4: palptr Pal_SS4, 3 PalPtr_SS5: palptr Pal_SS5, 3 PalPtr_SS6: palptr Pal_SS6, 3 PalPtr_SS7: palptr Pal_SS7, 3 PalPtr_SS1_2p: palptr Pal_SS1_2p,3 PalPtr_SS2_2p: palptr Pal_SS2_2p,3 PalPtr_SS3_2p: palptr Pal_SS3_2p,3 PalPtr_OOZ_B: palptr Pal_OOZ_B, 1 PalPtr_Result: palptr Pal_Result,0 palptr Pal_EHZ, 1 ; this too! palptr Pal_EHZ, 1 ; this too! palptr Pal_EHZ, 1 ; this too! PalPtr_Knux: palptr Pal_Knux, 0 PalPtr_CPZ_K_U: palptr Pal_CPZ_K_U, 0 PalPtr_ARZ_K_U: palptr Pal_ARZ_K_U, 0 PalPtr_SS_K: palptr Pal_SS_K, 0 PalPtr_HPZ_K_U: palptr Pal_HPZ_K_U, 0 ; MENUS GO HERE PalPtr_SonicMenu: palptr Pal_SonicMenu, 0 PalPtr_TailsMenu: palptr Pal_TailsMenu, 0 PalPtr_KnuxMenu: palptr Pal_KnuxMenu, 0 PalPtr_EggmanMenu: palptr Pal_EggmanMenu, 0 PalPtr_RetroMenu: palptr Pal_RetroMenu, 0 PalPtr_Ending: palptr Pal_Ending, 0
I'm not sure what you exactly mean by "crashing the game". Are you trying to load those Pal_EHZ entries? The only issue that I came across was with "levartptrs" not being able to take into account any additional operators without surrounding it with parenthesis (i.e. you can't put in PalID_EHZ+1, because it gets assembled as "PalID_EHZ+1<<24", which is PalID_EHZ+$1000000). To fix that alongside any other potential issues, I changed: Code (Text): dc.l (plc1<<24)|art dc.l (plc2<<24)|map16x16 dc.l (palette<<24)|map128x128 to Code (Text): dc.l ((plc1)<<24)|(art) dc.l ((plc2)<<24)|(map16x16) dc.l ((palette)<<24)|(map128x128)
I assume "crashing the game" refers to a 68K exception occurring when you try to use those palette indices, but without knowing what kind of exception (address error, illegal instruction, etc.) is occurring, I would not know where to start looking. Are you using an exception handler in your hack?
No, as in it crashes if ANYTHING tries to load those entries. I wasn't, although that gave me the idea to import the Sonic 1 error exception to see what comes up; I got Line 1111 Emulator $00000001 as my result.
Line 1111 Emulator exception at offset $00000001 (part of the initial stack pointer value in the vector table). Seems something is causing those pointers to jump to invalid code instead of their intended destination.
Strange, as the broken entries are broken regardless of what's there, even with a generic EHZ entry. PalLoad function.
Please post some code/be more specific, because otherwise, I'm lost. I wanna see how EXACTLY you are doing it. It's possible that there's something about it you are not catching. So far, you've only really told me what you are intending to do, but not what's actually being done.
Code (Text): moveq #$36,d0 ; load Knuckles' palette index + bsr.w PalLoad_Now ; load Sonic's palette line The code used to load the palette; and yes, I did also use move.b and even move.w to load it instead, but it still crashes.
Okay, so a couple of things: Code (Text): PalPtr_SEGA: palptr Pal_SEGA, 0 ; 00 PalPtr_Title: palptr Pal_Title, 1 ; 01 PalPtr_BGND: palptr Pal_BGND, 0 ; 02 PalPtr_EHZ: palptr Pal_EHZ, 1 ; 03 PalPtr_EHZ2: palptr Pal_EHZ2, 1 ; 04 PalPtr_OWZ1: palptr Pal_OWZ1, 1 ; 05 PalPtr_OWZ2: palptr Pal_OWZ2, 1 ; 06 PalPtr_WZ: palptr Pal_WZ, 1 ; 07 PalPtr_SSZ1: palptr Pal_SSZ1, 1 ; 08 PalPtr_SSZ2: palptr Pal_SSZ2, 1 ; 09 PalPtr_MTZ: palptr Pal_MTZ, 1 ; 0A PalPtr_MTZ2: palptr Pal_MTZ, 1 ; 0B PalPtr_WFZ: palptr Pal_WFZ, 1 ; 0C PalPtr_HTZ: palptr Pal_HTZ, 1 ; 0D PalPtr_HPZ: palptr Pal_HPZ, 1 ; 0E PalPtr_RWZ: palptr Pal_RWZ, 1 ; 0F PalPtr_OOZ: palptr Pal_OOZ, 1 ; 10 PalPtr_MCZ: palptr Pal_MCZ, 1 ; 11 PalPtr_CNZ: palptr Pal_CNZ, 1 ; 12 PalPtr_CPZ: palptr Pal_CPZ, 1 ; 13 PalPtr_CPZ2: palptr Pal_CPZ2, 1 ; 14 PalPtr_DEZ: palptr Pal_DEZ, 1 ; 15 PalPtr_ARZ: palptr Pal_ARZ, 1 ; 16 PalPtr_ARZ2: palptr Pal_ARZ2, 1 ; 17 palptr Pal_EHZ, 1 ; 18 PalPtr_SCZ: palptr Pal_SCZ, 1 ; 19 PalPtr_OWZ1_U: palptr Pal_OWZ1_U, 1 ; 1A PalPtr_HPZ_U: palptr Pal_HPZ_U, 0 ; 1B PalPtr_CPZ_U: palptr Pal_CPZ_U, 0 ; 1C PalPtr_SS: palptr Pal_SS, 0 ; 1D PalPtr_ARZ_U: palptr Pal_ARZ_U, 0 ; 1E PalPtr_ARZ2_U: palptr Pal_ARZ2_U, 0 ; 1F PalPtr_MCZ_B: palptr Pal_MCZ_B, 1 ; 20 PalPtr_CNZ_B: palptr Pal_CNZ_B, 1 ; 21 PalPtr_SS1: palptr Pal_SS1, 3 ; 22 PalPtr_SS2: palptr Pal_SS2, 3 ; 23 PalPtr_SS3: palptr Pal_SS3, 3 ; 24 PalPtr_SS4: palptr Pal_SS4, 3 ; 25 PalPtr_SS5: palptr Pal_SS5, 3 ; 26 PalPtr_SS6: palptr Pal_SS6, 3 ; 27 PalPtr_SS7: palptr Pal_SS7, 3 ; 28 PalPtr_SS1_2p: palptr Pal_SS1_2p,3 ; 29 PalPtr_SS2_2p: palptr Pal_SS2_2p,3 ; 2A PalPtr_SS3_2p: palptr Pal_SS3_2p,3 ; 2B PalPtr_OOZ_B: palptr Pal_OOZ_B, 1 ; 2C PalPtr_Result: palptr Pal_Result,0 ; 2D palptr Pal_EHZ, 1 ; 2E palptr Pal_EHZ, 1 ; 2F palptr Pal_EHZ, 1 ; 30 PalPtr_Knux: palptr Pal_Knux, 0 ; 31 PalPtr_CPZ_K_U: palptr Pal_CPZ_K_U, 0 ; 32 PalPtr_ARZ_K_U: palptr Pal_ARZ_K_U, 0 ; 33 PalPtr_SS_K: palptr Pal_SS_K, 0 ; 34 PalPtr_HPZ_K_U: palptr Pal_HPZ_K_U, 0 ; 35 PalPtr_SonicMenu: palptr Pal_SonicMenu, 0 ; 36 PalPtr_TailsMenu: palptr Pal_TailsMenu, 0 ; 37 PalPtr_KnuxMenu: palptr Pal_KnuxMenu, 0 ; 38 PalPtr_EggmanMenu: palptr Pal_EggmanMenu, 0 ; 39 PalPtr_RetroMenu: palptr Pal_RetroMenu, 0 ; 3A PalPtr_Ending: palptr Pal_Ending, 0 ; 3B Looks like you miscounted, or you changed the palette table. One more question, from where is this being run at? Is it from an object?
It's just the routine that loads Sonic's (or, in this case, Knuckles') palette when starting the level (Level_LoadPal).
Only thing I can think of is for you to go to where the stack pointer register points to in RAM, and follow the path of addresses that are pushed from calls/interrupts to determine where it's exactly crashing from, with the help of your listing output (note that interrupts/exceptions push the return address and the status register (which will look something like $23xx or $27xx in Sonic 2), so keep that in mind. Also if you are using vladikcomper's debugger, the stack trace starting from SP is already there at the bottom of the screen). Might be helpful to post the rest of what's under Level_LoadPal, maybe. It might be better to move this into Basic Q&A or a separate thread, to be honest. Kinda clogging up this thread.
Fixing the Pitch of the Sega Sound This is one that I am absolutely amazed no one ANYWHERE has seemed to have noticed, much less discussed: the pitch of the SEGA Sound in Sonic 2 is just a tiny bit higher pitched than it should be. Whether it is intentional or not, I don't know, but I found it impossible to ignore. Thankfully, it is actually really easy to restore it to match Sonic 1 and 3K. In the sound driver, find this section under zPlaySegaSound: Code (Text): .loop: ld a,(hl) ; Get next PCM byte ld (zYM2612_D0),a ; Send to DAC inc hl ; Advance pointer nop ld b,0Ch ; Sega PCM pitch djnz $ ; Delay loop nop ld a,(zAbsVar.QueueToPlay) ; Get next item to play cp c ; Is it 80h? jr nz,.stop ; If not, stop Sega PCM ld a,(hl) ; Get next PCM byte ld (zYM2612_D0),a ; Send to DAC inc hl ; Advance pointer nop ld b,0Ch ; Sega PCM pitch djnz $ ; Delay loop nop dec de ; 2 less bytes to play ld a,d ; a = d or e ; Is de zero? jp nz,.loop ; If not, loop Delete all four of the nops, and change both instances of '0Ch' to '0Dh'. That's it. (This does appear to be safe for real hardware; the 2612 in my Model 1 VA6 handles it without any problems.) Fix Erratic Behavior of Various Badniks in Debug Mode In debug mode, quite a few Badniks will still track Sonic while he is an object, while others will behave erratically, such as Grabbers moving up and down wildly. This is caused by Obj_GetOrientationToPlayer not checking for debug mode. This is also a simple fix: Code (Text): Obj_GetOrientationToPlayer: moveq #0,d0 moveq #0,d1 lea (MainCharacter).w,a1 ; a1=character move.w x_pos(a0),d2 sub.w x_pos(a1),d2 mvabs.w d2,d4 ; absolute horizontal distance to main character lea (Sidekick).w,a2 ; a2=character move.w x_pos(a0),d3 sub.w x_pos(a2),d3 mvabs.w d3,d5 ; absolute horizontal distance to sidekick cmp.w d5,d4 ; get shorter distance bls.s ++ ; branch, if main character is closer Above the 'cmp.w d5,d4', insert these two lines: Code (Text): tst.w (Debug_placement_mode).w ; is debug mode active? bne.s + ; if so, treat sidekick as closer There are quite a few other similar bugs in debug mode in both Sonic 1 and 2 that can be fixed by inserting those two lines in various locations.
You know how in 2-player mode, whenever a teleport monitor is hit, the background color never changes? Well, turns out it's the result of a single line of code; in Vint_Level, you should see something similar to this: Code (Text): Vint_Level: bsr.w ReadJoypads tst.b (Teleport_timer).w beq.s loc_6F8 lea (VDP_control_port).l,a5 tst.w (Game_paused).w ; is the game paused ? bne.w loc_748 ; if yes, branch subq.b #1,(Teleport_timer).w bne.s + move.b #0,(Teleport_flag).w + cmpi.b #$10,(Teleport_timer).w blo.s loc_6F8 lea (VDP_data_port).l,a6 move.l #vdpComm($0000,CRAM,WRITE),(VDP_control_port).l move.w #$EEE,d0 move.w #$1F,d1 - move.w d0,(a6) dbf d1,- That $1F should be changed to $20, and now the color changes correctly.
I don't think that's correct. Here's the complete code: Code (ASM): lea (VDP_data_port).l,a6 move.l #vdpComm($0000,CRAM,WRITE),(VDP_control_port).l move.w #$EEE,d0 ; White. move.w #32-1,d1 - move.w d0,(a6) dbf d1,- ; Skip a colour. move.l #vdpComm($0042,CRAM,WRITE),(VDP_control_port).l if fixBugs move.w #31-1,d1 else ; This does one more colour than necessary: it isn't accounting for ; the colour that was skipped earlier! move.w #32-1,d1 endif - move.w d0,(a6) dbf d1,- What this does is fill the first two palette lines with white, skip the first colour of the third palette line, and then set the remaining colours to white. Skipping that one colour is deliberate. There is, however, a small bug where the first colour of the first palette line is set to white twice because of an incorrect loop counter.
It's well-documented that the title cards on water levels in the game have a minor visual defect: they are drawn with the level's water palette for a single frame. A small issue, but no issue is too small to fix as far as I'm concerned. Stepping through the level load code with Exodus, the cause appears to be a race condition related to disabling interrupts while HBlank is enabled and set to run on scanline 224 (i.e., at the bottom of the screen). The latter normally causes HBlank and then VBlank to be run in that order (as evidenced by the different background color visible in the overscan at the bottom of the screen in CPZ2). However, if the end of a frame is reached while interrupts are disabled, things fall apart: the two interrupts are queued and processed after interrupts are reenabled, and as VBlank has a higher priority (6) than HBlank (4), it gets run first, causing the dry palette to be overwritten by the water palette when HBlank runs. DrawBlockRow/DrawRow (which is called multiple times by DrawInitalBG/DrawTilesAtStart) repeatedly disables and enables interrupts in order to write to VRAM, and the timings are just such that a frame ends while that subroutine has interrupts disabled. As far as I can tell, this race condition has the potential to occur anytime that interrupts are disabled AND HBlank is set to run at or near the bottom of the screen. The only way to truly eliminate it would be to disable HBlank if the water surface is not on-screen, but even that might not help if the water level is very close to the bottom. However, the instance that causes the miscolored title cards has a simple workaround: delay enabling HBlank on the VDP until we are ready to slide the title cards away. Under Level/GM_Level, find this: Code (ASM): ; Sonic 2 GitHub + move.w (Hint_counter_reserve).w,(a6) clr.w (VDP_Command_Buffer).w move.l #VDP_Command_Buffer,(VDP_Command_Buffer_Slot).w tst.b (Water_flag).w ; does level have water? beq.s Level_LoadPal ; if not, branch move.w #$8014,(a6) ; H-INT enabled ;=========================== ; Sonic 2 ASM68K .not_2P: move.w (v_vdp_hint_counter).w,(a6) reset_dma_queue ; clear and reset the DMA queue tst.b (f_water).w ; does level have water? beq.s .skip_water ; if not, branch move.w #vdp_md_color|vdp_enable_hint,(a6) ; normal color mode, horizontal interrupts enabled We want to move that last instruction all the way to near the end of the level load sequence. The best location would be immediately after the call to PalLoad_Water_ForFade/PalLoad_Water_Next: Code (ASM): + bsr.w PalLoad_Water_ForFade move.w #$8014,(VDP_control_port).l ;=========================== .gotunderwaterpal: bsr.w PalLoad_Water_Next ; load water palette that'll be shown after fading in move.w #vdp_md_color|vdp_enable_hint,(vdp_control_port).l ; normal color mode, horizontal interrupts enabled Note that we have not changed how HBlank is enabled for two-player mode. It MUST be enabled where it is for things to work correctly in that case.
Cool fix, although Sonic 2 Community Cut had a different way to resolve the issue by directly messing with PalToCRAM; for the sake of completeness, here it is (insert between Underwater_palette and vdpComm lines): Code (Text): ; title card flicker fix from Community's Cut move.l d0,-(sp) move.w (Water_Level_1).w,d0 subi.w #224,d0 cmp.w (Camera_Y_pos).w,d0 ble.s + lea (Normal_palette).w,a0 ; load palette from RAM + move.l (sp)+,d0 Either works.
The Rexon object has a near-completely implemented but unused feature in its code. Although it's hard to actually see it in action, its body normally oscillates back and forth in the lava while waiting for a player to get close. However, there is also code for a second behavior: simply standing still in the lava while waiting. Code (ASM): ; Sonic 2 Git Obj94_ReadyToCreateHead: bsr.w Obj_GetOrientationToPlayer addi.w #$60,d2 cmpi.w #$100,d2 bhs.s loc_373AE bsr.w Obj94_CreateHead loc_373AE: bsr.w Obj94_SolidCollision jmpto MarkObjGone, JmpTo39_MarkObjGone ; =========================================================================== ; Sonic 2 ASM68K Rex_Wait_Stationary: ; unused, as ost_routine is never set to run it ($4) bsr.w GetClosestPlayer ; get nearest player addi.w #$60,d2 cmpi.w #$100,d2 bcc.s .no_spawn ; branch if they're not close enough yet bsr.w Rex_SpawnHead .no_spawn: bsr.w Rex_Solid jmpto DespawnObject,JmpTo39_DespawnObject The only thing that's missing is code to actually set that routine. This cannot be done by subtype, since the Rexon uses the subobject data system, which uses the subtype OST slot to store the index into the subobject data pointer list. However, the fact that the Rexon has two pointers and two IDs ($94 and $96, with only the latter used) associated with it seems to suggest that the object ID would have been used to trigger this behavior. The Grounder object actually does this, with one ID signifying a Grounder hidden in a wall, the other a Grounder that's already roaming. Patterning off of that, finishing this unfinished feature is as simple as adding a few lines to Obj94_Init/Rex_Init: Code (ASM): ; Sonic 2 Git Obj94_Init: bsr.w LoadSubObject move.b #2,mapping_frame(a0) cmpi.b #ObjID_Rexon,id(a0) ; $94 for stationary, $96 for moving beq.s .stationary ; branch if this Rexon is stationary move.w #-$20,x_vel(a0) move.b #$80,objoff_2A(a0) rts .stationary: move.b #4,routine(a0) ; Obj94_ReadyToCreateHead rts ; =========================================================================== ; Sonic 2 ASM68K Rex_Init: bsr.w LoadSubtypeData ; go to Rex_Wait next move.b #id_Frame_Rexon_Body,ost_frame(a0) cmpi.b #id_Rexon_Dup,ost_id(a0) ; $94 for stationary, $96 for moving beq.s .stationary ; branch if this Rexon is stationary move.w #-$20,ost_x_vel(a0) move.b #$80,ost_rex_turntime(a0) ; move left for 128 frames rts .stationary: move.b #id_Rex_Wait_Stationary,ost_primary_routine(a0) rts