SCHG How-to

Fix a race condition with Pattern Load Cues

From Sonic Retro

Revision as of 16:21, 6 March 2011 by FraGag (talk | contribs) (Initial version)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

(Original guide by FraGag)

In Sonic 1, there is a race condition with Pattern Load Cues that can cause the game to crash. This situation can happen if the player rolls and pans the camera down at the end of Labyrinth Zone acts 1 and 2 (video).

How to fix the bug (SVN disassembly)

This is the RunPLC routine: <asm>; ---------------------------------------------------------------------------

Subroutine to use graphics listed in a pattern load cue
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

RunPLC: ; XREF: GM_Level; et al tst.l (v_plc_buffer).w beq.s Rplc_Exit tst.w (f_plc_execute).w bne.s Rplc_Exit movea.l (v_plc_buffer).w,a0 lea (loc_1502).l,a3 lea (v_ngfx_buffer).w,a1 move.w (a0)+,d2 bpl.s loc_160E adda.w #$A,a3

loc_160E: andi.w #$7FFF,d2 move.w d2,(f_plc_execute).w bsr.w NemDec4 move.b (a0)+,d5 asl.w #8,d5 move.b (a0)+,d5 moveq #$10,d6 moveq #0,d0 move.l a0,(v_plc_buffer).w move.l a3,(v_ptrnemcode).w move.l d0,($FFFFF6E4).w move.l d0,($FFFFF6E8).w move.l d0,($FFFFF6EC).w move.l d5,($FFFFF6F0).w move.l d6,($FFFFF6F4).w

Rplc_Exit: rts

End of function RunPLC</asm>

Move this line: <asm> move.w d2,(f_plc_execute).w</asm> after this line: <asm> move.l d6,($FFFFF6F4).w</asm>

How to fix the bug (Hivebrain's 2005 disassembly)

The RunPLC routine was called RunPLC_RAM. The line <asm> move.w d2,(f_plc_execute).w</asm> appears as: <asm> move.w d2,($FFFFF6F8).w</asm>

Explanation of the bug

Pattern Load Cues are a mechanism to spread out decompression of Nemesis-compressed patterns over several frames to keep the game from hanging, because it is a slow process. When a PLC request is queued, some tiles are immediately decompressed, then some values are stored in RAM so that processing can resume later in VBlank at the point where processing was suspended. The race condition occurs because one of these values, f_plc_execute ($FFF6F8), is initialized before the initial decompression is started, whereas the other values are initialized after the initial decompression is complete. If the VBlank interrupt occurs while the initial decompression is still running, the routines in VBlank (specifically, at sub_1642 and sub_165E) will start decompression, because f_plc_execute is not 0, but using uninitialized values. The critical value is v_ptrnemcode ($FFF6E0), which is a pointer to the routine to use to output the decompress data. Its value when no PLCs are suspended is 0, and there is no valid code at address 0, so when the game tries to jump there, it crashes.

To fix the problem, f_plc_execute ($FFF6F8) must be set after all the other values are initialized.

This bug is also present in Sonic 2, but is harder to reproduce. In Sonic 3 and Sonic & Knuckles, the bug is fixed as above.