Alignment directives for DMA boundary bug

Discussion in 'Engineering & Reverse Engineering' started by qiuu, Sep 21, 2019.

  1. qiuu


    Tech Member
    Blue Ball & Blocks
    You might be aware of the bug that if art/data that's meant to be transferred to VRAM via DMA crosses a $20000 boundary, the latter part of the art will not be transferred correctly, see Hardware/emulator quirks for a detailed description.

    I have a working disassembly of Kid Chameleon, and as the Kid's art and some other stuff are transferred via DMA, it's affected by this. Suggestion 1 from the linked post suggests to write a macro to detect if some art crosses the boundary, but is it also somehow possible to automatically align the art in this case? Ideally I would like to have macros that only align art whenever needed, so I don't have to add a flag that hackers have to set first if they want this DMA safeguard to be active.
    Any suggestions?

    (The best idea I currently have is modify the DMA queue code that is used for most sprites to split the transfers if necessary, and align the other few bits of art that don't use the queue to the next power of 2 that's bigger than the art size. The latter is a bit wasteful, but at least I wouldn't apply it to the bulk of the sprites which use the DMA queue.)
  2. MarkeyJester


    A D V A N C E Resident Jester
    Yes, it is possible to auto-align if the data crosses a boundary/window, this can be done by labelling the end location of the art and using that as a reference. You'll then XOR the beginning offset with the end offset, AND the result by $FE0000, and if the value is non-zero, it crossed the boundary, thus peform an alignment.

    The problem is, this requires the assembler you're using to be able to forward reference labels ahead of time, so you will likely need an assembler which can perform multiple passes. AS will be able to do this, though other assemblers such as asm68k will not as they cannot forward reference a label in the early passes, and don't do enough passes to adjust themselves for the offset changes.

    Another consideration I might be able to throw, since it's dynamically loaded/uncompressed art, I assume the art is loaded in individual frames/sprites/pieces. Sonic 3 has lots of Sonic art such that it crosses a boundary, however, the crossover just happens to occur between the end of an art piece, and the beginning of another, so no actual transfers cross over. This will be the most efficient method for both memory and CPU time, but it'd require careful planning.
    • Informative Informative x 1
    • List
  3. qiuu


    Tech Member
    Blue Ball & Blocks
    Yeah I've tried what you suggested before, but it didn't work because I'm using an if-condition that refers to labels whose addresses are not yet know during the first pass:

    error: expression must be evaluatable in first pass

    Is there any other way of doing this? (I'm using AS.)
    Code (Text):
    2.     if (Label_post ! Label_pre)&$FE0000 <> 0
    3.     align $8000 ; $8000 is the largest possible word-sized value, but that's sufficient
    4.     endif
    5. Label_pre:
    6. ; some art included here
    7. Label_post:
    I can do
    Code (Text):
    1.     if ((*) ! ((*)+FILESIZE))&$FE0000 <> 0
    2.     align $8000
    3.     endif
    but I don't know of any way to get the file size automatically.
    Last edited: Sep 21, 2019
  4. Hivebrain


    53.4N, 1.5W
    Align the art to $20 and DMA each tile separately. I don't know if lots of DMAs would be much slower than a single DMA of equal size though.
  5. MainMemory


    Kate the Wolf Tech Member
    I've come up with a modification to my DPLC macro to handle this by automatically splitting the DPLC into two parts if it detects a boundary overflow in the art.
    Code (Text):
    1. dplcEntry macro tiles,offset
    2.    if dplcTiles <> 0
    3.    if ((dplcTiles+(offset*$20))/131072) <> ((dplcTiles+(offset*$20)+(tiles*$20)-1)/131072)
    4.    dplcEntry   tiles-(((dplcTiles+(offset*$20)+(tiles*$20))#131072)/$20),offset
    5.    dplcEntry   ((dplcTiles+(offset*$20)+(tiles*$20))#131072)/$20,offset+(tiles-(((dplcTiles+(offset*$20)+(tiles*$20))#131072)/$20))
    6.    exitm
    7.    endif
    8.    endif
    9.    if SonicMappingsVer==3
    10.    dc.w   ((offset&$FFF)<<4)|((tiles-1)&$F)
    11.    else
    12.    dc.w   (((tiles-1)&$F)<<12)|(offset&$FFF)
    13.    endif
    14.    endm


    The cute one here Tech Member
    Code (Text):
    1. bincludeDMA    macro {INTLABEL}, file
    2. __LABEL__    label *
    4.     if MOMPASS<>1
    5.         if (__LABEL__&$FE0000)<>(__LABEL___End&$FE0000)
    6.             align $20000
    7.         endif
    8.     endif
    10.     binclude file
    12. __LABEL___End    label *
    13.     endm
    I tried this, and it seems to work just fine. The important part here, is the check to MOMPASS; Apparently in pass 1, AS gets a little confused. So, we only check it the next pass. The align statement works with the S&K Git disassembly macro.