; **experimental** data compression/decompression routines, API subject to change!! ; This file contains the shared routines that work on all targets. compression { %option ignore_unused, merge sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) { ; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding. ; output_function = address of a routine that gets a byte arg in A, ; which is the next RLE byte to write to the compressed output buffer or file. ; is_last_block = usually true, but you can set it to false if you want to concatenate multiple ; compressed blocks (for instance if the source data is >64Kb) ; Worst case result storage size needed = (size + (size+126) / 127) + 1 ; This routine is not optimized for speed but for readability and ease of use. uword idx = 0 uword literals_start_idx = 0 ubyte literals_length = 0 asmsub call_output_function(ubyte arg @A) { %asm {{ jmp (p8v_output_function) }} } sub next_same_span() { ; returns length in cx16.r1L, and the byte value in cx16.r1H cx16.r1H = data[idx] cx16.r1L = 0 while data[idx]==cx16.r1H and cx16.r1L<128 and idx1 { ; a replicate run if literals_length>0 output_literals() call_output_function((cx16.r1L^255)+2) ; 257-cx16.r1L call_output_function(cx16.r1H) } else { ; add more to the literals run if literals_length==128 output_literals() if literals_length==0 literals_start_idx = idx-1 literals_length++ } } if literals_length>0 output_literals() if is_last_block call_output_function(128) } sub encode_rle(uword data, uword size, uword target, bool is_last_block) -> uword { ; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding. ; Returns the size of the compressed RLE data. Worst case result storage size needed = (size + (size+126) / 127) + 1. ; is_last_block = usually true, but you can set it to false if you want to concatenate multiple ; compressed blocks (for instance if the source data is >64Kb) ; This routine is not optimized for speed but for readability and ease of use. uword idx = 0 uword literals_start_idx = 0 ubyte literals_length = 0 uword orig_target = target sub next_same_span() { ; returns length in cx16.r1L, and the byte value in cx16.r1H cx16.r1H = data[idx] cx16.r1L = 0 while data[idx]==cx16.r1H and cx16.r1L<128 and idx1 { ; a replicate run if literals_length>0 output_literals() @(target) = (cx16.r1L^255)+2 ; 257-cx16.r1L target++ @(target) = cx16.r1H target++ } else { ; add more to the literals run if literals_length==128 output_literals() if literals_length==0 literals_start_idx = idx-1 literals_length++ } } if literals_length>0 output_literals() if is_last_block { @(target) = 128 target ++ } return target-orig_target } asmsub decode_rle_srcfunc(uword source_function @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY { ; -- Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding. ; Also stops decompressing when the maxsize has been reached. Returns the size of the decompressed data. ; Instead of a source buffer, you provide a callback function that must return the next byte to compress in A. %asm {{ sta _cb_mod1+1 sty _cb_mod1+2 sta _cb_mod2+1 sty _cb_mod2+2 sta _cb_mod3+1 sty _cb_mod3+2 lda cx16.r0L ldy cx16.r0H sta P8ZP_SCRATCH_W2 ; target ptr sta _orig_target sty P8ZP_SCRATCH_W2+1 sty _orig_target+1 lda cx16.r0L clc adc cx16.r1L sta cx16.r1L lda cx16.r0H adc cx16.r1H sta cx16.r1H ; decompression limit _loop ; while target (W2) < limit (r1) lda P8ZP_SCRATCH_W2 ldy P8ZP_SCRATCH_W2+1 cmp cx16.r1L tya sbc cx16.r1H bcs _end ; check control byte _cb_mod1 jsr $ffff ; modified bpl _literals cmp #128 beq _end ; replicate next byte -n+1 times eor #255 clc adc #2 sta P8ZP_SCRATCH_REG _cb_mod2 jsr $ffff ; modified ldx P8ZP_SCRATCH_REG ldy #0 - sta (P8ZP_SCRATCH_W2),y iny dex bne - ; add A to target lda P8ZP_SCRATCH_REG clc adc P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2 bcc _loop inc P8ZP_SCRATCH_W2+1 bcs _loop _literals ; copy the next n+1 bytes pha sta P8ZP_SCRATCH_B1 ldy #0 sty P8ZP_SCRATCH_REG _cb_mod3 jsr $ffff ; modified ldy P8ZP_SCRATCH_REG sta (P8ZP_SCRATCH_W2),y inc P8ZP_SCRATCH_REG dec P8ZP_SCRATCH_B1 bpl _cb_mod3 ; add N+1 to target pla sec adc P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2 bcc _loop inc P8ZP_SCRATCH_W2+1 bcs _loop _orig_target .word 0 _end ; return w2-orig_target, the size of the decompressed data lda P8ZP_SCRATCH_W2 ldy P8ZP_SCRATCH_W2+1 sec sbc _orig_target tax tya sbc _orig_target+1 tay txa rts }} } asmsub decode_rle(uword compressed @AY, uword target @R0, uword maxsize @R1) clobbers(X) -> uword @AY { ; -- Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding. ; Also stops decompressing if the maxsize has been reached. ; Returns the size of the decompressed data. %asm {{ sta P8ZP_SCRATCH_W1 ; compressed data ptr sty P8ZP_SCRATCH_W1+1 lda cx16.r0L ldy cx16.r0H sta P8ZP_SCRATCH_W2 ; target ptr sta _orig_target sty P8ZP_SCRATCH_W2+1 sty _orig_target+1 lda cx16.r0L clc adc cx16.r1L sta cx16.r1L lda cx16.r0H adc cx16.r1H sta cx16.r1H ; decompression limit _loop ; while target (W2) < limit (r1) lda P8ZP_SCRATCH_W2 ldy P8ZP_SCRATCH_W2+1 cmp cx16.r1L tya sbc cx16.r1H bcs _end ; check control byte ldy #0 lda (P8ZP_SCRATCH_W1),y bpl _literals cmp #128 beq _end ; replicate next byte -n+1 times eor #255 clc adc #2 pha tax iny lda (P8ZP_SCRATCH_W1),y ldy #0 - sta (P8ZP_SCRATCH_W2),y iny dex bne - ; add A to target pla clc adc P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2 bcc + inc P8ZP_SCRATCH_W2+1 clc + ; increase source by 2 lda P8ZP_SCRATCH_W1 adc #2 sta P8ZP_SCRATCH_W1 bcc _loop inc P8ZP_SCRATCH_W1+1 bcs _loop _literals ; copy the next n+1 bytes pha tax inc P8ZP_SCRATCH_W1 bne + inc P8ZP_SCRATCH_W1+1 + ldy #0 - lda (P8ZP_SCRATCH_W1),y sta (P8ZP_SCRATCH_W2),y iny dex bpl - ; add N+1 to source pla tax sec adc P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1 bcc + inc P8ZP_SCRATCH_W1+1 + ; add N+1 to target as well txa sec adc P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2 bcc _loop inc P8ZP_SCRATCH_W2+1 bcs _loop _orig_target .word 0 _end ; return w2-orig_target, the size of the decompressed data lda P8ZP_SCRATCH_W2 ldy P8ZP_SCRATCH_W2+1 sec sbc _orig_target tax tya sbc _orig_target+1 tay txa rts }} } asmsub decode_zx0(uword compressed @R0, uword target @R1) clobbers(A,X,Y) { ; Decompress a block of data compressed in the ZX0 format ; This can be produced using the "salvador" compressor with -classic ; It has faster decompression than LZSA and a better compression ratio as well. ; see https://github.com/emmanuel-marty/salvador for the compressor tool ; see https://github.com/einar-saukas/ZX0 for the compression format ; ; NOTE: for speed reasons this decompressor is NOT bank-aware and NOT I/O register aware; ; it only outputs to a memory buffer somewhere in the active 64 Kb address range ; %asm {{ ; *************************************************************************** ; *************************************************************************** ; ; zx0_6502.asm ; ; NMOS 6502 decompressor for data stored in Einar Saukas's ZX0 format. ; ; The code is 196 bytes long, and is self-modifying. ; ; Copyright John Brandwood 2021. ; ; Distributed under the Boost Software License, Version 1.0. ; (See accompanying file LICENSE_1_0.txt or copy at ; http://www.boost.org/LICENSE_1_0.txt) ; ; Adapted for Prog8 by Irmen de Jong ; ; *************************************************************************** ; *************************************************************************** ; *************************************************************************** ; *************************************************************************** ; ; Decompression Options & Macros ; ; *************************************************************************** ; *************************************************************************** ; ; Data usage is 8 bytes of zero-page. ; .if cx16.r0 < $100 ; r0-r15 registers are in zeropage just use those zx0_srcptr = cx16.r0 ;$F8 ; 1 word. zx0_dstptr = cx16.r1 ;$FA ; 1 word. zx0_length = cx16.r2 ;$FC ; 1 word. zx0_offset = cx16.r3 ;$FE ; 1 word. .else .error "in decode_zx0: r0-15 are not in zeropage and no alternatives have been set up yet" ; TODO .endif ; *************************************************************************** ; *************************************************************************** ; ; zx0_unpack - Decompress data stored in Einar Saukas's ZX0 format. ; ; Args: zx0_srcptr = ptr to compessed data ; Args: zx0_dstptr = ptr to output buffer ; Uses: lots! ; zx0_unpack: .if cx16.r0>=$100 ; set up the source and destination pointers lda cx16.r0L sta zx0_srcptr lda cx16.r0H sta zx0_srcptr+1 lda cx16.r1L sta zx0_dstptr lda cx16.r1H sta zx0_dstptr+1 .endif ldy #$FF ; Initialize default offset. sty =$100 ; set up the source and destination pointers lda cx16.r0L sta tsget lda cx16.r0H sta tsget+1 lda cx16.r1L sta tsput lda cx16.r1H sta tsput+1 .endif ldy #0 lda (tsget),y sta optRun + 1 inc tsget bne entry2 inc tsget + 1 entry2: ; ILLEGAL lax (tsget),y lda (tsget),y tax bmi rleorlz cmp #$20 bcs lz2 ; literal tay ts_delit_loop: lda (tsget),y dey sta (tsput),y bne ts_delit_loop txa inx updatezp_noclc: adc tsput sta tsput bcs updateput_hi putnoof: txa update_getonly: adc tsget sta tsget bcc entry2 inc tsget+1 bcs entry2 updateput_hi: inc tsput+1 clc bcc putnoof rleorlz: ; ILLEGAL: alr #$7f and #$7f lsr a bcc ts_delz ; RLE beq optRun plain: ldx #2 iny sta tstemp ; number of bytes to de-rle lda (tsget),y ; fetch rle byte ldy tstemp runStart: sta (tsput),y ts_derle_loop: dey sta (tsput),y bne ts_derle_loop ; update zero page with a = runlen, x = 2 , y = 0 lda tstemp bcs updatezp_noclc done: rts ; LZ2 lz2: beq done ora #$80 adc tsput sta lzput lda tsput + 1 sbc #$00 sta lzput + 1 ; y already zero lda (lzput),y sta (tsput),y iny lda (lzput),y sta (tsput),y tya dey adc tsput sta tsput bcs lz2_put_hi skp: inc tsget bne entry2 inc tsget + 1 bne entry2 lz2_put_hi: inc tsput + 1 bcs skp ; LZ ts_delz: lsr a sta lzto + 1 iny lda tsput bcc long sbc (tsget),y sta lzput lda tsput+1 sbc #$00 ldx #2 ; lz MUST decrunch forward lz_put: sta lzput+1 ldy #0 lda (lzput),y sta (tsput),y iny lda (lzput),y sta (tsput),y ts_delz_loop: iny lda (lzput),y sta (tsput),y lzto: cpy #0 bne ts_delz_loop tya ; update zero page with a = runlen, x = 2, y = 0 ldy #0 ; clc not needed as we have len - 1 in A (from the encoder) and C = 1 jmp updatezp_noclc optRun: ldy #255 sty tstemp ldx #1 ; A is zero bne runStart long: ; carry is clear and compensated for from the encoder adc (tsget),y sta lzput iny ; ILLEGAL: lax (tsget),y lda (tsget),y tax ora #$80 adc tsput + 1 cpx #$80 rol lzto + 1 ldx #3 bne lz_put ; !notreached! }} } /*** ; prog8 source code for the RLE routines above: sub decode_rle_prog8(uword @zp compressed, uword @zp target, uword maxsize) -> uword { cx16.r0 = target ; original target cx16.r1 = target+maxsize ; decompression limit while target uword { cx16.r0 = target ; original target cx16.r1 = target+maxsize ; decompression limit while target