How to fix Pattern Load Cues queue shifting bug

Discussion in 'Tutorials Archive' started by vladikcomper, Mar 24, 2012.

Thread Status:
Not open for further replies.
  1. vladikcomper

    vladikcomper Well-Known Member Member

    Joined:
    Dec 2, 2009
    Messages:
    415
    Pattern Load Cues, or simply PLC, is a system for loading Nemesis-compressed art during game execution. PLC is used to load art at the beginning of the level, as well as at certain points of the game (like loading singpost or boss art at the end of level). This system is rather complicated, in Sonic 1 and 2 it has few bugs, one of the was described in "How to fix PLC race condition" guide.


    Recently I have figured out another bug regarding PLC queue shifting mechanism, which I considered to be simple queue overflow earlier.


    Introduction


    PLC system has a buffer, better to be called 'queue', taking $60 bytes in memory space. Queue is filled by the art load cues, each takes 6 bytes, first 4 being source ROM offset, and last two being destination VRAM offset. The cues are executed one by one until all the queue gets empty. To see what these cues are, check out _incPattern Load Cues.asm file in Sonic 1's disassembly.


    PLC queue can hold up to $10 (16) cues. Cues execution starts from the beginning of the queue storage. When the cue is done, all the cues are getting moved: the first cues is replaced by the second, the second - by the third and so on. In other words, the whole queue gets shifted. However, due to program error, when the buffer is filled up with all $10 cues, the last one doesn't clear on shifting, which leads to overcopying it, so all the queue eventually overfills with the same cue, so the game gets stuck decompressing the same art over and over again.


    This bug is easy to meet in Sonic 1: once you have added two more cues into PLC_GHZ, the queue will take $10 cues, and the game will stuck at the Title Cards, though $10 cues is an is acceptable number.


    Explanation of the bug


    Let's look at queue shifting code to understand the reason of the bug. As an example, I will show you the code from Sonic 2 Xenowhirl's Disassembly, as it's more annotated in this place than Sonic 1's. Remember, the this code is the same in both Sonic 1 and Sonic 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



    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 last ($10-th) cue which are VRAM offset. So the VRAM offset will remain from the previous cue.


    Secondly, the last cue in the queue is not cleared, which will cause overcopying it. Eventually, all the queue will be filled with the last cue stored and the game will stuck endlessly processing the same cue.


    Fixing the bug


    Sonic 1 (Hivebrain's Disassembly)


    Go to 'loc_16DC' and replace the whole code with this:



    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 (Xenowhirl's Disassembly)


    Go to 'ProcessDPLC_Pop' and replace the whole code with this:



    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



    This new version of code will shift the queue correctly and protect the last cue from overcopying. The bug is gone! But don't forget about queue overflow, this will fix bug when queue is filled with $10 cues, but $11 and more cues are out of queue size!


    EDIT: GRAMMAR
     
    Last edited by a moderator: Mar 25, 2012
    Nat The Porcupine and KCEXE like this.
  2. Psycho RFG

    Psycho RFG Well-Known Member Member

    Joined:
    Feb 22, 2011
    Messages:
    234
    Nice fix, one more time. Very well explained and it works fine!
     
  3. redhotsonic

    redhotsonic Also known as RHS Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    Location:
    England
    Call me stupid, but I do not understand this "bug". I don't think I've ever come across this bug in my hack.
     
  4. Selbi

    Selbi The Euphonic Mess Member

    Joined:
    Jul 20, 2008
    Messages:
    2,429
    Location:
    Northern Germany
    This bug only occurs when you actually modify the PLCs to a point where the game can't handle it anymore. It's more like a "We didn't have to go further than this, so we didn't add support for it" thing.
     
  5. Animemaster

    Animemaster Lets get to work! Member

    Joined:
    Mar 20, 2009
    Messages:
    1,229
    Location:
    UK
    Yeah Selbi's knocked it right on the head. Thats pretty much how I'd describe it, that being said this maybe of use to me, thanks for the tutorial.
     
  6. vladikcomper

    vladikcomper Well-Known Member Member

    Joined:
    Dec 2, 2009
    Messages:
    415
    From what I've seen, in Sonic 2 Art Load Cues look smaller than in Sonic 1, so buffer is harder to full up. This means you'll be safe from this bug, until you add really many new entries into PLC lists.


    As for Sonic 1, when I was doing my first hack, I easily bumped into this bug, when added new art into GHZ's PLC list. However, only GHZ can be a problem here, other zones use smaller PLC lists.


    Also, thanks to flamewing on Sonic Retro, I'm now aware this bug was in Sonic 3/Sonic & Knuckles too.

    In fact, the PLC buffer was designed to hold up to $10 entries, but due to program error it could normally process no more than $0F entries. Processing $10 entries caused bug, where the whole buffer was filled with garbage (left-overs of the last entry).


    This guide only helps to gain one more entry in the PLC buffer, which is not too much, but at least, another part of engine now works properly and another hidden bug is gone =)
     
    Last edited by a moderator: Mar 25, 2012
  7. redhotsonic

    redhotsonic Also known as RHS Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    Location:
    England
    I will say that I have two monitors in my hack. The original blue shield and and a grey shield (to attract rings). If you have one and then get the other (say you have the blue shield then get the grey one), the grey shields art loads it PLC and then over-writes it in the blue shield's art). And vice versa. If I kept switching between monitors, would this eventually fill up the buffer?



    tails_1up:
    ;static_shield_monitor:


    moveq #$4E,d0 ; This should load attraction shield


    jsr (LoadPLC).l


    bset #3,status_secondary(a1)


    addq.w #1,(a2)


    bset #0,status_secondary(a1)


    move.w #$AF,d0


    jsr (PlayMusic).l


    move.b #$38,(Object_RAM+$2180).w ; load Obj38 (shield) at $FFFFD180


    move.w a1,(Object_RAM+$2180+parent).w


    rts
     
  8. vladikcomper

    vladikcomper Well-Known Member Member

    Joined:
    Dec 2, 2009
    Messages:
    415
    Yes, it's possible, though I think it's hard to reproduce. You'll have to switch faster than PLC system decompresses a single art.


    PLC decompresses 3 tiles per frame -- just divide number of tiles in your art by 3, you will know how many frames it will take to decompress.


    Imagine that you put many many monitors in a row and break each every frame (only pure TASers can do so). In this case, the buffer will eventually become full (16 entries) or even overflow. If buffer is just full, the game won't freeze, it will only stick decompressing the same art, it will never stop then. Once you will try to push anything else there, the buffer will overflow. I can't say what can happen, it depends on how memory after buffer is used ('LoadPLC' will try to find empty memory slot and probably corrupt some zeroed variables).


    Either way, it's really hard to reproduce. The way you load art is ok, though I'd suggest one thing.


    You can use 'LoadPLC2' instead of 'LoadPLC'. This one will clear the buffer before putting anything there. But in this case you should ensure that you can't take monitor few frames after level starts (or it will cancel loading main level patterns). However, if you load more than only monitor art during the game, the old way would be certainly better, as new one in this case could cause 'rare' bugs when one of requested arts won't load.
     
  9. redhotsonic

    redhotsonic Also known as RHS Member

    Joined:
    Aug 10, 2007
    Messages:
    2,969
    Location:
    England

    Okay, mate. Thanks for the advice =)
     
Thread Status:
Not open for further replies.