base:rle_pack_unpack
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | base:rle_pack_unpack [2015-04-17 04:33] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== RLE Toolkit for CC65 v 1.0 ====== | ||
+ | By MagerValp. | ||
+ | |||
+ | The homepage and sources to this Toolkit is available [[http:// | ||
+ | |||
+ | 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 #< | ||
+ | sta src | ||
+ | lda #> | ||
+ | sta src + 1 | ||
+ | |||
+ | ; set destination pointer | ||
+ | lda #< | ||
+ | sta dest | ||
+ | lda #> | ||
+ | sta dest + 1 | ||
+ | |||
+ | ; set length of source data when packing | ||
+ | ; parameter is ignored when unpacking | ||
+ | lda #< | ||
+ | sta srclen | ||
+ | lda #> | ||
+ | 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 | ||
+ | < | ||
+ | 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 | ||
+ | ; < | ||
+ | ; 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: | ||
+ | destlen: | ||
+ | |||
+ | |||
+ | .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: | ||
+ | |||
+ | |||
+ | .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 ; | ||
+ | jsr @decsrclen ; | ||
+ | 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 ; | ||
+ | jsr @decsrclen ; | ||
+ | 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 ; | ||
+ | bne @rlenext | ||
+ | @rleend: | ||
+ | lda lastbyte ; store double byte | ||
+ | jsr rle_store | ||
+ | txa ; and counter | ||
+ | jsr rle_store | ||
+ | jmp @end | ||
+ | @stop: | ||
+ | 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: | ||
+ | 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 ; | ||
+ | jsr @decsrclen ; | ||
+ | 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, | ||
+ | |||
+ | |||
+ | .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 ; | ||
+ | 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 ; | ||
+ | @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 ; | ||
+ | jmp @unpack ; next | ||
+ | @rle: | ||
+ | jsr rle_read ; read byte count | ||
+ | tax | ||
+ | beq @end ; 0 = end of stream | ||
+ | lda lastbyte | ||
+ | @read: | ||
+ | jsr rle_store ; | ||
+ | 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 " | ||
+ | |||
+ | |||
+ | // 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, | ||
+ | memset(dest, | ||
+ | |||
+ | // pack src to buffer | ||
+ | packlen = rle_pack(buffer, | ||
+ | // unpack buffer to dest | ||
+ | unpacklen = rle_unpack(dest, | ||
+ | |||
+ | // check differences | ||
+ | diffcount = 0; | ||
+ | for (i = 0; i < DATASIZE; ++i) { | ||
+ | if (*src++ != *dest++) { | ||
+ | ++diffcount; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // print statistics | ||
+ | printf(" | ||
+ | printf(" | ||
+ | printf(" | ||
+ | } | ||
+ | </ | ||
+ | ===== 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' | ||
+ | < | ||
+ | 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 *~ | ||
+ | |||
+ | </ | ||
+ | |||
+ | |||
base/rle_pack_unpack.txt · Last modified: 2015-04-17 04:33 by 127.0.0.1