Extend ROM Chunks to $FF in Sonic 1

Discussion in 'Tutorials' started by GenesisDoes, Feb 17, 2019.

  1. GenesisDoes

    GenesisDoes Well-Known Member Member

    Joined:
    Jan 2, 2016
    Messages:
    128
    Location:
    Pittsburgh, PA
    A technical problem I had to figure out when developing Socket the Hedgeduck ROM hack was to enable Sonic 1 to use up to $FF chunks in a zone. The original Socket/Time Dominator game had many zones which utilized upto $FF chunks. Socket's chunks are the same size as Sonic 1's (256x256 px or an array of 16x16 blocks), which was a contributing factor to selecting the Sonic 1 engine for the hack.

    Sonic 1 and Sonic CD handle chunks differently than the other Genesis games in the series. Sonic 2 and onwards use chunks sized at 128x128 px (or an array of 8x8 blocks), while Sonic 1 use chunks sized at 256x256 px (or an array of 16x16 blocks). Due to the double-sized chunks, it takes more RAM to hold each chunk's configuration in memory. The stock Sonic 1 engine can only handle $52 chunks (+1 empty one) in RAM, while Sonic 2 and onwards can handle upto $FF chunks in RAM. Despite this RAM problem, it is possible to bypass it by modifying the game to load chunks from ROM for particular zones (what I will refer to as "ROM Chunks"). ROM Chunks are required to be stored uncompressed in ROM (due to too much overhead to decompress from ROM on-the-fly, since we are no longer using RAM to store them uncompressed).

    ChunkSizes.png
    Size comparison between chunk types
    Not only do the chunks have different sizes between Sonic1 and the other games, but Sonic 1 also handles loops and roll tunnels differently from Sonic 2 and onwards. Sonic 2 and onwards use a path swapper object to enable alternate collision for each block for loops and such, and a roll object to force rolling, while Sonic 1 uses some hard-coded LUTs to enable collision swapping for loops and auto-rolling functionality for roll-tunnels. This LUT holds upto 2 chunk IDs for loops and upto 2 chunk IDs for roll tunnels in each zone. LUT slots that aren't used in a zone are just set to $7F. In all of the Sonic games, chunk IDs are a byte long; Sonic 2 and onwards use all 8 bits (upto $FF or 256 chunks), while Sonic 1 uses only 7 bits (upto $7F or 128 chunks). In Sonic 1, the MSB is used as a loop flag; the base chunk ID with the MSB set is the value used in the LUT. (So, for example, if you want to use chunk $35 (%00110101 in binary) in GHZ1 as a loop chunk, either add $7F to base chunk ID or just set the MSB for the chunk ID ($35+$7F=$B4, or %10110101 in binary), and then use this new value in the LUT as the chunk ID.)

    In Sonic 1, the "collision swapping" for loops is done lazily by replacing one chunk with an alternative one, based on which direction Sonic is facing. This alternate chunk for collision is hardcoded to be the next ID. (So if chunk $35 is the first half of the loop, chunk $36 is the other half). There is one exception to this rule: in vanilla Sonic 1, SLZ has a chunk ID that is hardcoded to use another in the collision swapping code. Roll-tunnel chunks in the LUT don't use the MSB flag or any collision swapping; while Sonic is touching these roll-tunnel chunks he will be forced into a roll until he leaves the chunk. Labyrinth Zone also uses a separate LUT of chunk IDs to enable the water slide function.

    S1Loops.png
    Loop system (Sonic 1)
    To make a long story short, even if you use ROM Chunks to bypass the $53 chunk limit imposed by RAM in Sonic 1, you will only be allowed to use upto $7F chunks, due to the loop-flag being the MSB of the chunk ID. With the ROM Chunk system, attempting to use chunk IDs above $7F will display either the wrong graphics or use the wrong collision.

    This tutorial will show the user how to enable particular ROM Chunk zones to use upto $FF ROM Chunks (what I call "Big ROM Chunks") and have the game properly handle the LUTs for loop, roll-tunnel chunks, and LZ water slide chunks. This tutorial is written for Sonic 1 Github disasm, and will handle applying ROM Chunks and Big ROM Chunks to Sonic 1. Credits go to FraGag for the original ROM Chunk guide and to MarkeyJester for some additional commentary on the chunk drawing code; the first part of this tutorial is FraGag's guide rewritten to target Sonic 1 Github's nomenclature. We will also modifiy the SonLVL project files for the disasm to properly handle Big ROM Chunks (S2NA format)chunk IDs.

    Using the uncompressed chunk mappings
    Since the game normally stores compressed data, we need to change it to store uncompressed data instead in Sonic.asm . For example, for Green Hill Zone, replace this:

    Code:
    Blk256_GHZ:    incbin    map256\ghz.bin
           even
    
    with this:

    Code:
    Blk256_GHZ:    incbin    map256_u\ghz.bin
           even
    
    Make sure to add the new map256_u folder to the root of your disasm. The original ROM Chunk guide mentions about modifying the SonLVL files to make the affected ROM Chunk zones point to the proper uncompressed chunk art. To keep things much simpler, in Socket the Hedgeduck, I just keep all zones for SonLVL to point and to handle the compressed chunk art, and let the build batch file call another batch file to decompress the art as appropriately when assembling. You will need to download the command-line forms of The Sega Data Compressor and place "koscmp.exe" and "Kosinski.dll" at the root of your disasm. At the root folder, create a new file called "Uncompress.bat", and fill it in as such:

    Code:
    REM Map256 Decompression (for levels using (BIG) ROM Chunks)
    Cls
    koscmp.exe -x -v map256\GHZ.bin map256_u\GHZ.bin
    
    REM Add other entries like this to decompress for other (Big) ROM Chunk zones
    In build.bat, above this line

    Code:
    asm68k /k /p /o ae- sonic.asm, s1built.bin >errors.txt, , sonic.lst
    add

    Code:
    REM Map256 Decompression
    Call "Uncompress.bat"
    This will decompress the appropriate zone's chunks before building.


    Skipping loading chunks to RAM (title screen)
    If you decide to read the chunks from ROM for Green Hill Zone and you still use the Green Hill Zone data for your title screen, you should skip decompressing the chunks to avoid overwriting other data in RAM. In Sonic.asm, goto Tit_LoadText and find the following lines:

    Code:
          lea    (Blk256_GHZ).l,a0 ; load GHZ 256x256 mappings
          lea    (v_256x256).l,a1
          bsr.w    KosDec
    Comment them out.

    Skipping loading chunks to RAM (levels)
    In Sonic.asm at LevelDataLoad, the chunks data, amongst others, is decompressed to RAM. We are going to disable this. Find the following lines:

    Code:
           lea   (v_256x256).l,a1 ; RAM address for 256x256 mappings
           bsr.w   KosDec
           bsr.w   LevelLayoutLoad
           move.w   (a2)+,d0
           move.w   (a2),d0
           andi.w   #$FF,d0
           cmpi.w   #(id_LZ<<8)+3,(v_zone).w ; is level SBZ3 (LZ4) ?
           bne.s   @notSBZ3   ; if not, branch
           moveq   #palid_SBZ3,d0   ; use SB3 palette
    
    and replace them with the following:

    Code:
          cmpi.b    #id_GHZ,(v_zone).w     ; are we in GHZ?
          beq.s    @no_dec                ; if yes, branch
          cmpi.b    #id_MZ,(v_zone).w     ; are we in MZ?
          beq.s    @no_dec                    ; if yes, branch
          ;Ditto for other level slots that use ROM Chunks...
      
    @dec: 
            lea    (v_256x256).l,a1    ; RAM address for 256x256 mappings
            jsr    KosDec
    
    @no_dec:
            bsr.w    LevelLayoutLoad
            move.w    (a2)+,d0
            move.w    (a2),d0
            andi.w    #$FF,d0  
            cmpi.w    #(id_LZ<<8)+3,(v_zone).w ; is level SBZ3 (LZ4) ?
            bne.s    @notSBZ3    ; if not, branch
            moveq    #palid_SBZ3,d0    ; use SB3 palette
    
    Fixing art
    The chunks are composed of blocks, which contain art tiles. Since the chunks are not in RAM anymore, you will get empty levels. In Sonic.asm, goto DrawBlocks_2 and replace the code from

    Code:
            moveq    #-1,d3
    to
    Code:
            rts
    with

    Code:
            cmpi.b    #id_GHZ,(v_zone).w     ; are we in GHZ?
            beq.w    @ghz                    ; if yes, branch
            cmpi.b    #id_MZ,(v_zone).w     ; are we in MZ?
            beq.s    @mz                        ; if yes, branch
           ;Repeat for all other zones with ROM Chunks...
    
    @NoRomChunks:
            moveq    #-1,d3                    ; load chunks from RAM. MJ: prepare FFFF in d3
            bsr.w    LocateBlock
            jmp        @Continue
      
    @ghz:
            moveq    #0,d3
            bsr.s    LocateBlock
            add.l    #Blk256_GHZ,d3
            bra.w        @Continue
    
    @mz:
            moveq    #0,d3
            bsr.w    LocateBlock
            add.l    #Blk256_MZ,d3
            bra.s        @Continue
            nop
    
    ;Repeat with local labels for all other ROM Chunk zones,
    ;replacing the Blk256 label with the appropriate one for the zone
      
    @continue:
            movea.l    d3,a0                    ; MJ: set address (Chunk to read)
            move.w    (a0),d3
            andi.w    #$3FF,d3
            lsl.w    #3,d3
            adda.w    d3,a1
            rts
    ; ---------------------------------------------------------------------------
    
    LocateBlock:
            move.b    (a4,d0.w),d3    ; load chunk ID in d3. MJ: collect correct chunk ID from layout
            beq.s    LocateBlock_EmptyChunk
            subq.b    #1,d3
            andi.w    #$7F,d3                    ; MJ: keep within 7F
            ror.w    #7,d3     
            add.w    d4,d4
            andi.w    #$1E0,d4        ; MJ: keep Y pos within 480 pixels (?)
            andi.w    #$1E,d5            ; MJ: keep X pos within 30(?) 
            add.w    d4,d3            ; MJ: add calc'd Y pos to ror'd d3
            add.w    d5,d3            ; MJ: add calc'd X pos to ror'd d3
            rts
    ; ---------------------------------------------------------------------------
    
    LocateBlock_EmptyChunk:
            addq.w    #4,sp    ; pop a stack frame to leave a1 pointing at the first tile
            rts
    
    Fixing collision
    The chunks also contain collision information, so now we need to tell the game that the data is loaded elsewhere. In the file "_incObj\sub FindNearestTile.asm", find the Floor_ChkTile_LocateBlock subroutine and replace the entirety with the following code:

    Code:
    ; ||||||||||||||| S U B   R O U T   I N E |||||||||||||||||||||||||||||||||||||||
    
    
    Floor_ChkTile_LocateBlock:
           lea   (v_lvllayout).w,a1
           move.b   (a1,d0.w),d1   ; get 256x256 tile number
           beq.w   @blanktile   ; branch if 0
           bmi.s   @specialtile   ; branch if >$7F
           subq.b   #1,d1
           ext.w   d1
           ror.w   #7,d1
           move.w   d2,d0
           add.w   d0,d0
           andi.w   #$1E0,d0
           add.w   d0,d1
           move.w   d3,d0
           lsr.w   #3,d0
           andi.w   #$1E,d0
           add.w   d0,d1
           rts
    ; ---------------------------------------------------------------------------
    
    @specialtile:
           andi.w   #$7F,d1               ;Keep chunk ID within $7F
           btst   #6,obRender(a0) ; is object "behind a loop"?
           beq.s   @treatasnormal   ; if not, branch
           addq.w   #1,d1
     
           ;Code below is Sega's sleazy SLZ hardcoded loop IDs. If SLZ is modified, comment out
           ;In SLZ, If chunk ID=$29, use $51 as loop complement    
           cmpi.b   #id_slz,(v_zone).w    ; are we in slz?
           bne.s   @treatasnormal       ; if not, skip Sega's shameful hardcoding
           cmpi.w   #$29,d1
           bne.s   @treatasnormal
           move.w   #$51,d1
    
       @treatasnormal:
           subq.b   #1,d1
           ror.w   #7,d1
           move.w   d2,d0
           add.w   d0,d0
           andi.w   #$1E0,d0
           add.w   d0,d1
           move.w   d3,d0
           lsr.w   #3,d0
           andi.w   #$1E,d0
           add.w   d0,d1
           rts
    ; ---------------------------------------------------------------------------
    
    @blanktile:
           lea   ($FFFFFF00).w,a1   ; override a1
           addq.w   #4,sp           ; pop a stack frame to avoid adding the address of the chunk mappings to a1
           rts
    
    ; ===========================================================================
    
    ; ---------------------------------------------------------------------------
    ; Subroutine to   find which tile   the object is standing on
    
    ; input:
    ;   d2 = y-position of object's bottom edge
    ;   d3 = x-position of object
    
    ; output:
    ;   a1 = address within 256x256 mappings where object is standing
    ;        (refers to a 16x16 tile number)
    ; ---------------------------------------------------------------------------
    
    FindNearestTile:
           move.w   d2,d0       ; get y-pos. of bottom edge of object
           lsr.w   #1,d0
           andi.w   #$380,d0
           move.w   d3,d1       ; get x-pos. of object
           lsr.w   #8,d1
           andi.w   #$7F,d1
           add.w   d1,d0       ; combine    
        
           ;Add other checks here for other ROM Chunk zones
           cmpi.b   #id_ghz,(v_zone).w ; are we in GHZ?
           beq.s   @ghz              ; if yes, branch
           cmpi.b   #id_mz,(v_zone).w ; are we in MZ?
           beq.s   @mz              ; if yes, branch
           moveq   #-1,d1
           bsr.w   Floor_ChkTile_LocateBlock
           movea.l   d1,a1
           rts
    ; ---------------------------------------------------------------------------
    @ghz:
           moveq   #0,d1
           bsr.w   Floor_ChkTile_LocateBlock
           add.l   #Blk256_GHZ,d1
           movea.l   d1,a1
           rts        
        
    @mz:
           moveq   #0,d1
           bsr.w   Floor_ChkTile_LocateBlock
           add.l   #Blk256_MZ,d1
           movea.l   d1,a1
           rts    
    ; End of function
    
    Compared to the original guide, I have added an additional check to apply Sega's sleazy loop chunk ID hardcoding for only SLZ's loops. In the original game, if the chunk loop ID is $29 (assumed to only happen in SLZ), it's complement will be $51; otherwise the next chunk ID is used. If your chunk IDs shuffle around for SLZ, you should either adjust or comment out the affected code appropriately.

    And you've did it you can now have up to $80 Chunks and it frees a lot of ram! ($0000 to $A3FF) Which means you can Sonic 3's Object and ring managers to sonic 1!

    Please proceed to the Big ROM Chunks guide below to extend ROM Chunks to $FF chunks, and to learn how to enable loops in zones other than GHZ or SLZ.

    This tutorial will modify the ROM Chunks system to allow up to $FF chunks ("Big ROM Chunks"). It will allow you to selectively enable Big ROM Chunks for particular ROM Chunk zones, and utilizes a new flag to tell the game when to do so. Essentially it removes ANDing the chunk ID by $7F, freeing usage to higher chunk IDs for various LUTs.

    Big ROM Chunks setup

    In order to allow SonLVL to handle Big ROM Chunks, in "SonLVL INI Files\SonLVL.ini" underneath

    Code:
    romfile=../s1built.bin
    
    add

    Code:
    layoutfmt=S2NA
    
    This will enable SonLVL to handle Sonic 2 Nick Arcade format level layouts (upto $FF Sonic 1 chunks). Any levels which utilize Big ROM Chunks and which use loops (such as GHZ and SLZ) shouldn't use SonLVL's loopchunk setting. If they do use the setting, remove the line. The setting will loop something like this
    Code:
    loopchunks=35
    in the zone's act definitions.

    Next we will setup a new flag to indicate Big ROM Chunks. In variables.asm, after the line

    Code:
    v_limitleft3:   = $FFFFF732   ; left level boundary, at the end of an act (2 bytes)
    add
    Code:
    f_ROMChunk:        = $FFFFF734    ; Is this a ROM-Chunk level?
    Fixing the art (toggle flag, extend the chunk IDs)

    Where the game adjusts the block's art for chunks, the game will need to set/reset the Big ROM Chunk flag appropriately. In Sonic.asm, goto the line labeled DrawBlocks_2. Underneath there, you will find the local label @NoRomChunks. Add this line underneath the label

    Code:
            move.b    #0, (f_ROMChunk).w        ; Turn ROM Chunks off
    
    Directly underneath @NoRomChunks, you should have many local labels for each ROM Chunk zone to point to the appropriate Blk256 labels and load chunks from ROM. Underneath each zone's local label in which you wish to extend to Big ROM Chunks, add this line

    Code:
            move.b    #1, (f_ROMChunk).w        ;!@ BIG_Rom_Chunk. Set ROM_Chunk flag
    For example, for the changes to the local label of @ghz used in the prior tutorial

    Code:
    @ghz:
            move.b    #1, (f_ROMChunk).w        ;!@ BIG_Rom_Chunk. Set ROM_Chunk flag
            moveq    #0,d3
            bsr.s    LocateBlock
            add.l    #Blk256_GHZ,d3
            bra.w        @Continue
    
    Close by you should find the line labeled LocateBlock. Change this

    Code:
    LocateBlock:
            move.b    (a4,d0.w),d3    ; load chunk ID in d3. MJ: collect correct chunk ID from layout
            beq.s    LocateBlock_EmptyChunk
            subq.b    #1,d3
            andi.w    #$7F,d3                    ; MJ: keep within 7F
            ror.w    #7,d3     
            add.w    d4,d4
            andi.w    #$1E0,d4        ; MJ: keep Y pos within 480 pixels (?)
            andi.w    #$1E,d5            ; MJ: keep X pos within 30(?) 
            add.w    d4,d3            ; MJ: add calc'd Y pos to ror'd d3
            add.w    d5,d3            ; MJ: add calc'd X pos to ror'd d3
            rts
    
    into

    Code:
    LocateBlock:
            move.b    (a4,d0.w),d3    ; load chunk ID in d3. MJ: collect correct chunk ID from layout
            beq.s    LocateBlock_EmptyChunk
            subq.b    #1,d3
    
            ;!@ BIG_ROM_Chunks
            tst.b    (f_ROMChunk).w    ;!@ Is this a ROM Chunk level?
            bne.s    @BIG_Chunks        ; if so, branch
      
            andi.w    #$7F,d3                    ; MJ: keep within 7F
            ror.w    #7,d3     
            bra.s    @LocateBlock_Continue    ;!@ BIG_Rom_Chunks
     
        ;!@ BIG_ROM_Chunks
        @BIG_Chunks:
            andi.w    #$FF,d3            ; Keep within FF instead
            lsl.l    #8,d3
            add.l    d3,d3
    
        @LocateBlock_Continue:
            add.w    d4,d4
            andi.w    #$1E0,d4        ; MJ: keep Y pos within 480 pixels (?)
            andi.w    #$1E,d5            ; MJ: keep X pos within 30(?)
      
            add.w    d4,d3            ; MJ: add calc'd Y pos to ror'd d3
            add.w    d5,d3            ; MJ: add calc'd X pos to ror'd d3
      
        @LocateBlock_locret:
            rts
    If the Big ROM Chunk flag is set, the chunk ID will be limited to $FF chunks; otherwise $7F (standard Sonic 1 chunk behavior).

    Fixing collision (handle flag, extend chunk IDs)

    In the file "_incObj\sub FindNearestTile.asm", replace the entirety with the following:
    Code:
    ; ||||||||||||||| S U B   R O U T   I N E |||||||||||||||||||||||||||||||||||||||
    
    
    Floor_ChkTile_LocateBlock:
           ;!@ BIG_Rom_Chunk
           lea       (v_lvllayout).w,a1    
           tst.b   (f_ROMChunk).w       ;Is this a Big ROM Chunk level?
            bne.s   @bigchunk           ;if so, branch    
        
           move.b   (a1,d0.w),d1   ; get 256x256 tile number
           beq.w   @blanktile   ; branch if 0
           bmi.s   @specialtile   ; branch if >$7F
           bra.s   @continue
        
       @bigchunk:
           move.b   (a1,d0.w),d1       ; get 256x256 tile number
           beq.w   @blanktile           ; branch if 0
        
           cmp.b   (v_256loop1).w,d1    ; is Sonic on 1st loop tile?
           beq.s   @specialtile       ; if yes, branch
           cmp.b   (v_256loop2).w,d1   ; is Sonic on 2nd loop tile?
           beq.s   @specialtile       ; if yes, branch
        
       @continue:
           subq.b   #1,d1
        
           ;!@ Big_ROM_Chunk
           tst.b   (f_ROMChunk).w       ;Is this a Big ROM Chunk level?
           bne.s   @normalfix           ;if so, branch    
        
           ;Keep within 7F
           ext.w   d1
           ror.w   #7,d1
           bra.s   @normalcont
        
       ;!@ Big_Rom_Chunk
       @normalfix:
           andi.w   #$FF,d1            ; Keep within FF instead
            lsl.l    #8,d1
            add.l    d1,d1
        
       @normalcont:    
           move.w   d2,d0
           add.w   d0,d0
           andi.w   #$1E0,d0
           add.w   d0,d1
           move.w   d3,d0
           lsr.w   #3,d0
           andi.w   #$1E,d0
           add.w   d0,d1
           rts
    ; ---------------------------------------------------------------------------
    
    @specialtile:
           ;!@ Big_Rom_Chunk
           tst.b   (f_ROMChunk).w       ;Is this a Big ROM Chunk level?
           bne.s   @bigandy           ;if so, branch            
           andi.w   #$7F,d1               ;Keep chunk ID within $7F
           bra.s   @specialcont
       @bigandy:
           andi.w   #$FF,d1               ;Keep chunk ID within $FF instead
        
       @specialcont:
           btst   #6,obRender(a0) ; is object "behind a loop"?
           beq.s   @treatasnormal   ; if not, branch
           addq.w   #1,d1
     
           ;Code below is Sega's sleazy SLZ hardcoded loop IDs. If SLZ is modified, comment out
           ;In SLZ, If chunk ID=$29, use $51 as loop complement    
           cmpi.b   #id_slz,(v_zone).w    ; are we in slz?
           bne.s   @treatasnormal       ; if not, skip Sega's shameful hardcoding
           cmpi.w   #$29,d1
           bne.s   @treatasnormal
           move.w   #$51,d1
    
       @treatasnormal:
           subq.b   #1,d1
           ;!@ Big_Rom_Chunk
           tst.b   (f_ROMChunk).w       ;Is this a Big ROM Chunk level?
           bne.s   @treatbigfix       ;if so, branch                    
           ;Keep within $7FF
           ror.w   #7,d1
           bra.s   @treatcont
        
       @treatbigfix:
           ;Keep within $FF
            lsl.l    #8,d1
            add.l    d1,d1
        
       @treatcont:    
           move.w   d2,d0
           add.w   d0,d0
           andi.w   #$1E0,d0
           add.w   d0,d1
           move.w   d3,d0
           lsr.w   #3,d0
           andi.w   #$1E,d0
           add.w   d0,d1
           ;movea.l   d1,a1
           rts
    ; ---------------------------------------------------------------------------
    
    @blanktile:
           lea   ($FFFFFF00).w,a1   ; override a1
           addq.w   #4,sp           ; pop a stack frame to avoid adding the address of the chunk mappings to a1
           rts
    
    ; ===========================================================================
    
    ; ---------------------------------------------------------------------------
    ; Subroutine to   find which tile   the object is standing on
    
    ; input:
    ;   d2 = y-position of object's bottom edge
    ;   d3 = x-position of object
    
    ; output:
    ;   a1 = address within 256x256 mappings where object is standing
    ;        (refers to a 16x16 tile number)
    ; ---------------------------------------------------------------------------
    
    FindNearestTile:
           move.w   d2,d0       ; get y-pos. of bottom edge of object
           lsr.w   #1,d0
           andi.w   #$380,d0
           move.w   d3,d1       ; get x-pos. of object
           lsr.w   #8,d1
           andi.w   #$7F,d1
           add.w   d1,d0       ; combine    
        
           ;Add other checks here for other ROM Chunk zones
           cmpi.b   #id_ghz,(v_zone).w ; are we in GHZ?
           beq.s   @ghz              ; if yes, branch
           cmpi.b   #id_mz,(v_zone).w ; are we in MZ?
           beq.s   @mz              ; if yes, branch
           moveq   #-1,d1
           bsr.w   Floor_ChkTile_LocateBlock
           movea.l   d1,a1
           rts
    ; ---------------------------------------------------------------------------
    @ghz:
           moveq   #0,d1
           bsr.w   Floor_ChkTile_LocateBlock
           add.l   #Blk256_GHZ,d1
           movea.l   d1,a1
           rts        
        
    @mz:
           moveq   #0,d1
           bsr.w   Floor_ChkTile_LocateBlock
           add.l   #Blk256_MZ,d1
           movea.l   d1,a1
           rts    
    ; End of function
    
    If Big ROM Chunks are not used, the chunk ID in which Sonic is located in will be compared against $00 (empty chunk), if negative (>$7F, loop chunks), or anything else (normal chunks). If the chunk is $0, a stack frame is popped to avoid messing up address arithmetic for proper usage. If it is negative (a loop chunk), $7F is subtracted from the loop chunk ID to get the real chunk ID, and either this ID or the next one are used for collision. (The exception to this rule is the sleazily hardcoded loop chunk in SLZ.) Which ID is used is determined by Sonic's current sprite priority (low/high), handled by Sonic_Loops function. For all other normal chunks, the chunk ID is kept within $7F.

    If the Big ROM Chunks are used, the comparisons and actions to take work as usual, except for the following. For loop chunks, the chunk ID is compared directly against the 2 loop chunk IDs stored in RAM from the loop/roll-tunnel LUT (no fiddling with the MSB) instead of based on if it is negative. Chunks are kept within $FF for math for all three cases.

    Extending Loop/Roll LUT to handle Big ROM Chunks
    The file "_inc\LevelSizeLoad & BgScrollSpeed.asm" contains the LUT with chunk IDs for loops and roll-tunnels. It can be found at label LoopTileNums, and looks something like

    Code:
    ; ---------------------------------------------------------------------------
    ; Which    256x256    tiles contain loops or roll-tunnels
    ; ---------------------------------------------------------------------------
    
    LoopTileNums:
    
    ;         loop    loop    tunnel    tunnel
    
        dc.b   $B5,   $7F,   $1F,   $20   ; Green Hill
        dc.b    $7F,    $7F,    $7F,    $7F    ; Labyrinth
        dc.b    $7F,    $7F,    $7F,    $7F    ; Marble
        dc.b    $AA,    $B4,    $7F,    $7F    ; Star Light
        dc.b    $7F,    $7F,    $7F,    $7F    ; Spring Yard
        dc.b    $7F,    $7F,    $7F,    $7F    ; Scrap Brain
        dc.b    $7F,    $7F,    $7F,    $7F    ; Ending (Green Hill)
        zonewarning LoopTileNums,4
    
            even
    
    ; ===========================================================================
    As previously explained, the first 2 bytes for each zone's entry is the chunk ID for upto 2 loop chunks, while the last 2 bytes are for roll-tunnels. These should be updated accordingly for your modified zones. It is strongly recommended to set unused bytes for Big ROM Chunk zones to $FF; normal levels should use $7F. Both chunk $7F and $FF should ideally be empty. Big ROM Chunk zones require using just the absolute chunk ID (no need to fiddle with the MSB) for the LUT's values.

    The file "_incObj\Sonic Loops.asm" actually handles being rolled by roll-tunnels and settings Sonic's proper sprite priority (which drives the loop collision), based on the IDs in the above LUT. At the beginning of the function are some checks to only allow roll-tunnel/loops in certain zones. You will need to adjust these comparisons appropriately in order to allow loops/roll-tunnels in other zones.

    Those comparisons look something like this:

    Code:
    ;
    ---------------------------------------------------------------------------
    ; Subroutine to    make Sonic run around loops (GHZ/SLZ)
    ; ---------------------------------------------------------------------------
    
    ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    Sonic_Loops:
            ;Comparisons here
            cmpi.b    #id_SLZ,(v_zone).w ; is level SLZ ?
            beq.s    @isstarlight    ; if yes, branch
            tst.b    (v_zone).w    ; is level GHZ ?
            bne.w    @noloops    ; if not, branch
    
        @isstarlight:
           ;Code to handle loops/roll-tunnels here
            rts
    
       @noloops:
          ;Skips loop/roll-tunnel handling
          rts
    ; ===========================================================================
    
    Fortunately, that function does comparisons directly against the chunk ID (no fiddling with the MSB), so no adjustments are needed for Big ROM Chunks.

    Extending LZ water slides to handle Big ROM Chunks
    In the file "_inc\LZWaterFeatures.asm" is another LUT with chunk IDs used to make Sonic ride down water slides. it can be found at label Slide_Chunks and looks like

    Code:
    Slide_Chunks:
           dc.b 2, 7, 3, $4C, $4B, 8, 4
    
    Update this LUT as appropriately. The actual sliding is handled at LZWaterSlides label. Change

    Code:
    LZWaterSlides:
            lea    (v_player).w,a1
            btst    #1,obStatus(a1)    ; is Sonic jumping?
            bne.s    loc_3F6A    ; if not, branch
            move.w    obY(a1),d0
            lsr.w    #1,d0
            andi.w    #$380,d0
            move.b    obX(a1),d1
            andi.w    #$7F,d1
            add.w    d1,d0
            lea    (v_lvllayout).w,a2
            move.b    (a2,d0.w),d0
            lea    Slide_Chunks_End(pc),a2
            moveq    #Slide_Chunks_End-Slide_Chunks-1,d1
    
    into

    Code:
    LZWaterSlides:
            lea    (v_player).w,a1
            btst    #1,obStatus(a1)    ; is Sonic jumping?
            bne.s    loc_3F6A    ; if not, branch
            move.w    obY(a1),d0
            lsr.w    #1,d0
            andi.w    #$380,d0
            move.b    obX(a1),d1
       
        ;!@ BIG_ROM_Chunks
            tst.b    (f_ROMChunk).w    ;!@ Is this a ROM Chunk level?
            bne.s    @bigandy        ; if so, branch  
            andi.w    #$FF,d1
            beq.s    @continue
        @bigandy:
            andi.w    #$7F,d1
       
        @continue:
            add.w    d1,d0
            lea    (v_lvllayout).w,a2
            move.b    (a2,d0.w),d0
            lea    Slide_Chunks_End(pc),a2
            moveq    #Slide_Chunks_End-Slide_Chunks-1,d1
    
    in order to handle water slides with higher chunk IDs.

    Congratulations, you have extended ROM Chunks to handle upto $FF chunks! Use this to build larger, more complex zones.

    ROM_Chunks.png
    Proper collision/properties of
    Big ROM Chunks in action
     
    Last edited: Feb 20, 2019
  2. ProjectFM

    ProjectFM Optimistic and self-dependent Member

    Joined:
    Oct 4, 2014
    Messages:
    782
    Location:
    Portland, Maine
    Why don't you use the path swapping objects that Sonic 2 and 3K use? MarkeyJester's Project 128 disassembly has them pre-ported over.
     
  3. GenesisDoes

    GenesisDoes Well-Known Member Member

    Joined:
    Jan 2, 2016
    Messages:
    128
    Location:
    Pittsburgh, PA
    I could, but ideally, this guide should have the loop system work with both Big ROM Chunks and with normal Sonic 1 RAM chunks or small ROM Chunks (for backwards compatibility purposes), especially since MarkeyJester's guide has the path swapper system ported over for Sonic 2 styled chunks, not for Sonic 1 styled ones. The guide is meant for maxing out the range of Sonic 1 chunks to an entire byte's worth, for those who need more 256 px chunks (like I needed for Socket the Hedgeduck). Not having the loop system functional wasn't an issue with Socket the Hedgeduck, since Socket doesn't have any real loops (closest thing to a loop is depicted below, and I was able to get away without using it). It is an issue for anybody wishing to use Big ROM Chunks in a more generic use case, however.

    Loops.png

    The problem I was having with the guide is that the loop chunks won't update to their alternate chunk when Sonic goes upside down with Big ROM Chunk levels; I haven't yet been able to locate where in the game's code it handles swapping the loop chunk. (Sonic_Loops function only handles changing Sonic's low/high sprite priority as he goes upside down in a loop; I have a hunch the chunk swapping is buried within several Sonic physics-related functions, using something more complex than an andi #$7f, some_data_register opcode (where some_data_register opcode is the current chunk ID Sonic is located in).

    Anyone know about the specifics on how Sonic 1 swaps the loop chunk?
     
  4. Natsumi

    Natsumi Phoenix egg Member

    Joined:
    Oct 7, 2011
    Messages:
    684
    Location:
    Long and dangerous river
    loc_1499A handles the loops, by adding 1 to the chunk ID, if the chunk ID has MSB set, and the "high plane" flag is set for Sonic. All you'd really have to do, is modify it to work with $FF chunks. Of course, you have to pay careful attention to the loop chunks code as well, to make sure it detects the player correctly.
     
  5. ProjectFM

    ProjectFM Optimistic and self-dependent Member

    Joined:
    Oct 4, 2014
    Messages:
    782
    Location:
    Portland, Maine
    Now that I think about it, I release how that couldn't work since Sonic 1 uses only 1 collision plane and does loops differently. I don't know exactly how Sonic 1's loops work, but I think it would be best to hardcode which chunks are loops like what is done with the tunnels.
     
  6. MarkeyJester

    MarkeyJester ! % # @ Member

    Joined:
    Jun 27, 2009
    Messages:
    2,725
    Expanding the 256x256 per chunk byte limit as opposed to switching to 128x128 is a fair enough decision in my book, you do get better overall chunk space, though I would still recommend 128x128.

    ...but the path swapping, you would be foolish to say no to that form of advantage. There is enough free space within the chunk (per word block) to hold a secondary "Top/LRB" setting, the bit order just needs to be rearranged a little, I would say that the addition of a second path of bits would cause less efficient compression, but since you have the chunks uncompressed stored in ROM, the space would effectively be identical in size for the data.

    As others have said, this would also allow you to surpass 7F as your limit as the MSB would not be reserved for chunk swapping. Both SonED and SonLVL should support a mix-mash of chunk formats and multi-path support, so it's not like you would be at a disadvantage in terms of tools either.

    Bottom line, if you wanna say no to 128x128, fine, but don't say no to path swappers, the flexibility you'll throw away would be absolutely insulting to your project. It's not to say you cannot make your desired layout without them, but it would make your laying out job much easier and it would solve your 7F problem.
     
  7. GenesisDoes

    GenesisDoes Well-Known Member Member

    Joined:
    Jan 2, 2016
    Messages:
    128
    Location:
    Pittsburgh, PA
    I've updated the guide with the proper fixes to support loops with Big ROM Chunks. For fixing Big ROM Chunks, loop chunk IDs are detected by comparing the current chunk ID against the values in the loop/roll-tunnel LUT and keeping the chunk ID within $FF, instead of checking if the chunk value is >$7F. This allows backwards compatibility with the normal loop system and gets the new system to work.
     
    KCEXE and ProjectFM like this.