Skip to content

Instantly share code, notes, and snippets.

@Pigu-A
Created November 11, 2018 17:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pigu-A/a836ff717fcefbd6ea2569626db88fb4 to your computer and use it in GitHub Desktop.
Save Pigu-A/a836ff717fcefbd6ea2569626db88fb4 to your computer and use it in GitHub Desktop.
(Actual) Z80 port of Pokemon Crystal's decompressor. Assuming the entire routine is loaded in the RAM.
Decompress:
; Pokemon Crystal uses an lz variant for compression.
; This is mainly (but not necessarily) used for graphics.
; This function decompresses lz-compressed data from hl to de.
LZ_END EQU $ff ; Compressed data is terminated with $ff.
; A typical control command consists of:
LZ_CMD EQU %11100000 ; command id (bits 5-7)
LZ_LEN EQU %00011111 ; length n (bits 0-4)
; Additional parameters are read during command execution.
; Commands:
LZ_DATA EQU 0 << 5 ; Read literal data for n bytes.
LZ_REPEAT_1 EQU 1 << 5 ; Write the same byte for n bytes.
LZ_REPEAT_2 EQU 2 << 5 ; Alternate two bytes for n bytes.
LZ_ZERO EQU 3 << 5 ; Write 0 for n bytes.
; Another class of commands reuses data from the decompressed output.
LZ_COPY EQU 7 ; bit
; These commands take a signed offset to start copying from.
; Wraparound is simulated.
; Positive offsets (15-bit) are added to the start address.
; Negative offsets (7-bit) are subtracted from the current position.
LZ_COPY_NORMAL EQU 4 << 5 ; Repeat n bytes from the offset.
LZ_COPY_FLIPPED EQU 5 << 5 ; Repeat n bitflipped bytes.
LZ_COPY_REVERSED EQU 6 << 5 ; Repeat n bytes in reverse.
; If the value in the count needs to be larger than 5 bits,
; LZ_LONG can be used to expand the count to 10 bits.
LZ_LONG EQU 7 << 5
; A new control command is read in bits 2-4.
; The top two bits of the length are bits 0-1.
; Another byte is read containing the bottom 8 bits.
LZ_LONG_HI EQU %00000011
; In other words, the structure of the command becomes
; 111xxxyy yyyyyyyy
; x: the new control command
; y: the length
; For more information, refer to the code below and in extras/gfx.py.
; Swap de and hl for speed
ex de, hl
; Save the output address
; for rewrite commands.
ld (.LZaddress), hl
.Main
ld a, (de)
cp LZ_END
jr z, .end
and LZ_CMD
cp LZ_LONG
jr nz, .short
.long
; The count is now 10 bits.
; Read the next 3 bits.
; %00011100 -> %11100000
ld a, (de)
add a
add a ; << 3
add a
; This is our new control code.
and LZ_CMD
ld (.buffer), a
ld a, (de)
inc de
and LZ_LONG_HI
ld b, a
ld a, (de)
inc de
ld c, a
; read at least 1 byte
inc bc
jr .command
.end
ex de, hl
ret
.short
ld (.buffer), a
ld a, (de)
inc de
and LZ_LEN
ld c, a
ld b, 0
; read at least 1 byte
inc c
.command
; Modify loop counts to support 8 bit loop counters
ld a, c
and a
jr z, .multiple_of_256
inc b
.multiple_of_256
ld c, b
ld b, a
ld a, 0
.buffer = $-1
bit LZ_COPY, a
jr nz, .copy
cp LZ_REPEAT_1
jr z, .repeat_one
cp LZ_REPEAT_2
jr z, .repeat_two
cp LZ_ZERO
jr z, .zero
; Read literal data for bc bytes.
; Revert modified loop counts back for block transfer
ld a, b
and a
jr z, .multiple_of_256_2
dec c
.multiple_of_256_2
ld b, c
ld c, a
ex de, hl
ldir
ex de, hl
jr .Main
.repeat_one
; Write the same byte for bc bytes.
ld a, (de)
inc de
.repeat_loop
ld (hl), a
inc hl
djnz .repeat_loop
dec c
jr nz, .repeat_loop
jr .Main
.repeat_two
; Alternate two bytes for bc bytes.
; store alternating bytes in d and e
ld a, (de)
inc de
push de
ld (.dst), a
ld a, (de)
ld e, a
ld a, 0
.dst = $-1
ld d, a
; d = byte 1
; e = byte 2
; hl = destination
.repeat_two_loop
ld (hl), d
inc hl
djnz .next_byte
dec c
jr z, .done_repeating
.next_byte
ld (hl), e
inc hl
djnz .repeat_two_loop
dec c
jr nz, .repeat_two_loop
.done_repeating
; Skip past the bytes we were alternating.
pop de
inc de
jr .Main
.zero
; Write 0 for bc bytes.
xor a
jr .repeat_loop
.copy
; Copy decompressed data from previously outputted values.
push de
push hl
ld a, (de)
bit 7, a ; set: relative, clear: absolute
jr z, .absolute
; Relative offsets count backwards from hl and contain an excess of $7f.
; In other words, $80 = hl - 1, $81 = hl - 2, ..., $ff = hl - 128.
cpl
sub $80
ld e, a
ld d, $ff
jr .ok
.absolute
; Absolute offset from the beginning of the output.
ld h, a
inc de
ld a, (de)
ld l, a
ld de, 0
.LZaddress = $-2
.ok
add hl, de
ex de, hl
pop hl
; Determine the kind of copy.
; Note that (.buffer) could also contain LZ_LONG, but that's an error in the command stream, as of now unhandled.
ld a, (.buffer)
cp LZ_COPY_FLIPPED
jr z, .flipped
cp LZ_COPY_REVERSED
jr z, .reversed
; Copy data for bc bytes.
; Revert modified loop counts back for block transfer
ld a, b
and a
jr z, .multiple_of_256_3
dec c
.multiple_of_256_3
ld b, c
ld c, a
ex de, hl
ldir
ex de, hl
jr .done_copying
.flipped
; Copy bitflipped data for bc bytes.
ld a, (de)
inc de
ld (hl), b ;use the current output as buffer
ld b, 0
rept 8
rra
rl b
endr
ld a, b
ld b, (hl)
ld (hl), a
inc hl
djnz .flipped
dec c
jr nz, .flipped
jr .done_copying
.reversed
; Copy byte-reversed data for bc bytes.
ld a, (de)
dec de
ld (hl), a
inc hl
djnz .reversed
dec c
jr nz, .reversed
.done_copying
pop de
ld a, (de)
add a
jr c, .next
inc de ; positive offset is two bytes
.next
inc de
jp .Main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment