====== RLE Toolkit for CC65 v 1.0 ====== By MagerValp. The homepage and sources to this Toolkit is available [[http://www.paradroid.net/rle/|here]]. Check that page for potential updates to this code. A copy of the source package (of v1.0) is also available for download right here from {{:base:rle-toolkit-1.0.zip|codebase64}}. RLE Toolkit consists of: * a small RLE compression library for CC65 * commandline utilities to pack and unpack files * sample code that shows you how to use it ====== Compression API ====== There are two functions, rle_pack and rle_unpack, that can be called from both C and assembler. C interface: unsigned int __fastcall__ rle_pack(unsigned char *dest, unsigned char *src, unsigned int length); unsigned int __fastcall__ rle_unpack(unsigned char *dest, unsigned char *src); Assembler interface: .import rle_pack .importzp src, dest .import srclen, destlen ; set source pointer lda #sourcedata sta src + 1 ; set destination pointer lda #destbuffer sta dest + 1 ; set length of source data when packing ; parameter is ignored when unpacking lda #datalen sta srclen + 1 jsr rle_pack or jsr rle_unpack ; length of output is returned in destlen ====== Packed stream format ====== When two or more consecutive bytes are identical, they are replaced by . A COUNT of 0 indicates End Of Stream. A COUNT of 1 indicates that two bytes should be written, a COUNT of 2 indicates three bytes, and so on. ====== Commandline tools ====== rlepack infile outfile rleunpack infile outfile Self explanatory. Right? ====== The Routines ====== ===== rle.h ===== /* Pack data. Returns the number of bytes written to destination. */ unsigned int __fastcall__ rle_pack(unsigned char *dest, const unsigned char *src, unsigned int length); /* Unpack data. Returns the number of unpacked bytes. */ unsigned int __fastcall__ rle_unpack(unsigned char *dest, const unsigned char *src); ===== rle.s ===== ; Routines for packing and unpacking run length encoded byte streams ; ; When two or more consecutive bytes are identical, they are replaced by ; . A COUNT of 0 indicates End Of Stream. A COUNT ; of 1 indicates that two bytes should be written, a COUNT of 2 indicates ; three bytes, and so on. .export rle_read, rle_store .exportzp src, dest .export lastbyte .export destlen .importzp ptr1, ptr2 .zeropage src = ptr1 ; borrow cc65's temp pointers dest = ptr2 .bss lastbyte: .res 1 ; last byte read destlen: .res 2 ; number of bytes written .code ; read a byte and increment source pointer rle_read: lda (src),y inc src bne :+ inc src + 1 : rts ; write a byte and increment destination pointer rle_store: sta (dest),y inc dest bne :+ inc dest + 1 : inc destlen bne :+ inc destlen + 1 : rts ===== rlepack.s ===== .export _rle_pack, rle_pack .import rle_store, rle_read .importzp src, dest .import lastbyte .import destlen .import popax .bss srclen: .res 2 ; length of source data .code ; cc65 interface to rle_pack ; unsigned int __fastcall__ rle_pack(unsigned char *dest, unsigned char *src, unsigned int length); _rle_pack: sta srclen ; save length arg stx srclen + 1 jsr popax ; get src arg sta src stx src + 1 jsr popax ; get dest arg sta dest stx dest + 1 jsr rle_pack ; execute lda destlen ; return length ldx destlen + 1 rts ; run length encode a stream rle_pack: ldy #0 sty destlen ; reset the byte counter sty destlen + 1 jsr rle_read ; read the first byte sta lastbyte ; save for reference jsr rle_store ; store it jsr @decsrclen ; decrease source count beq @end ; if you're trying to pack a single byte, this the end @pack: jsr rle_read ; grab a byte cmp lastbyte ; same as last byte? beq @rle ; then count bytes and store run length sta lastbyte ; save for reference jsr rle_store ; store byte jsr @decsrclen ; decrease source count bne @pack ; next @end: lda lastbyte ; store last byte... jsr rle_store lda #0 ; ...with a 0 count as the terminator jsr rle_store rts ; done @rle: ldx #1 ; start with a count of 1 jsr @decsrclen beq @rleend @rlenext: jsr rle_read ; grab a byte cmp lastbyte ; make sure it's the same bne @newbyte ; no, then terminate inx ; inc counter beq @stop ; overflow? jsr @decsrclen ; check for end of data bne @rlenext @rleend: ; end of data lda lastbyte ; store double byte jsr rle_store txa ; and counter jsr rle_store jmp @end @stop: ; overflow lda lastbyte ; store the double byte jsr rle_store lda #$ff ; $ff as the byte count jsr rle_store inx ; start over with a counter of 1 jsr @decsrclen beq @rleend bne @rlenext @newbyte: ; new byte detected pha ; save lda lastbyte ; store double byte jsr rle_store txa ; and counter jsr rle_store pla ; restore new byte sta lastbyte ; save for reference jsr rle_store ; store it jsr @decsrclen ; data left? bne @pack ; yep, pack beq @end ; nope, end ; decrease number of bytes left, return 0 when done @decsrclen: lda srclen bne :+ dec srclen + 1 : sec sbc #1 sta srclen ora srclen + 1 rts ===== rleunpack.s ===== .export _rle_unpack, rle_unpack .import rle_store, rle_read .importzp src, dest .import lastbyte .import destlen .import popax .code ; cc65 interface to rle_unpack ; unsigned int __fastcall__ rle_unpack(unsigned char *dest, unsigned char *src); _rle_unpack: sta src ; save src arg stx src + 1 jsr popax ; get dest arg sta dest stx dest + 1 jsr rle_unpack ; execute lda destlen ; return length ldx destlen + 1 rts ; unpack a run length encoded stream rle_unpack: ldy #0 sty destlen ; reset byte counter sty destlen + 1 jsr rle_read ; read the first byte sta lastbyte ; save as last byte jsr rle_store ; store @unpack: jsr rle_read ; read next byte cmp lastbyte ; same as last one? beq @rle ; yes, unpack sta lastbyte ; save as last byte jsr rle_store ; store jmp @unpack ; next @rle: jsr rle_read ; read byte count tax beq @end ; 0 = end of stream lda lastbyte @read: jsr rle_store ; store X bytes dex bne @read beq @unpack ; next @end: rts ===== test.c ===== And finally, a test program (for the cc65 compiler). /* Pack data to a buffer, unpack and compare with original. If source and destination are identical, everything works. Simple. */ #include #include #include "rle.h" // number of bytes to pack #define DATASIZE 1000 // source #define SRC 0x0400 // destination #define DEST 0x4400 // buffer, worst case should be DATASIZE * 1.5 + 2 #define BUFFER 0xc000 #define BUFSIZE 0x1000 void main(void) { unsigned int packlen, unpacklen, diffcount, i; unsigned char *src, *dest, *buffer; src = (unsigned char *) SRC; dest = (unsigned char *) DEST; buffer = (unsigned char *) BUFFER; // fill with 0x55 to see what gets written memset(buffer, 0x55, BUFSIZE); memset(dest, 0x55, DATASIZE); // pack src to buffer packlen = rle_pack(buffer, src, DATASIZE); // unpack buffer to dest unpacklen = rle_unpack(dest, buffer); // check differences diffcount = 0; for (i = 0; i < DATASIZE; ++i) { if (*src++ != *dest++) { ++diffcount; } } // print statistics printf("Packed %d bytes to %d bytes\n", DATASIZE, packlen); printf("Unpacked to %d bytes\n", unpacklen); printf("%d bytes differ\n", diffcount); } ===== Makefile ===== Example makefile just to show how this stuff can be built. In any case, don't forget that you can download the whole package at MagerValp's site - [[http://www.paradroid.net/rle/]]. ARGET=c64 CC=cl65 AS=ca65 LD=cl65 AR=ar65 C1541=c1541 CFLAGS=-Oirs -t $(TARGET) AFLAGS= %.o: %.c $(CC) -c $(CFLAGS) $< %.o: %.s $(AS) $(AFLAGS) $< all: rle.lib test.prg RLEOBJS = \ rle.o \ rlepack.o \ rleunpack.o TESTOBJS= \ test.o \ rle.lib rle.lib: $(RLEOBJS) $(AR) a $@ $^ test.prg: $(TESTOBJS) $(LD) -m test.map -Ln test.lab -t $(TARGET) -o test.prg $(AFLAGS) $^ .PHONY: clean distclean clean: rm -f *.o rle.lib rm -f test.prg test.map test.lab distclean: clean rm -f *~