How to optimize Shield Art loading in Sonic 1

Discussion in 'Tutorials Archive' started by SuperEgg, Apr 29, 2014.

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

    SuperEgg I'm a guy that knows that you know that I know Member

    Joined:
    Oct 17, 2009
    Messages:
    Location:
    THE BEST GOD DAMN STATE OF TEXAS
    Have you ever been working on a hack in Sonic 1 and realized you don't have enough VRAM to load more art? I know I have, and the idea of not having enough room is quite annoying. Well no more, because with this tutorial,you nice folks will now have 31 free 8x8 blocks of VRAM.

    As a side note, this tutorial is written specifically with the 2005 disassembly in mind. I'm assuming that this tutorial is simple enough to port to the HG disassembly, but as I have little time to actually do it myself, or mental enough to try to waddle my way around the damn thing, port to your own risk. Also, this code should work in all of the Sonic 128 disassemblies as well, in fact this code was written is said disassembly.

    Enjoy

    Step One: Adding the DMA Queue

    If you haven't added this piece of code and it's fixes into your disassembly, go ahead and add it. You may have it already if you've added in the Spin Dash from this tutorial. If that is the case, go down to Step two.

    Look for "PalCycle_SS" and add this piece of code right before it. This will speed things up for later.


    ; ---------------------------------------------------------------------------
    ; Subroutine for queueing VDP commands (seems to only queue transfers to VRAM),
    ; to be issued the next time ProcessDMAQueue is called.
    ; Can be called a maximum of 18 times before the buffer needs to be cleared
    ; by issuing the commands (this subroutine DOES check for overflow)
    ; ---------------------------------------------------------------------------
    ; In case you wish to use this queue system outside of the spin dash, this is the
    ; registers in which it expects data in:
    ; d1.l: Address to data (In 68k address space)
    ; d2.w: Destination in VRAM
    ; d3.w: Length of data
    ; ---------------------------------------------------------------------------

    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_144E: DMA_68KtoVRAM: QueueCopyToVRAM: QueueVDPCommand: Add_To_DMA_Queue:
    QueueDMATransfer:
    movea.l ($FFFFC8FC).w,a1
    cmpa.w #$C8FC,a1
    beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer

    ; piece together some VDP commands and store them for later...
    move.w #$9300,d0 ; command to specify DMA transfer length & $00FF
    move.b d3,d0
    move.w d0,(a1)+ ; store command

    move.w #$9400,d0 ; command to specify DMA transfer length & $FF00
    lsr.w #8,d3
    move.b d3,d0
    move.w d0,(a1)+ ; store command

    move.w #$9500,d0 ; command to specify source address & $0001FE
    lsr.l #1,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    move.w #$9600,d0 ; command to specify source address & $01FE00
    lsr.l #8,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    move.w #$9700,d0 ; command to specify source address & $FE0000
    lsr.l #8,d1
    move.b d1,d0
    move.w d0,(a1)+ ; store command

    andi.l #$FFFF,d2 ; command to specify destination address and begin DMA
    lsl.l #2,d2
    lsr.w #2,d2
    swap d2
    ori.l #$40000080,d2 ; set bits to specify VRAM transfer
    move.l d2,(a1)+ ; store command

    move.l a1,($FFFFC8FC).w ; set the next free slot address
    cmpa.w #$C8FC,a1
    beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer
    move.w #0,(a1) ; put a stop token at the end of the used part of the buffer
    ; return_14AA:
    QueueDMATransfer_Done:
    rts
    ; End of function QueueDMATransfer


    ; ---------------------------------------------------------------------------
    ; Subroutine for issuing all VDP commands that were queued
    ; (by earlier calls to QueueDMATransfer)
    ; Resets the queue when it's done
    ; ---------------------------------------------------------------------------

    ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

    ; sub_14AC: CopyToVRAM: IssueVDPCommands: Process_DMA: Process_DMA_Queue:
    ProcessDMAQueue:
    lea ($C00004).l,a5
    lea ($FFFFC800).w,a1
    ; loc_14B6:
    ProcessDMAQueue_Loop:
    move.w (a1)+,d0
    beq.s ProcessDMAQueue_Done ; branch if we reached a stop token
    ; issue a set of VDP commands...
    move.w d0,(a5) ; transfer length
    move.w (a1)+,(a5) ; transfer length
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; source address
    move.w (a1)+,(a5) ; destination
    move.w (a1)+,(a5) ; destination
    cmpa.w #$C8FC,a1
    bne.s ProcessDMAQueue_Loop ; loop if we haven't reached the end of the buffer
    ; loc_14CE:
    ProcessDMAQueue_Done:
    move.w #0,($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w
    rts
    ; End of function ProcessDMAQueue

    Once you've finished, go to "loc_CD4", "loc_DAE", "loc_EEE", and loc_FAE"

    Replace this piece of code.


    tst.b ($FFFFF767).w
    beq.s loc_D50
    lea ($C00004).l,a5
    move.l #$94019370,(a5)
    move.l #$96E49500,(a5)
    move.w #$977F,(a5)
    move.w #$7000,(a5)
    move.w #$83,($FFFFF640).w
    move.w ($FFFFF640).w,(a5)
    move.b #0,($FFFFF767).w

    With this.


    jsr (ProcessDMAQueue).l 
    Next, go to "Level_ClrVars3". After this piece of code


    move.w #$8ADF,($FFFFF624).w
    move.w ($FFFFF624).w,(a6) 
    Add this


    clr.w ($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w 
    Next, go to "loc_47D4". After


    jsr Hud_Base 
    Add


    clr.w ($FFFFC800).w
    move.l #$FFFFC800,($FFFFC8FC).w 
    The last fix will be fixing the Sonic Object.

    Go to "LoadSonicDynPLC". Replace this.


    ​ moveq #0,d0
    move.b $1A(a0),d0 ; load frame number
    cmp.b ($FFFFF766).w,d0
    beq.s locret_13C96
    move.b d0,($FFFFF766).w
    lea (SonicDynPLC).l,a2
    add.w d0,d0
    adda.w (a2,d0.w),a2
    moveq #0,d1
    move.b (a2)+,d1 ; read "number of entries" value
    subq.b #1,d1
    bmi.s locret_13C96
    lea ($FFFFC800).w,a3
    move.b #1,($FFFFF767).w

    SPLC_ReadEntry:
    moveq #0,d2
    move.b (a2)+,d2
    move.w d2,d0
    lsr.b #4,d0
    lsl.w #8,d2
    move.b (a2)+,d2
    lsl.w #5,d2
    lea (Art_Sonic).l,a1
    adda.l d2,a1

    SPLC_LoadTile:
    movem.l (a1)+,d2-d6/a4-a6
    movem.l d2-d6/a4-a6,(a3)
    lea $20(a3),a3 ; next tile
    dbf d0,SPLC_LoadTile ; repeat for number of tiles

    dbf d1,SPLC_ReadEntry ; repeat for number of entries

    locret_13C96:
    rts

    With this.


    moveq #0,d0
    move.b $1A(a0),d0 ; load frame number
    cmp.b ($FFFFF766).w,d0
    beq.s locret_13C96
    move.b d0,($FFFFF766).w
    lea (SonicDynPLC).l,a2
    add.w d0,d0
    adda.w (a2,d0.w),a2
    moveq #0,d5
    move.b (a2)+,d5
    subq.w #1,d5
    bmi.s locret_13C96
    move.w #$F000,d4
    move.l #Art_Sonic,d6

    SPLC_ReadEntry:
    moveq #0,d1
    move.b (a2)+,d1
    lsl.w #8,d1
    move.b (a2)+,d1
    move.w d1,d3
    lsr.w #8,d3
    andi.w #$F0,d3
    addi.w #$10,d3
    andi.w #$FFF,d1
    lsl.l #5,d1
    add.l d6,d1
    move.w d4,d2
    add.w d3,d4
    add.w d3,d4
    jsr (QueueDMATransfer).l
    dbf d5,SPLC_ReadEntry ; repeat for number of entries

    locret_13C96:
    rts  
    At this point, your ROM should be safe to build, so go ahead and build. Go play a level and see if Sonic appears on screen. If so, that means you did good and you deserve a cookie.

    Step Two: Separating the Shield and Invincibility objects

    This step is probably the most difficult step, but once it is outta the way, it'll make things easier.

    First, go to Object 38, you should see this.


    Obj38:
    moveq #0,d0
    move.b $24(a0),d0
    move.w Obj38_Index(pc,d0.w),d1
    jmp Obj38_Index(pc,d1.w)
    ; ===========================================================================
    ; More more code.
    Delete the whole object. In it's place put this in.


    Obj38: ; XREF: Obj_Index
    move.l #UnC_Shield,d1 ; Call for Regular Shield Art
    move.w #$A820,d2 ; Load Art from this location (VRAM location*20)
    ; In this case, VRAM = $541*20
    move.w #$200,d3
    jsr (QueueDMATransfer).l
    ; ---------------------------------------------------------------------------
    ShieldObj_Main:
    moveq #0,d0
    move.b $24(a0),d0
    move.w Shield_Index(pc,d0.w),d1
    jmp Shield_Index(pc,d1.w)
    ; ===========================================================================
    Shield_Index:
    dc.w Shield_Init-Shield_Index
    dc.w ShieldChecks-Shield_Index
    ; ===========================================================================
    Shield_Init:
    addq.b #2,$24(a0)
    move.l #Map_Obj38, $0004(A0) ; Load Shield Map into place
    move.b #4,1(a0)
    move.b #1,$18(a0)
    move.b #$18,$19(a0)
    move.w #$541,2(a0) ; Set VRAM location
    btst #7,($FFFFD002).w
    beq.s ShieldChecks
    bset #7,2(a0)
    ; ---------------------------------------------------------------------------
    ShieldChecks:
    tst.b ($FFFFFE2D).w ; Test if Sonic has a shield
    bne.s SonicHasShield ; If so, branch to do nothing
    tst.b ($FFFFFE2C).w ; Test if Sonic got invisibility
    beq.s jmp_DeleteObj38 ; If so, delete object temporarily
    ShieldProperties:
    move.w ($FFFFD008).w,8(a0) ; Load Main Character X-position
    move.w ($FFFFD00C).w,$C(a0) ; Load Main Character Y-position
    move.b ($FFFFD022).w,$22(a0) ; Something about Character status
    lea (Ani_obj38).l, a1 ; Load Animation Scripts into a1
    jsr AnimateSprite
    jmp DisplaySprite
    SonicHasShield:
    rts
    jmp_DeleteObj38: ; loc_12648:
    jmp DeleteObject

    This object is now just the shield object. Now, let's go add in the invincibility object.

    Find Obj4A, you should see this.


    ; ---------------------------------------------------------------------------
    ; Object 4A - special stage entry from beta
    ; ---------------------------------------------------------------------------

    Obj4A: ; XREF: Obj_Index
    moveq #0,d0
    move.b $24(a0),d0
    move.w Obj4A_Index(pc,d0.w),d1
    jmp Obj4A_Index(pc,d1.w)
    ; More code after this

    Replace the whole object with this.


    ; ---------------------------------------------------------------------------
    ; Object 4A - New Invincibility Object
    ; ---------------------------------------------------------------------------

    Obj4A: ; XREF: Obj_Index
    move.l #UnC_Stars,d1
    move.w #$A820,d2
    move.w #$200,d3
    jsr (QueueDMATransfer).l
    Invincibility_Main:
    moveq #0,d0
    move.b $24(a0),d0
    Invincibility_Init:
    addq.b #2,$24(a0)
    move.l #Map_obj38,4(a0) ; loads mapping
    move.b #4,1(a0)
    move.b #1,$18(a0)
    move.b #$10,$19(a0)
    move.w #$541,2(a0) ; shield specific code
    ; ===========================================================================

    Obj4A_Stars: ; XREF: Obj38_Index
    tst.b ($FFFFFE2D).w ; does Sonic have invincibility?
    beq.s Obj4A_Delete2 ; if not, branch
    move.w ($FFFFF7A8).w,d0
    move.b $1C(a0),d1
    subq.b #1,d1
    bra.s Obj4A_StarTrail
    ; ===========================================================================
    lsl.b #4,d1
    addq.b #4,d1
    sub.b d1,d0
    move.b $30(a0),d1
    sub.b d1,d0
    addq.b #4,d1
    andi.b #$F,d1
    move.b d1,$30(a0)
    bra.s Obj4A_StarTrail2a
    ; ===========================================================================

    Obj4A_StarTrail: ; XREF: Obj4A_Stars
    lsl.b #3,d1
    move.b d1,d2
    add.b d1,d1
    add.b d2,d1
    addq.b #4,d1
    sub.b d1,d0
    move.b $30(a0),d1
    sub.b d1,d0
    addq.b #4,d1
    cmpi.b #$18,d1
    bcs.s Obj4A_StarTrail2
    moveq #0,d1

    Obj4A_StarTrail2:
    move.b d1,$30(a0)

    Obj4A_StarTrail2a:
    lea ($FFFFCB00).w,a1
    lea (a1,d0.w),a1
    move.w (a1)+,8(a0)
    move.w (a1)+,$C(a0)
    move.b ($FFFFD022).w,$22(a0)
    lea (Ani_obj38).l,a1
    jsr (AnimateSprite).l
    jmp (DisplaySprite).l
    ; ===========================================================================

    Obj4A_Delete2: ; XREF: Obj4A_Stars
    jmp (DeleteObject).l

    This completes the object replacement portion of the tutorial, let's move on. If you tried to build at this point, you'll notice it won't as there is a few errors. This next step will remedy that.

    Step Three: Adding and fixing the Art

    This step is the second most easiest step of this tutorial. First, you need to download these two files and save them into the "artunc" folder. 

    Uncompressed Invicibility Stars Art

    Uncompressed Shield Art

    (btw, click on the names, they are hyperlinks straight to the art downloads.)

    Once that has been taken care of, go to "nem_Shield". You should see this.


    Nem_Shield: binclude artnem/shield.bin ; shield
    align 2
    Nem_Stars: binclude artnem/invstars.bin ; invincibility stars
    align 2

    Now, assuming you've already placed the art into the correct folder, go ahead and replace that with this.


    UnC_Shield: binclude artunc/shield.bin
    align 2
    UnC_Stars: binclude artunc/stars.bin ; invincibility stars
    align 2

    Now your objects will load the art. But before you go off and build, go open your "Pattern Load Cues.asm" file. You'll need to fix just one portion of the cues.

    Go to "PLC_Main2", and you should see this.


    PLC_Main2: dc.w 2
    dc.l Nem_Monitors ; monitors
    dc.w $D000
    dc.l Nem_Shield ; shield
    dc.w $A820
    dc.l Nem_Stars ; invincibility stars
    dc.w $AB80 
    Replace it with this.


    PLC_Main2: dc.w 0
    dc.l Nem_Monitors ; monitors
    dc.w $D000 
    This should fix all the art issues. Now, for those wondering why I'm making you load uncompressed art as opposed to Nemesis compressed art, a simple explanation shall be given.

    1. The DMA queue can not load Nemesis compressed art.

    2. Even if it could, you still need to remove the art from the PLC. The reason is that now your art between Invincibility and Shield now shares the same VRAM location, and will simply overwrite each other when the one of the objects is in use.

    Now, there is one last step to finish, and that is....

     Step Four: Object loading from the Monitors

    For those who have noticed, the shield and Invincibility objects are now separate objects. As such, the way that the monitor loads said objects must also be updated. Luckily for us, since only the Invincibility object is the only one that changed, this last step will be the easiest step of all.

    Go to "Obj2E_ChkInvinc". Replace this.


    Obj2E_ChkInvinc:
    cmpi.b #5,d0 ; does monitor contain invincibility?
    bne.s Obj2E_ChkRings
    tst.b ($FFFFFE19).w
    bne.s Obj2E_NoMusic
    move.b #1,($FFFFFE2D).w ; make Sonic invincible
    move.w #$4B0,($FFFFD032).w ; time limit for the power-up
    move.b #$38,($FFFFD200).w ; load stars object ($3801)
    move.b #1,($FFFFD21C).w
    move.b #$38,($FFFFD240).w ; load stars object ($3802)
    move.b #2,($FFFFD25C).w
    move.b #$38,($FFFFD280).w ; load stars object ($3803)
    move.b #3,($FFFFD29C).w
    move.b #$38,($FFFFD2C0).w ; load stars object ($3804)
    move.b #4,($FFFFD2DC).w
    tst.b ($FFFFF7AA).w ; is boss mode on?
    bne.s Obj2E_NoMusic ; if yes, branch
    move.w #$87,d0
    jmp (PlaySound).l ; play invincibility music

    With this


    Obj2E_ChkInvinc:
    cmpi.b #5,d0 ; does monitor contain invincibility?
    bne.s Obj2E_ChkRings
    move.b #1,($FFFFFE2D).w ; Set Invisibility to 1
    move.w #$4B0,($FFFFD032).w ; Set Invisibility timer to 4B0
    move.b #$4A,($FFFFD200).w ; load stars object ($3801)
    move.b #1,($FFFFD21C).w
    move.b #$4A,($FFFFD240).w ; load stars object ($3802)
    move.b #2,($FFFFD25C).w
    move.b #$4A,($FFFFD280).w ; load stars object ($3803)
    move.b #3,($FFFFD29C).w
    move.b #$4A,($FFFFD2C0).w ; load stars object ($3804)
    move.b #4,($FFFFD2DC).w
    tst.b ($FFFFF7AA).w ; is boss mode on?
    bne.s DontPlayMusic ; If so, don't play music
    cmpi.w #$C,($FFFFFE14).w ; Check if Sonic has air left
    bls.s DontPlayMusic ; If so, don't play music
    move.w #$87,d0 ; Load Invisibility music
    jmp (PlaySound).l
    DontPlayMusic:
    rts

     Once you have finished, save and build. If you've done everything correctly, your ROM will build.
     
    Last edited by a moderator: Apr 30, 2014
    Nat The Porcupine and Clownacy like this.
Thread Status:
Not open for further replies.