PLC Queue Shifting Fix - RetroKoH/S1Fixed GitHub Wiki

(Original Guide by Vladikcomper)
Source: SSRG Thread
Commit: b335b11
(NOTE: This fix is handled in Sonic Retro's s1disasm by FixBugs)

"Pattern Load Cues" (PLCs) is the term referring to a system for loading Nemesis-compressed art during game execution. PLCs are used to load art at the beginning of the level, as well as at certain other points of the game, such as loading signpost or boss art at the end of a level. This system is rather complicated, and in Sonic 1 and 2 it has a few bugs, one of which was addressed in the "How to fix PLC race condition" guide. This guide addresses another bug with the mechanism, which is thought to be a simple queue overflow error.

The PLC system has a buffer, or 'queue', occupying $60 (96) bytes of RAM. The queue is filled by the art load cues, each takes 6 bytes. The first 4 bytes are the source ROM offset, and last two bytes are the destination VRAM offset. The cues are executed one by one until the queue is emptied out. To see what these cues are, check out _inc/Pattern Load Cues.asm.

The queue can hold up to $10 (16) cues. Cue execution starts from the beginning of the queue storage. When the cue is done, all the cues are shifted forward: the first cues is replaced by the second, the second - by the third and so on. However, due to an error, when the buffer is filled up with all $10 cues, the last one doesn't clear on shifting, which leads to it being copied over, and the queue eventually being overfilled with the same cue, so the game gets stuck decompressing the same art over and over again.

This bug is easy to come across in Sonic 1: once you have added two more cues into PLC_GHZ, the queue will contain $10 (16) cues, and the game will become stuck at the Title Card, due to the error noted above. The code that needs to be fixed is in sonic.asm at loc_16DC. There are two things to note when we observe this code:

Firstly, this code transfers $16*4 = $58 bytes, which doesn't even match an integral number of cues. It should've done $5A bytes, which is $F cues. Transferring $58 bytes means it won't transfer last 2 bytes of the final cue which are the destination VRAM offset. So the VRAM offset will remain that of the prior cue. Secondly, the last cue is not cleared, which will cause the overcopying issue mentioned previously.

To fix this bug, we will make the following changes to the code:

loc_16DC:
-	lea	(v_plc_buffer).w,a0
-	moveq	#(v_plc_buffer_only_end-v_plc_buffer-6)/4-1,d0


+	lea	(v_plc_buffer).w,a0
+	lea 	6(a0),a1
+	moveq   #$E,d0		; do $F cues

loc_16E2:
-	move.l	6(a0),(a0)+
-	dbf	d0,loc_16E2
-	if (v_plc_buffer_only_end-v_plc_buffer-6)&2
-		move.w	6(a0),(a0)
-	endif

+	move.l  (a1)+,(a0)+
+	move.w  (a1)+,(a0)+
+	dbf 	d0,loc_16E2

-	clr.l	(v_plc_buffer_only_end-6).w
-	rts

+	moveq   #0,d0
+	move.l  d0,(a0)+	; clear the last cue to avoid overcopying it
+	move.w  d0,(a0)+	;
+	rts

This new code will shift the queue correctly and prevent the last cue from overcopying. The function now works correctly, but don't forget about queue overflow: everything will work properly only when the queue is filled with up to $10 cues. Any more, and you'll have problems!