322 lines
9.2 KiB
Plaintext
322 lines
9.2 KiB
Plaintext
;Level-crusher depacker
|
|
;v1.0 (c)1998 Taboo Productions!
|
|
;All rights reserved
|
|
|
|
;v1.1 by CS, slightly modified to fit into stack, max outputsize ca. $0200-$fffc
|
|
|
|
|
|
speed = 6 ;speed with which the file was crushed (=compression rate),
|
|
;always use 6 for max compression unless you still use an 8088
|
|
sysline = 1 ;1=add Basic SYS line, 0=plain binary file
|
|
linenum = 2001 ;place Basic line number here (ignored if sysline=0)
|
|
exeaddr = 2070 ;of the payload, of course
|
|
debug = 0 ;1=indicate progress via bordercolors
|
|
;0=no progress indication (=faster depacking & shorter depacker)
|
|
|
|
|
|
;no longer used, all that stuff has to be handled by the payload now:
|
|
|
|
val01 = $37 ;value of $01 system register after depack
|
|
;C64 power-on default = $37, but other values might be needed,
|
|
;depending on what the payload expects.
|
|
cliflag = 0 ;1=reenable, 0=keep disabled interrupts after depack
|
|
;(IRQ is most of the time reenabled by the payload,
|
|
;but sometimes you have to do that manually)
|
|
blank = 0 ;1= leave screen blank, 0=enable after depack (dito)
|
|
|
|
|
|
*=$801
|
|
|
|
.if sysline
|
|
.word eolptr ;start address of next(=last) basic line
|
|
.word linenum ;line number of sysline (as set above)
|
|
.byte $9e ;basic token for SYS
|
|
sysnum .byte 0,0,0,0 ;placeholders for SYS address (see below)
|
|
.text "" ;put your crazy SYSline talk here if required
|
|
.byte 0 ;end of basic line
|
|
eolptr .byte 0,0 ;next basic line, 2x0 = end of basic text
|
|
.fi
|
|
|
|
start sei
|
|
|
|
.if sysline
|
|
temp=*
|
|
*=sysnum
|
|
.byte ^start ;replace placeholders with correct petscii-digits (see above)
|
|
*=temp
|
|
.fi
|
|
|
|
ldx #0 ;black border
|
|
stx $d020
|
|
stx $d011 ;screen off
|
|
lda #$1 ;set c128 to 2MHz mode
|
|
sta $d030
|
|
lda #$34 ;all ram memconfig.
|
|
sta $1
|
|
|
|
movecode lda depacker,x ;copy depacker+some crap to $100 - $1ff
|
|
txs ;init stack ptr while we're at it, clever, eh?
|
|
pha
|
|
inx
|
|
bne movecode
|
|
|
|
;move input stream to memtop, .Y=# of pages to copy:
|
|
|
|
ldy #>(endofprog-crushed+$ff)
|
|
movedata lda endofprog-$100,x
|
|
sta $ff00,x ;starting at the last page
|
|
inx
|
|
bne movedata
|
|
dec movedata+2 ;previous page src
|
|
dec movedata+5 ;previous page tgt
|
|
dey
|
|
bne movedata ;repeat until all pages copied
|
|
|
|
;set source = start of moved input stream:
|
|
|
|
lda #<$10000-endofprog+crushed
|
|
sta @b src
|
|
lda #>$10000-endofprog+crushed
|
|
sta @b srch
|
|
|
|
;set destination = load address of packed file:
|
|
|
|
lda depackaddr
|
|
sta @b dest
|
|
lda depackaddr+1
|
|
sta @b desth
|
|
jmp godepack ;start unpacking
|
|
|
|
;-------------------------------------------
|
|
;zeropage registers used by decompressor:
|
|
|
|
src = $ae ;current input-file address ptr.
|
|
srch = src+1
|
|
|
|
dest = $2d ;depack-to address pointer
|
|
desth = dest+1
|
|
|
|
byte = $fb ;control bit dispense byte
|
|
lng = $fc ;sequence length
|
|
lo = $fd ;for copy address
|
|
hi = lo+1 ;and various 16 bit stuff
|
|
;--------------------------------------------------------
|
|
;the depack algorythm, living in the stack up to ca. $1d7
|
|
|
|
depacker
|
|
.logical $100
|
|
|
|
godepack ldx #$00 ;empty the control bit dispenser
|
|
stx byte ;to force reading it from input-file right away
|
|
|
|
nextaction stx hi ;DEPACK MAINLOOP: hi always = 0 here
|
|
lda #$01 ;length of upcoming run is at least 1 byte
|
|
jsr getbit ;get sequence flag:
|
|
bcs repeat ;if set then repeat a previous byte sequence
|
|
|
|
;else copy a run of uncompressed bytes.
|
|
;the length of that run is stored as stopbit,bit,... sequence:
|
|
|
|
getcopylen jsr getbit ;get length of uncompressed run:
|
|
bcs gotcopylen ;if stopbit set then end of run
|
|
|
|
jsr getbit ;else shift length bit(s)
|
|
|
|
rol ;into accu, hi
|
|
rol hi
|
|
bpl getcopylen ;max length is $ffff
|
|
|
|
gotcopylen tax ;got length,
|
|
beq copypage ;if it is a multiple of 256 then...
|
|
|
|
copybytes ldy #$00 ;(else) copy .X src-bytes
|
|
lda (src),y
|
|
inc src
|
|
bne cb1
|
|
inc srch
|
|
cb1 sta (dest),y ;to output stream
|
|
inc dest
|
|
bne cb2
|
|
inc desth
|
|
cb2 dex
|
|
bne copybytes
|
|
|
|
copypage cpx hi ;...check pages left to copy
|
|
dec hi
|
|
bcc copybytes ;if pages left then copy 256 more src-bytes
|
|
stx hi ;else hi=0, done.
|
|
|
|
;having just copied an uncompressed sequence we can safely assume the next sequence
|
|
;matches something already in the output data and thus don't need any
|
|
;control bits to tell the next step:
|
|
|
|
;repeat an earlier byte sequence at the current output position.
|
|
;first, get sequence length which is encoded as a stopbit, bit, ... sequence again,
|
|
;but is preceeded by a special length-flag:
|
|
|
|
repeat lda #$01 ;repeat a sequence: dft length=1
|
|
jsr getbit ;get length-flag:
|
|
bcc rep2bytes ;if = 0 then the sequence length 2-bytes
|
|
|
|
getreplen jsr getbit ;else 3+ bytes, get next stop bit
|
|
bcs gotreplen ;if set then end of length bits
|
|
|
|
jsr getbit ;else get next length bit
|
|
rol
|
|
bcc getreplen ;repeat if length still < 256
|
|
|
|
;else a length of 256+ bytes signals that we have reached end of input file:
|
|
|
|
jmp exeaddr ;we're done with depacking
|
|
|
|
rep2bytes inx ;X=1 to use special 2-byte address-chunks
|
|
|
|
;C=0 only for 2 byte sequences here, for all other sequences, C=1:
|
|
|
|
gotreplen adc #$01 ;true length = length+C+1
|
|
sta lng
|
|
|
|
;now the offset to the sequence to copy is computed. this is quite complicated :-)
|
|
;the number of "bit-chunks" forming the offset is retrieved from 2 more control bits.
|
|
;up to 4 chunks can be chained together, containing 2-4 bits each, depending on pack speed
|
|
;and sequence length, where 2-byte sequences use a seperate chunk table with an overall
|
|
;shorter search range to ensure that less than 16 bits are needed to encode the entire
|
|
;sequence specs:
|
|
|
|
txa ;A=1 for 2byters here, else 0
|
|
jsr getbit ;get number of bit-chunks needed for offset
|
|
rol
|
|
jsr getbit
|
|
rol
|
|
tay ;Y now = 0-3 for 3+byters or 4-7 for 2byters
|
|
lda #$00
|
|
|
|
;after that, look up how many bits are in the current chunk and read in as many offset bits.
|
|
;if one more chunks follow, add 1 to the current offset and repeat with next chunk:
|
|
;the maximum offset range that can be achieved by this method is
|
|
;2^[bits of all used chunks]+2^[bits of all but last chunk]+...+2^[bits of last chunk]-1
|
|
|
|
getnumbits ldx tab,y ;get number of bits used in current chunk
|
|
|
|
getoffset jsr getbit ;MSB comes first
|
|
rol ;shift offset bits to A, hi
|
|
rol hi
|
|
dex ;next bit of current chunk
|
|
bne getoffset
|
|
|
|
dey ;next chunk or copy done?:
|
|
bmi gotoffset+1 ;if last 3+ byte chunk already processed then...
|
|
cpy #$03
|
|
beq gotoffset ;or if last 2 byte chunk already processed then...
|
|
|
|
clc ;else another chunk will follow,
|
|
adc #$01 ;extend offset range by 2^(bits still to follow)
|
|
bcc getnumbits ;to save some more bits
|
|
inc hi
|
|
bcs getnumbits
|
|
|
|
;after all required offset bits have been retrieved, calculate the start address of the
|
|
;sequence to repeat and append the specified amount of bytes from there at the current
|
|
;output position. The offset stored in the control bits points at the end of that
|
|
;sequence, so sequence length is added to find out the start address. This saves some
|
|
;bits but also makes it impossible to have repetitions overlap the current output
|
|
;position what could improve the pack rate in some situations.
|
|
|
|
gotoffset clc ;...(required for 2 byte sequences only)
|
|
adc lng ;...offset = offset + sequence length
|
|
bcc calcaddr
|
|
inc hi
|
|
|
|
calcaddr clc ;offset *-1 + current output address
|
|
sbc dest
|
|
eor #$ff
|
|
sta lo ;= source address of sequence to copy
|
|
lda hi
|
|
sbc desth
|
|
eor #$ff
|
|
sta hi
|
|
ldy #$00
|
|
|
|
repeatbytes lda (lo),y ;append <lng> bytes from <src> at output pos
|
|
sta (dest),y
|
|
iny
|
|
cpy lng
|
|
bne repeatbytes
|
|
|
|
tya ;new output pos = old pos. + <lng>
|
|
clc
|
|
adc dest
|
|
sta dest
|
|
bcc rb2
|
|
inc desth
|
|
|
|
rb2 ldx #$00 ;back to depack loop start
|
|
jmp nextaction
|
|
|
|
;control bit dispenser readout. In the original depacker the bit readout was
|
|
;inlined whereever bits were needed. Now included in this subroutine to make the
|
|
;depacker a few bytes shorter (but also several cycles slower, unfortunately):
|
|
|
|
getbit asl byte ;get a control bit: next bit -> C
|
|
bne gotbit ;if still bits left then done
|
|
|
|
pha ;else get next control byte: save .A, .Y
|
|
sty byte
|
|
ldy #$00
|
|
lda (src),y ;read from current input stream position
|
|
inc src
|
|
bne gb2
|
|
inc srch
|
|
|
|
gb2 ldy byte ;restore .Y
|
|
|
|
.if debug
|
|
inc $01 ;some color effects to show progress
|
|
sta $d020 ;useful to see if/where depacking stalls
|
|
dec $01
|
|
.fi
|
|
sec ;"out of bits" marker
|
|
rol ;1st control bit to C, marker to dispenser-byte
|
|
sta byte
|
|
pla
|
|
|
|
gotbit rts
|
|
|
|
|
|
;crush-speed dependant offset chunk width tables:
|
|
;lower speed settings shorten the search range for duplicate sequences, thereby
|
|
;worsening the compression rate but also speeding up depacking a little and packing
|
|
;even more so (not that you'd notice that anymore on a half decent PC, though).
|
|
;it might also speed up depacking because there might be more uncompressed bytes
|
|
;which are very straightforward to depack.
|
|
;the last 4 values are used for 2 byte sequences, the first 4 for any other size.
|
|
|
|
.if speed=6
|
|
tab .byte 4,3,3,3, 4,2,2,2
|
|
.fi
|
|
.if speed=5
|
|
tab .byte 4,2,3,3, 4,2,2,2
|
|
.fi
|
|
.if speed=4
|
|
tab .byte 4,2,2,3, 4,2,2,2
|
|
.fi
|
|
.if speed=3
|
|
tab .byte 4,2,2,2, 4,2,2,2
|
|
.fi
|
|
.if speed=2
|
|
tab .byte 3,2,2,2, 3,2,2,2
|
|
.fi
|
|
.if speed=1
|
|
tab .byte 3,1,2,2, 3,1,2,2
|
|
.fi
|
|
.if speed=0
|
|
tab .byte 2,2,1,1, 2,2,1,1
|
|
.fi
|
|
|
|
.here
|
|
|
|
depackaddr ;the first two bytes of the crushed file are the load address
|
|
crushed =*+2 ;crunched data starts right behind
|
|
.binary crushed.bin
|
|
endofprog =*
|