PUCRUNCH, l'ultime compacteur ?
------------------------------
Cet archive contient plusieurs sources de routines a utiliser pour
decompacter un fichier compacte avec PUCRUNCH. Il contient aussi
la version Windows de PUCRUNCH.
Comme d'habitude, pas d'accent dans cet article.
Version 1.0 * Tom et Jerry/GPA le 02/07/2007
C'est quoi PUCRUNCH ?
---------------------
PUCRUNCH est un compacteur cree par un programmeur sur Commodore C64,
Pasi Ojala, qui presente la particularite de fonctionner sur PC.
Le programme est capable de generer des executables pour tous les
Commodore 8bits, mais permet egalement de creer des fichiers de
donnees simples.
Beneficiant de la puissance de calcul apportee par une plateforme
materielle recente, PUCRUNCH compacte de facon quasiment instantanee
tout fichier de taille modeste et utilise des techniques de recherches
complexes pour produire un taux de compression excellent, meilleur que
tout ce qui existe a ce jour sur CPC.
Des programmeurs Z80 se sont penches sur ce produit et ont cree des
routines permettant de decompresser un fichier PUCRUNCHed. Le premier
source diffuse semble avoir ete fait pour la Nintendo Gameboy en ...
1999 (qui a dit que les consoles ne servent a rien :-) ).
D'autres routine sont depuis sorties, mais aucune a ce jour n'avait
ete adaptee pour le l'Amstrad CPC.
Cette injustice est desormais reparee !
Les sources :
-------------
Site officiel de PUCRUNCH :
http://www.cs.tut.fi/~albert/Dev/pucrunch/
Routine GB par Jeff Frohwein :
http://www.cs.tut.fi/~albert/Dev/pucrunch/uncrunch-z80.asm
Routine Z80 par Jussi Pitkänen :
http://bree.homeunix.net/~ccfg/pucrunch/
Routine Sega Master System par Maxim :
http://www.smspower.org/maxim/smssoftware/pucrunch.html
L'archive ci-joint contient des versions CPC de ces routines plus
deux versions inedites. Tous les sources sont compilables avec
Maxam, l'ultime assembleur sur CPC :-).
GB.ASM Port Gameboy
Z80.ASM Port "Z80"
GB_CPC.ASM Version optimisee du port Gameboy
BASIC.ASM Source Maxam du port Gameboy optimise avec passage des
parametres via un CALL sous Basic
GB.ASM
Taille en octets : &1C0
Le premier source est present a titre "historique". C'est celui qui
est le plus commente (en anglais) et le plus lisible car il n'utilise
pas d'astuce de programmation.
Z80.ASM
Taile en octets : &129
Le second source est une version tres particuliere, utilisant les registres
secondaires du Z80 pour eviter de couteuses operations de lecture/ecriture
de variables en memoire. C'est la routine la plus rapide a la decompression
et la plus courte.
J'ai du modifier un peu le source pour qu'il fonctionne sous BASIC
(sauvegarde des registres secondaires BC, DE et HL).
Autre particularite, le source ne stocke pas dans une table les 31
octets necessaires a la decompression. Il va les lire dans l'en-tete
du code compacte. Si cette zone est ecrasee lors du decompactage,
plantage assure !
Il est bien evidemment facile de rajouter une copie de cette table
dans la routine, mais du coup, sa taille augmente un peu.
GB-CPC.ASM
Taille en octets : &18D
Une version optimisee du source Gameboy. La routine est un peu plus courte,
et environ 10% plus rapide a la decompression. Elle est moins performante
que Z80.ASM, mais travaille sans probleme sous interruption.
BASIC.ASM
Taille en octets : &19C
Une version de GB-CPC.ASM permettant de passer sous Basic les parametres
necessaires au decompactage par un simple CALL.
Quelques considerations techniques sur le decompactage
------------------------------------------------------
L'implantation en memoire du code compacte
******************************************
En fonction de la taille du programme original et du code compacte,
il existe deux options pour determiner ou stocker en memoire le
fichier compacte.
Sur de petits fichiers, on peut implanter le code compacte dans une
zone qui n'est pas utilisee par le programme d'origine.
Un exemple :
On compacte un programme utilisant la memoire entre &1000 et &5000.
Le code compacte fait &3000 de longueur.
On pourra sans risque implanter le code compacte en &5000, car cette
zone ne sera jamais ecrite par la routine de decompactage et le code
compacte n'est pas assez long pour "endommager" les zones memoire
reservees au systeme (grossierement, au dessus de &A67B pour un CPC6128).
On pourrait aussi par exemple implanter le code compacte en memoire
video (&C000).
Sur un programme plus gros, il n'y aura pas assez de memoire "libre"
pour utiliser cette technique. On utilise alors le principe de la "zone
tampon". Le programme compacte est implante dans la zone memoire du
programme normal, mais avec un decalage suffisant pour que la fin du
programme compacte ne soit pas ecrasee lors du decompactage.
Comment determiner la taille de cette zone tampon ? Eh bien, cela depend
de la memoire qui reste disponible et du programme compacte.
On peut considerer qu'une zone tampon de &0100 est dans la plupart des
cas suffisante.
Si on reprend notre exemple :
Programme standard utilisant la memoire entre &1000 et &5000
Code compacte de longueur &3000
Zone "tampon" : entre &5000 et &5100
Implantation du code compacte : &5100-&3000 = &2100
Si le decompactage plante, il faut augmenter la taille de la zone
tampon.
Pourquoi Utiliser PUCRUNCH plutot qu'un autre compacteur ?
**********************************************************
La performance : PUCRUNCH est selon mes tests de 10 a 20% plus
performant que CPCT 3.0 (desole Yves !), la reference sur
CPC.
La vitesse : le compactage d'un programme est tres rapide. Plus
besoin d'attendre une demi-heure pour compacter un jeu !
La souplesse : PUCRUNCH etant un programme PC, la taille du
programme a compacter n'a pas d'importance. On peut ainsi
compacter de tres gros programmes, que seuls quelques rares
compacteurs comme le Crown Cruncher peuvent traiter.
PUCRUNCH a t'il des defauts ?
*****************************
Il faut disposer d'un PC. Je suppose que la plupart des utilisateurs
de CPC en sont equipes...
Par rapport a certains compacteurs qui s'occupent de tout, il est
necessaires d'avoir un minimum de bases sur l'organisation de la
memoire du CPC pour bien s'en servir.
Le decompactage est plus lent que la majorite des autres crunchers
sur CPC. Cela reste neanmoins tout a fait correct au regard de la
taille des fichiers compactes.
Il n'y a pas de front-end, de programme qui vous prend en charge,
et vous evite de penibles calculs :-). D'un autre cote, l'usage
d'un source garantit une totale adaptabilite a vos besoin.
Exemple de compactage d'un ecran de presentation 17k
----------------------------------------------------
Dans l'archive DSK, vous trouverez le fichier BUGGY.SCR (transfert
de la page ecran C64 d'un jeu connu, Buggy Boy), qui nous servira
pour ce petit didacticiel.
1ere etape : transferer le fichier sur votre PC.
************
On peut par exemple utiliser ManageDsk de Demoniak. Seule regle
IMPERATIVE, le fichier doit etre exporte SANS EN-TETE.
Si vous avez correctement fait le transfert, le fichier sur PC doit
avoir une taille de 16384 octets (soit &4000).
2eme etape : le compactage.
************
Copier le fichier BUGGY.SCR dans le meme repertoire que PCRUNCH.EXE.
Ouvrir une fenetre de commande en mode texte.
Taper la commande : PCRUNCH.EXE -C0 -D BUGGY.SCR BUGGY.CRU
Si tout se passe bien, vous devez voir dans le repertoire de travail
un nouveau fichier BUGGY.CRU, de 4500 octets (soit 1194 !).
3eme etape : le transfert vers le CPC.
************
Vous reprenez votre outil prefere, et vous importez sur une disquette
CPC le fichier BUGGY.CRU en mode BINAIRE. Le fichier n'a aucune
adresse d'implantation ni d'execution par defaut.
4eme etape : Determiner les adresses d'implantation en memoire du
fichier compacte et du decompacteur.
************
Une page ecran etant implante en memoire video (&C000-&FFFF), nous avons
a notre disposition toute la ram "systeme" (&0040-&A67B pour un CPC 6128).
Le fichier compacte peut donc etre charge ou bon vous semble dans cette
zone.
Dans notre exemple, nous allons charger l'ecran en &4000.
La routine de decompactage utilisee sera celle utilisable directement en
Basic. Par defaut, elle est implantee en &A000.
5eme etape : Compiler le decompacteur.
************
Lancer Maxam. Charger avec la commande L (Load) le source BASIC.ASM.
Passer en mode edition avec la commande T (Text).
Compiler le source avec la commande A (Assemble).
>> Le programme BASIC.BIN est cree sur la disquette.
6eme etape : Creer un chargeur Basic pour l'image.
************
Le programme devra charger en memoire l'image compactee, la routine de
decompactage, puis lancer le decompactage.
En pratique, cela nous donne :
10 OPENOUT"D":MEMORY &3FFF
20 LOAD "BUGGY.CRU",&4000
30 LOAD "BASIC.BIN",&A000
40 CALL &A000,&4000,&C000
La ligne 40 contient l'appel a la routine. On lui transmet deux
informations :
- L'adresse de depart de l'ecran compacte
- L'adresse de depart ou decompacter l'image (soit le debut de la
memoire video)
Simple, non ?
Exemple de compactage de programme, Boulder Dash
------------------------------------------------
Basiquement, la methode est identique au compactage d'une page ecran.
Il faudra simplement personnaliser la routine de decompactage et bien
veiller a ce que le code compacte ne soit pas corrompu lors de la
decompression.
Dans cet exemple, nous travaillerons avec le jeu Boulder Dash. La
version la plus courante de ce programme se presente sous la forme
d'un fichier BOULDER.BIN de 29ko dont les caracteristiques sont :
Adresse d'implantation : &0200
Longueur : &7025
Adresse d'execution : &1F52
1ere etape : transferer le fichier sur votre PC.
************
On peut par exemple utiliser ManageDsk de Demoniak. Seule regle
IMPERATIVE, le fichier doit etre exporte SANS EN-TETE.
Si vous avez correctement fait le transfert, le fichier sur PC doit avoir
une taille de 28709 octets (soit &7025)
2eme etape : le compactage.
************
Copier le fichier BOULDER.BIN dans le meme repertoire que PCRUNCH.EXE
Ouvrir une fenetre de commande en mode texte.
Taper la commande PCRUNCH.EXE -C0 -D BOULDER.BIN BOULDER.CRU
Si tout se passe bien, vous devez voir dans le repertoire de travail un
nouveau fichier BOULDER.CRU, de taille 11532 (soit &2D0C !).
3eme etape : le transfert vers le CPC.
************
Vous reprenez votre outil prefere, et vous importez sur une disquette
CPC le fichier BOULDER.CRU en mode BINAIRE. Le fichier n'a aucune
adresse d'implantation ni d'execution par defaut.
4eme etape : Determiner les adresses d'implantation en memoire du
fichier compacte et du decompacteur.
************
Soyez attentif, cela devient un peu plus technique (mais pas complexe
pour autant).
Comme nous l'avons vu dans le paragraphe precedent, pour etre sur que
le decompactage ne se traduise pas par un plantage, il est necessaire
de faire en sorte que les dernieres donnees compactees chargees en
memoire ne soient pas ecrasees par le code decompacte.
Pour BOULDER DASH, la fin du programme "normal" se trouve en
(&200 + &7025) -1, soit &7224. Il nous reste donc beaucoup de memoire pour
loger la routine de decompactage et la zone "tampon".
Dans notre exemple, nous allons implanter la routine de decompactage en
&7800. Le fichier compacte sera charge juste en dessous, soit :
&7800 - longueur du fichier compacte
&7800 - &2D0C = &4AF4
5eme etape : compiler une version specifique du decompacteur.
************
BOULDER DASH etant implante assez bas en memoire, il n'est pas tres
pratique d'utiliser un chargeur Basic. L'appel a la routine de
decompactage devra donc etre immediatement enchaine par l'execution
du jeu, sans revenir au Basic.
Pour ce faire, il faut juste rajouter au debut de la routine que vous
avez choisie (GB.ASM, Z80.ASM ou GB_CPC.ASM)
LD HL,&4AF4
LD DE,&0200
CALL routine
JP &1F52
Compiler ensuite le source. Vous devez obtenir un fichier GB.BIN, GB-CPC.BIN
ou Z80.BIN. Si les fichiers n'ont pas ete modifies, ils doivent avoir la
taille suivante :
GB.BIN
Z80.BIN
GB-CPC.BIN
A ces longueur, il faudra rajouter celle du code que nous avons rajoute, soit
12 octets.
6eme etape : On fusionne tout ce bazar !
************
Nous devons d'abord determiner la longueur finale du programme avec le
decompacteur integre.
Le calcul est donc le suivant :
longueur du fichier compacte
+ longueur de la routine de decompactage
Pour Boulder Dash, on obtient (exemple avec source GB-CPC) :
&2D0C + (&18D + 12) = &2EA5
Maintenant, un simple petit programme Basic nous permettra de creer enfin
un fichier unique.
10 OPENOUT"D":MEMORY &4AF9
20 LOAD "BOULDER.CRU",&4AFA
30 LOAD "GB_CPC.BIN",&8000
40 SAVE "BOULDER.BIN",B,&4AF9,&2EA5,&8000
Au final, on se retrouve avec un executable de 12Ko au lieu de 29 !
NOTICE TEXTE n° 2 (8.1 Ko)
; Version CPC par T&J/GPA * 07/2007 !
; Petites optimisations diverses, sans toucher le corps de la routine.
; Voir la routine non modifiee pour essayer de comprendre son fonctionnement !
; Liste des modifications :
; - Remplacement de boucles de copies par des LDIR / LDI
; - Integration dans le code des variables MaxGamma et consors, gain en
; place et en temps machine !
; Utilisation du registre IX pour pointer sur TablePu (faible gain en place
; et en temps machine
; Remplacement de quelques JP par des JR
;* PUCRUNCH unpacker for GB
;* Modeled after Pasi Ojala's C64 code.
;*
;* Written in RGBDS
;*
;* V1.0 - Ported to GB by Jeff Frohwein, started 22-Jul-99
;* V1.1 - Various optimizations, 23-Jul-99
;* V1.2 - Even more optimizations, 23-Jul-99
;* V1.3 - Fixed a bug in the code. 256 byte copy didn't work. 24-Feb-00
;*
ORG &A000
NOLIST
WRITE "BASIC.BIN"
; Read CALL datas transmitted from Basic
booter CP &2
RET NZ
LD E,(IX+&0)
LD D,(IX+&1)
LD L,(IX+&2)
LD H,(IX+&3)
; ****** Unpack pucrunch data ******
; Entry HL = Source packed data
; DE = Destination for unpacked data
Unpack LD (OutPtr+1),DE
; Read the file header & setup variables
LD BC,6
ADD HL,BC
LD A,(HL)
INC HL
LD (escPu +1),A
INC HL
INC HL
LD A,(HL)
INC HL
LD (main +1),A ; EscBits
LD B,A
LD A,8
SUB B
LD (noesc +1),A ; Esc8Bits
LD A,(HL)
INC HL
LD (MaxGamma +1),A
DEC A
LD B,A
LD A,8
SUB B
LD (Max8Gamma +1),A
LD A,(HL)
INC HL
LD (Max1Gamma +1),A
ADD A,A
DEC A
LD (Max2Gamma +1),A
LD A,(HL)
INC HL
LD (ExtraBits +1),A
INC HL
INC HL
LD A,(HL)
INC HL
LD C,A
LD DE,tablePu-1
PUSH DE
POP IX
INC DE ; DE = TablePu, IX=TablePu-1 (useful for chrcode optimization)
; Copy the RLE table (maximum of 31 bytes) to RAM
LD B,&0
LDIR
LD D,&80
JR main
tablepu ds 31,&0
newesc
LD B,A
LD A,(escPu +1)
LD (regy +1),A
LD A,(main +1)
LD E,A
LD A,B
INC E
CALL getchk
LD (escPu +1),A
LD A,(regy +1)
; Fall through and get the rest of the bits.
noesc LD E,&00
INC E
CALL getchk
; Write out the escaped/normal byte
OutPtr LD BC,&0000
LD (BC),A
INC BC
LD (OutPtr+1),BC
; Fall through and check the escape bits again
main LD A,&00 ; changed LD A,(EscBits)
LD E,A
XOR A ; A = 0
LD (regy +1),A
INC E
CALL getchk ; X=2 -> X=0
LD B,A
escPu LD A,&00
CP B
LD A,B
JR NZ,noesc ; Not the escape code -> get the rest of the byte
; Fall through to packed code
CALL getval ; X=0 -> X=0
LD (lzpos +1),A ; xstore - save the length for a later time
SRL A ; cmp #1 ; LEN == 2 ? (A is never 0)
JR NZ,lz77 ; LEN != 2 -> LZ77
CALL get1bit ; X=0 -> X=0
SRL A ; bit -> C, A = 0
JR NC,lz77_2 ; A=0 -> LZPOS+1 LZ77, len=2
; e..e01
CALL get1bit ; X=0 -> X=0
SRL A ; bit -> C, A = 0
JR NC,newesc ; e..e010 New Escape
; e..e011 Short/Long RLE
regy LD A,&00 ; Y is 1 bigger than MSB loops
INC A
LD (regy +1),a
CALL getval ; Y is 1, get len, X=0 -> X=0
LD (lzpos +1),A ; xstore - Save length LSB
Max1Gamma LD B,&00
CP B ; ** PARAMETER 63-64 -> C set, 64-64 -> C clear..
JR C,chrcode ; short RLE, get bytecode
; Otherwise it's long RLE
longrle LD B,A
Max8Gamma LD A,&00
LD E,A ; ** PARAMETER 111111xxxxxx
LD A,B
CALL getbits ; get 3/2/1 more bits to get a full byte, X=2 -> X=0
LD (lzpos +1),A ; xstore - Save length LSB
CALL getval ; length MSB, X=0 -> X=0
LD (regy +1),A ; Y is 1 bigger than MSB loops
chrcode CALL getval ; Byte Code, X=0 -> X=0
LD E,A
LD (cpc1+2),A
CP 32 ; 31-32 -> C set, 32-32 -> C clear..
cpc1 LD A,(IX + &00)
JR C,less32 ; 1..31
; Not ranks 1..31, -> 111110xxxxx (32..64), get byte..
LD A,E ; get back the value (5 valid bits)
LD E,3
CALL getbits ; get 3 more bits to get a full byte, X=3 -> X=0
less32 PUSH HL
PUSH AF
LD A,(lzpos +1)
LD E,A ; xstore - get length LSB
LD B,E
INC B ; adjust for cpx#$ff;bne -> bne
LD A,(regy +1)
LD C,A
LD HL,(OutPtr +1)
POP AF
dorle LD (HL),A
INC HL
DEC B
JR NZ,dorle ; xstore 0..255 -> 1..256
DEC C
JR NZ,dorle ; Y was 1 bigger than wanted originally
LD (OutPtr +1),HL
POP HL
JP main
lz77 CALL getval ; X=0 -> X=0
LD B,A
Max2Gamma LD A,&00
CP B ; end of file ?
RET Z ; yes, exit
ExtraBits LD A,&00 ; ** PARAMETER (more bits to get)
LD E,A
LD A,B
DEC A ; subtract 1 (1..126 -> 0..125)
INC E
CALL getchk ;f ; clears Carry, X=0 -> X=0
lz77_2 LD (lzpos +2),A ; offset MSB
LD E,8
CALL getbits ; clears Carry, X=8 -> X=0
; Note Already eored in the compressor..
LD B,A
LD A,(lzpos +1)
LD E,A ; xstore - LZLEN (read before it's overwritten)
LD A,(OutPtr +1)
ADD A,B ; -offset -1 + curpos (C is clear)
LD (lzpos +1),A
LD A,(lzpos +2)
LD B,A
LD A,(OutPtr+2)
CCF
SBC B
LD (lzpos +2),A ; copy X+1 number of chars from LZPOS to OUTPOS
INC E ; adjust for cpx#$ff;bne -> bne
; Write decompressed bytes out to RAM
LD B,E
PUSH DE
PUSH HL
lzpos LD HL,&0000
LD DE,(OutPtr +1)
; Modification GPA pour utilisation de LDI
; du coup, on libere le registre A
LD A,B
OR A ; Is it zero?
JR Z,zero ; yes
INC A
SRL A
JR NC,olzloop
; optimisable en LDI INC BC
lzloop LDI ; Note Must be copied forward
olzloop LDI ; Note Must be copied forward
DEC A
JR NZ,lzloop ; X loops, (256,1..255)
LD (OutPtr+1),DE
POP HL
POP DE
JP main
zero LD A,128
JR lzloop
; getval Gets a 'static huffman coded' value
; ** Scratches X, returns the value in A **
getval LD A,1 ; X must be 0 when called!
LD E,A
loop0 SLA D
JR NZ,loop1
LD D,(HL)
INC HL
RL D ; Shift in C=1 (last bit marker)
; bitstr initial value = $80 == empty
loop1 JR NC,getchk ; got 0-bit
INC E
LD B,A ; save a
MaxGamma LD A,&00
CP E
LD A,B ; restore a
JR NZ,loop0
JR getchk
; getbits Gets X bits from the stream
; ** Scratches X, returns the value in A **
get1bit INC E
getbits SLA D
JR NZ,loop3
LD D,(HL)
INC HL
RL D ; Shift in C=1 (last bit marker)
; bitstr initial value = &80 == empty
loop3 RLA
getchk DEC E
JR NZ,getbits
OR A ; clear carry flag
RET
NOTICE TEXTE n° 3 (4.65 Ko)
; Porte sous Maxam par T&J/GPA en juin 2007
;### uncrunch.asm -- Standalone Pucrunch decompressor
;### Based on C64 code by Pasi Ojala
;### Ported to Z80 by Jussi Pitkdnen <ccfgàpp.inet.fi>
;###
;### Thanks to Jeff Frohwein for his GB-Z80 port.
;###
;## Decompress a pucrunched file.
;## In HL = source address
;## DE = destination address
;## Destroy AF, BC, DE, HL, AF', BC', DE', HL'
ORG &A000
WRITE "Z80.BIN"
NOLIST
; Rajout pour pouvoir utiliser sous Basic la routine sur un CPC
; On preserve les vecteurs secondaires (en particulier BC, utilise pour
; gerer l'etat de la memoire).
; On a CPC, it is necessary to save secondary registers if the routine
; has to be used under Basic.
main DI
EXX
PUSH HL
PUSH DE
PUSH BC
EXX
CALL uncrunch
EXX
POP BC
POP DE
POP HL
EXX
EI
RET
uncrunch:
push de ; destination pointer to 2nd register
exx ; set
pop de
exx
inc hl ; read the header self-modifying the
inc hl ; parameters straight into the code
inc hl ; skip useless data
inc hl
inc hl
inc hl
ld a, (hl) ; starting escape
inc hl
ld (esc+1), a
inc hl ; skip useless data
inc hl
ld a, (hl) ; number of escape bits
inc hl
ld (escb0+1), a
ld (escb1+1), a
ld b, a ; 8 - escape bits
ld a, 8
sub b
ld (noesc+1), a
ld a, (hl) ; maxGamma + 1
inc hl
ld (mg+1), a
ld b, a ; 8 - maxGamma
ld a, 9
sub b
ld (longrle+1), a
ld a, (hl) ; (1 << maxGamma)
inc hl
ld (mg1+1), a
add a, a ; (2 << maxGamma) - 1
dec a
ld (mg21+1), a
ld a, (hl) ; extra LZ77 position bits
inc hl
ld (elzpb+1), a
inc hl ; skip useless data
inc hl
ld e, (hl) ; RLE table length
ld (rlet+1), hl ; RLE table pointer
inc hl
ld d, 0
add hl, de
ld c, 128 ; start decompression
jp loop
newesc ld a, (esc+1) ; save old escape code
ld d, a
escb0 ld b, 2 ; ** parameter
xor a ; get new escape code
call get_bits
ld (esc+1), a
ld a, d
noesc ld b, 6 ; ** parameter
call get_bits ; get more bits to complete a byte
exx ; output the byte
ld (de), a
inc de
exx
loop xor a
escb1 ld b, 2 ; ** parameter
call get_bits ; get escape code
esc cp 0 ; ** parameter
jp nz, noesc
call get_gamma ; get length
exx
ld b, 0
ld c, a
exx
cp 1
jp nz, lz77 ; LZ77
xor a
call get_bit
jp nc, lz77_2 ; 2-byte LZ77
call get_bit
jp nc, newesc ; escaped literal byte
call get_gamma ; get length
exx
ld b, 1
ld c, a
exx
mg1 cp 64 ; ** parameter
jp c, chrcode ; short RLE, get bytecode
longrle ld b, 2 ; ** parameter
call get_bits ; complete length LSB
ex af, af'
call get_gamma ; length MSB
exx
ld b, a
ex af, af'
ld c, a
exx
chrcode call get_gamma ; get byte to repeat
push hl
rlet ld hl, 0000 ; ** parameter
ld d, 0
ld e, a
add hl, de
cp 32
ld a, (hl)
pop hl
jp c, dorle
ld a, e ; get 3 more bits to complete the
ld b, 3 ; byte
call get_bits
dorle exx ; output the byte n times
inc c
dorlei ld (de), a
inc de
dec c
jp nz, dorlei
dec b
jp nz, dorlei
exx
jp loop
lz77 call get_gamma ; offset MSB
mg21 cp 127 ; ** parameter
ret z ; EOF, return
dec a ; (1...126 -> 0...125)
elzpb ld b, 0 ; ** parameter
call get_bits ; complete offset MSB
lz77_2 ex af, af'
ld b, 8 ; offset LSB
call get_bits
cpl ; xor'ed by the compressor
exx ; combine them into offset
ld l, a
ex af, af'
ld h, a
inc hl
xor a ; CF = 0
push de ; (current output position) - (offset)
ex de, hl
sbc hl, de
pop de
inc bc
ldir ; copy
exx
jp loop
;## Get a bit from the source stream.
;## Return CF = result
get_bit:
sla c ; shift next bit into CF
ret nz
ld c, (hl) ; get next byte
inc hl ; increase source stream pointer
rl c ; shift next bit into CF, bit0 = 1
ret
;## Get multiple bits from the source stream.
;## In B = number of bits to get
;## Return A = result
get_bits
dec b
ret m
sla c ; shift next bit into CF
jp nz, gb1
ld c, (hl) ; get next byte
inc hl ; increase source stream pointer
rl c ; shift next bit into CF, bit0 = 1
gb1 rla ; rotate next bit into A
jp get_bits
;## Get an Elias Gamma coded value from the source stream.
;## Return A = result
get_gamma
ld b, 1
mg ld a, 7 ; ** parameter
gg1 call get_bit ; get bits until 0-bit or max
jr nc, gg2
inc b
cp b
jp nz, gg1
gg2 ld a, 1 ; get the actual value
dec b
jp get_bits