******************************** * * * LZ4FH uncompression for 6502 * * By Andy McFadden * * Version 1.0.1, August 2015 * * * * Refactored for size & speed * * by Peter Ferrie. * * * * Developed with Merlin-16 * * * ******************************** lst off org $0300 * * Constants * lz4fh_magic equ $66 ;ascii 'f' tok_empty equ 253 tok_eod equ 254 overrun_check equ 0 * * Variable storage * srcptr equ $3c ;2b a1l dstptr equ $3e ;2b a1h copyptr equ $00 ;2b savmix equ $02 ;1b savlen equ $03 ;1b * * ROM routines * bell equ $ff3a monitor equ $ff69 * * Parameters, stashed at the top of the text input * buffer. We use this, rather than just having them * poked directly into the code, so that the 6502 and * 65816 implementations work the same way without * either getting weird. * in_src equ $2fc ;2b in_dst equ $2fe ;2b entry lda in_src ;copy source address to zero page sta srcptr lda in_src+1 sta srcptr+1 lda in_dst ;copy destination address to zero page sta dstptr lda in_dst+1 sta dstptr+1 sta _desthi+1 ldy #$00 lda (srcptr),y cmp #lz4fh_magic ;does magic match? beq goodmagic fail jsr bell jmp monitor * These stubs increment the high byte and then jump * back. This saves a cycle because branch-not-taken * becomes the common case. We assume that we're not * unpacking data at $FFxx, so BNE is branch-always. hi2 inc srcptr+1 bne nohi2 hi3 inc srcptr+1 clc bcc nohi3 hi4 inc dstptr+1 bne nohi4 notempty cmp #tok_eod bne fail rts ;success! * handle "special" match values (value in A) specialmatch cmp #tok_empty bne notempty tya ;empty match, advance srcptr adc srcptr ; past and jump to main loop sta srcptr bcc mainloop inc srcptr+1 bne mainloop hi5 inc srcptr+1 clc bcc nohi5 goodmagic inc srcptr bne mainloop inc srcptr+1 mainloop * Get the mixed-length byte and handle the literal. ldy #$00 lda (srcptr),y ;get mixed-length byte sta savmix lsr A ;get the literal length lsr A lsr A lsr A beq noliteral cmp #$0f ;sets carry for >= 15 bne shortlit inc srcptr beq hi2 nohi2 lda (srcptr),y ;get length extension adc #14 ;(carry set) add 15 - will not exceed 255 * At this point, srcptr holds the address of the "mix" * word or the length extension, and dstptr holds the * address of the next output location. So we want to * read from (srcptr),y+1 and write to (dstptr),y. * We can do this by sticking the DEY between the LDA * and STA. * * We could save a couple of cycles by substituting * addr,y in place of (dp),y, but the added setup cost * would only benefit longer literal strings. shortlit tax tay :litloop lda (srcptr),y ;5 dey ;2 if len is 255, copy 0-254 sta (dstptr),y ;6 bne :litloop ;3 -> 16 cycles/byte * Advance srcptr by savlen+1, and dstptr by savlen txa sec ;this gets us the +1 adc srcptr sta srcptr bcs hi3 nohi3 ;carry cleared by hi3 txa adc dstptr sta dstptr bcs hi4 nohi4 dey ;Y=0; DEY so next INY goes to 0 * Handle match. Y holds an offset into srcptr such * that we need to increment it once to get the next * interesting byte. noliteral lda savmix and #$0f cmp #$0f blt :shortmatch ;BCC iny lda (srcptr),y ;get length extension cmp #237 ;"normal" values are 0-236 bge specialmatch ;BCS adc #15 ;will not exceed 255 * Put the destination address into copyptr. :shortmatch adc #4 ;min match; won't exceed 255 sta savlen ;save match len for later tax ;and keep it in X iny lda (srcptr),y ;match offset, lo sta copyptr iny lda (srcptr),y ;match offset, hi _desthi ora #$00 ;OR in hi-res page sta copyptr+1 * Advance srcptr past the encoded match while we still * remember how many bytes it took to encode. Y is * indexing the last value used, so we want to go * advance srcptr by Y+1. tya sec adc srcptr sta srcptr bcs hi5 nohi5 ;hi5 clears carry * Copy the match. The length is in X. Note this * must be a forward copy so overlapped data works. * * We know the match is at least 4 bytes long, so * we could save a few cycles by not doing the * ADC #4 earlier, and unrolling the first 4 * load/store operations here. ldy #$00 :copyloop lda (copyptr),y ;5 sta (dstptr),y ;6 iny ;2 dex ;2 bne :copyloop ;3 -> 18 cycles/byte * advance dstptr past copied data lda dstptr adc savlen ;carry is clear sta dstptr bcc mainloop inc dstptr+1 DO overrun_check LDA dstptr+1 CMP #$60 bcc mainloop BRK BRK ELSE bne mainloop ;always (not unpacking at $FFxx) FIN lst on sav LZ4FH6502 lst off