; Sonic 1 tile decompressor
;
; Needs 8 bytes of RAM for temporary storage. Define Sonic1TileLoaderMemory as the start address of the RAM to use.

.block "TileLoaderSonic1"
.section "Tile loader (Sonic 1)" free

; RAM usage
.enum Sonic1TileLoaderMemory export
Sonic1TileLoader_StartOfData dw
Sonic1TileLoader_RowCount dw
Sonic1TileLoader_UniqueRowsData dw
Sonic1TileLoader_ArtData dw
.ende

; A definition
.define SMS_VDP_DATA $be

Sonic1TileLoader_Decompress:
; Arguments:
; hl = data address
; VDP write address should be already set
; Uses af,bc,de,bc',de',hl'
; Note: this has been slightly modified from what is found in Sonic 1, for size and speed

; See http://info.sonicretro.org/SCHG:Sonic_the_Hedgehog_%288-bit%29#Header for details on the format

    ld (Sonic1TileLoader_StartOfData),hl

    ; Skip the "48 59" art header marker
    inc hl
    inc hl

    ; Read in the various offsets and convert to pointers:
    ; - BC is the row count, this counts down to zero as we process each row
    ; - DE points into the art data
    ; - DE' points into the duplicate rows data (per-tile bitmasks)
    ; - HL' points at the start of the art data


    ; Read the DuplicateRows offset into DE and save for later
    ld e,(hl)
    inc hl
    ld d,(hl)
    inc hl
    push de
        ; Read the ArtData offset into DE and save for later
        ld e,(hl)
        inc hl
        ld d,(hl)
        push de
            ; Read the row count into BC
            inc hl
            ld c,(hl)
            inc hl
            ld b,(hl)
            inc hl

            ld (Sonic1TileLoader_RowCount),bc       ; Store the row count
            ld (Sonic1TileLoader_UniqueRowsData),hl ; Where the UniqueRows list begins

            ; swap BC/DE/HL with their shadow values
            exx

            ; load DE with the absolute starting address of the art header; the DuplicateRows and ArtData values are always relative to this
            ld de,(Sonic1TileLoader_StartOfData)
        pop hl ; pull the ArtData value from the stack
        add hl,de ; get the absolute address of ArtData
        ld (Sonic1TileLoader_ArtData),hl ; and save it
        ; copy it to BC. this will be used to produce a counter from 0 to RowCount
        ld c,l
        ld b,h
    pop hl ; load HL with the DuplicateRows offset
    add hl,de ; get the absolute address of DuplicateRows

    ; swap DE & HL. DE will now be the DuplicateRows absolute address,
    ; and HL will be the absolute address of the art header
    ex de,hl

    ; now swap the original values back,
    ; BC will be the row counter
    ; DE will be the ArtData value
    exx

_processRow:
    ld hl,(Sonic1TileLoader_RowCount) ; load HL with the original row count number
    xor a       ; set A to 0 (Carry is reset)
    sbc hl,bc   ; subtract current counter from the row count - that is, count upwards from 0
    push hl
        ; get the row number in the current tile (0-7):
        ld d,a          ; zero-out D
        ld a,l          ; load A with the lo-byte of the counter
        and %00000111   ; clip to the first three bits, that is, "mod 8" it so it counts 0-7
        ld e,a          ; load E with this value, making it a 16-bit number in DE
        ld hl,_rowIndexTable
        add hl,de
        ld a,(hl)       ; get the bit mask for the particular row
    pop de

    ; divide the counter by 4
    srl d
    rr e
    srl d
    rr e
    srl d
    rr e

    ld hl,(Sonic1TileLoader_UniqueRowsData) ; the absolute address where the UniqueRows list begins
    add hl,de   ; add the counter, so move along to the DE'th byte in the UniqueRows list
    ld e,a
    ld a,(hl)   ; read the current byte in the UniqueRows list
    and e       ; test if the masked bit is set
    jr nz,_duplicateRow ; if the bit is set, it's a duplicate row, otherwise continue for a unique row

_uniqueRow:
    ; swap back the BC/DE/HL shadow values
    ; BC will be the pointer to the ArtData
    exx
        ; write 1 row of pixels (4 bytes) to the VDP
        ld a,(bc)
        out (SMS_VDP_DATA),a
        inc bc
        ld a,(bc)
        out (SMS_VDP_DATA),a
        inc bc
        ld a,(bc)
        out (SMS_VDP_DATA),a
        inc bc
        ld a,(bc)
        out (SMS_VDP_DATA),a
        inc bc
    exx
    jr _endOfRow

_duplicateRow:

    ; swap in the BC/DE/HL shadow values
    ; DE will be the pointer to the DuplicateRows data
    exx
        ld a,(de) ; read a byte from the duplicate rows list
        inc de ; move to the next byte
    exx

    ; HL will be re-purposed as the index into the art data
    ld h,0
    ; Check if the byte from the duplicate rows list begins with $f. This is used as a marker to specify a two-byte number for indexes over 256.
    cp $f0
    jr c,+      ; if less than $f0, use it as-is

    sub $f0     ; else, strip the $f0, i.e $f3 -> $03
    ld h,a      ; and set as the hi-byte for the art data index
    exx         ; fetch the next byte into A
        ld a,(de)
        inc de
    exx

+:  ; multiply the duplicate row's index number to the art data by 4 - each row of art data is 4 bytes
    ld l,a
    add hl,hl
    add hl,hl

    ld de,(Sonic1TileLoader_ArtData) ; get the absolute address to the art data
    add hl,de ; add the index from the duplicate row list

    ; write 1 row of pixels (4 bytes) to the VDP
    ld a,(hl)
    out (SMS_VDP_DATA),a
    inc hl
    ld a,(hl)
    out (SMS_VDP_DATA),a
    inc hl
    ld a,(hl)
    out (SMS_VDP_DATA),a
    inc hl
    ld a,(hl)
    out (SMS_VDP_DATA),a
    inc hl

_endOfRow:    
    ; decrease the remaining row count
    dec bc

    ; check if all rows have been done
    ld a,b
    or c
    jr nz,_processRow
    ret

_rowIndexTable:
.db %00000001
.db %00000010
.db %00000100
.db %00001000
.db %00010000
.db %00100000
.db %01000000
.db %10000000

.ends
.endb



Return to top
0.167s