Как исправить баг со сдвигом очереди Pattern Load Cues
Автор гида: vladikcomperPattern Load Cues, или сокращенно PLC, система для подгрузки сжатого в Nemesis арта. PLC загружает арт в начале уровней, а также в некоторые моменты игры (например, подгрузка арта сигнпоста или боссов в конце уровней). Эта система устроена довольно сложно, и в Соник 1 и 2 в ней присутствуют баги, один из которых описан в гиде «Как исправить баг с загрузкой Pattern Load Cues».
Еще один баг связан с механизмом сдвига PLC очереди.
Вступление
У системы PLC есть буффер, или правильнее сказать, очередь (queue, тип структуры данных), которая занимает $60 байт в памяти. В очередь записываются и поочередно исполняются команды на загрузку арта, каждая команда занимает 6 байт: первые 4 байта - исходный оффсет загружаемого арта в РОМе, оставшиеся 2 - оффсет VRAM, куда следует загрузить арт. Посмотреть как выглядят эти команды можно в файле _inc\Pattern Load Cues.asm в Соник 1.
Очередь PLC рассчитана на $10 (16) команд. Выполнение команд в очереди начинается с ее начала, как только команда выполнена, вся очередь сдвигается: первая команда замещается второй, вторая - третьей, и так далее. Говоря другими словами, команды в очереди сдвигаются. Однако, из-за ошибке в коде, когда буффер заполнен всеми $10 командами, последняя команда не очищается при сдвиге, что приводит к тому, что вся очередь заполняется остатками последней команды и игра попадает в бесконечный цикл, обрабатывая одну и ту же команду снова и снова.
С этим можно столкнуться в Соник 1, если добавить в PLC_GHZ хотя бы две новые команды, очередь заполнится до максимума, и игра зависнет на Title Cards, хотя количество команд в очереди будет в рамках допустимых значений.
Причина бага
Давайте рассмотрим код сдвига очереди, чтобы понять суть программной ошибки. В качестве примера, я приведу код из Sonic 2 Xenowhirl's Disassembly, так как он лучше раскомментирован, чем код из Соник 1. Заметьте, что сам код и в Соник 1, и в Соник 2, абсолютно одинаков.
; =========================================================================== ; pop one request off the buffer so that the next one can be filled ; loc_177A: ProcessDPLC_Pop: lea (Plc_Buffer).w,a0 moveq #$15,d0 - move.l 6(a0),(a0)+ dbf d0,- rts
Во-первых, этот код перемещает $16*4 = $58 байт, что даже не соответствует целому числу команд (не делится на 6). Он должен был перемещать $5A байт, т.е. $F команд. Поскольку код перемещает только $58 байт, недокопируются последние 2 байта последней ($10-ой команды), т.е. оффсет VRAM. Таким образом, оффсет этой команды собьется и останется с прошлой команды.
Во-вторых, последняя команда в очереди не очищается, что ведет к ее бесконечному копированию (или, "размазыванию" по очереди). В конечном счете, вся очередь будет заполнена остатками последней команды и игра будет бескончено обрабатывать одну и ту же команду.
Исправление бага
Sonic 1 (Hivenbrain's Disassembly)
Идите к loc_16DC и замените весь код на это:
loc_16DC: ; XREF: sub_165E lea ($FFFFF680).w,a0 lea 6(a0),a1 moveq #$E,d0 ; do $F cues loc_16E2: ; XREF: sub_165E move.l (a1)+,(a0)+ move.w (a1)+,(a0)+ dbf d0,loc_16E2 moveq #0,d0 move.l d0,(a0)+ ; clear the last cue to avoid overcopying it move.w d0,(a0)+ ; rts ; End of function sub_165E
Sonic 2 (Xenowhirls's Disassembly)
Идите к ProcessDPLC_Pop и замените весь код на это:
ProcessDPLC_Pop: lea (Plc_Buffer).w,a0 lea 6(a0),a1 moveq #$E,d0 ; do $F cues - move.l (a1)+,(a0)+ move.w (a1)+,(a0)+ dbf d0,- moveq #0,d0 move.l d0,(a0)+ ; clear the last cue to avoid overcopying it move.w d0,(a0)+ ; rts
Новая версия кода правильно сдвинет все команды в очереди и предотвратит ее засорение. Баг исправлен! Однако не забывайте про переполнение очереди, это исправление исправит проблему, когда очередь заполнена до $10 команд, но на $11 команд она уже не рассчитана.