Login

Subversion Repositories NedoOS

Rev

Blame | Last modification | View Log | Download | RSS feed

        module APULTRA
;
;  Size-optimized ApLib decompressor by spke & uniabis (ver.04 01-07/06/2020, 139 bytes)
;
;  The original Z80 decompressor for ApLib was written by Dan Weiss (Dwedit),
;  then tweaked by Francisco Javier Pena Pareja (utopian),
;  and optimized by Jaime Tejedor Gomez (Metalbrain).
;
;  This version was heavily re-optimized for size by spke.
;  (It is 17 bytes shorter and 22% faster than the 156b version by Metalbrain.)
;
;  ver.00 by spke (21/08/2018-01/09/2018, 141 bytes);
;  ver.01 by spke (spring 2019, 140(-1) bytes, slightly faster);
;  ver.02 by spke (05-07/01/2020, added full revision history, support for long offsets
;                  and an option to use self-modifying code instead of IY)
;  ver.03 by spke (18-29/05/2020, +0.5% speed, added support for backward compression)
;  ver.04 by uniabis (01-07/06/2020, 139(-1) bytes, +1% speed, added support for HD64180)
;
;  The data must be compressed using any compressor for ApLib capable of generating raw data.
;  At present, two best available compressors are:
;
;  "APC" by Sven-Ake Dahl: https://github.com/svendahl/cap or
;  "apultra" by Emmanuel Marty: https://github.com/emmanuel-marty/apultra
;
;  The compression can be done as follows:
;
;  apc.exe e <sourcefile> <outfile>
;  or
;  apultra.exe <sourcefile> <outfile>
;
;  A decent compressor was written by r57shell (although it is worse than compressors above):
;  http://gendev.spritesmind.net/forum/viewtopic.php?p=32548#p32548
;  The use of the official ApLib compressor by Joergen Ibsen is not recommended.
;
;  The decompression is done in the standard way:
;
;  ld hl,FirstByteOfCompressedData
;  ld de,FirstByteOfMemoryForDecompressedData
;  call DecompressApLib
;
;  Backward decompression is also supported; you can compress files backward using:
;
;  apultra.exe -b <sourcefile> <outfile>
;
;  uncomment option "DEFINE BackwardDecompression" and decompress the resulting files using:
;
;  ld hl,LastByteOfCompressedData
;  ld de,LastByteOfMemoryForDecompressedData
;  call DecompressApLib
;
;  The decompressor modifies AF, AF', BC, DE, HL, IX.
;
;  Of course, ApLib compression algorithms are (c) 1998-2014 Joergen Ibsen,
;  see http://www.ibsensoftware.com/ for more information
;
;  Drop me an email if you have any comments/ideas/suggestions: zxintrospec@gmail.com
;
;  This software is provided 'as-is', without any express or implied
;  warranty.  In no event will the authors be held liable for any damages
;  arising from the use of this software.
;
;  Permission is granted to anyone to use this software for any purpose,
;  including commercial applications, and to alter it and redistribute it
;  freely, subject to the following restrictions:
;
;  1. The origin of this software must not be misrepresented; you must not
;     claim that you wrote the original software. If you use this software
;     in a product, an acknowledgment in the product documentation would be
;     appreciated but is not required.
;  2. Altered source versions must be plainly marked as such, and must not be
;     misrepresented as being the original software.
;  3. This notice may not be removed or altered from any source distribution.

;       DEFINE FasterGetBit                                     ; 16% speed-up at the cost of extra 4 bytes
;       DEFINE SupportLongOffsets                               ; +4 bytes for long offset support. slows decompression down by 1%, but may be needed to decompress files >=32K
;       DEFINE BackwardDecompression                            ; decompress data compressed backwards, -5 bytes, speeds decompression up by 3%


        IFDEF FasterGetBit
                MACRO   GET_BIT
                        add a : call z,ReloadByte
                ENDM
        ELSE
                MACRO   GET_BIT
                        call GetOneBit
                ENDM
        ENDIF

        IFNDEF BackwardDecompression

                MACRO NEXT_HL
                inc hl
                ENDM

                MACRO COPY_1
                ldi
                ENDM

                MACRO COPY_BC
                ldir
                ENDM

        ELSE

                MACRO NEXT_HL
                dec hl
                ENDM

                MACRO COPY_1
                ldd
                ENDM

                MACRO COPY_BC
                lddr
                ENDM

        ENDIF

@DecompressApLib:       ld a,128

;
;  case "0"+BYTE: copy a single literal

CASE0:                  COPY_1                                  ; first byte is always copied as literal
ResetLWM:               ld b,-1                                 ; LWM = 0 (LWM stands for "Last Was Match"; a flag that we did not have a match)

;
;  main decompressor loop

MainLoop:               GET_BIT : jr nc,CASE0                   ; "0"+BYTE = copy literal
                        GET_BIT : jr nc,CASE10                  ; "10"+gamma(offset/256)+BYTE+gamma(length) = the main matching mechanism

                        ld bc,%11100000
                        GET_BIT : jr nc,CASE110                 ; "110"+[oooooool] = matched 2-3 bytes with a small offset

;
;  case "111"+"oooo": copy a byte with offset -1..-15, or write zero to dest

CASE111:
ReadFourBits            GET_BIT                                 ; read short offset (4 bits)
                        rl c : jr c,ReadFourBits
                        ex de,hl : jr z,WriteZero               ; zero offset means "write zero" (NB: B is zero here)

                        ; "write a previous byte (1-15 away from dest)"
                        push hl                                 ; BC = offset, DE = src, HL = dest
        IFNDEF BackwardDecompression
                        sbc hl,bc                               ; HL = dest-offset (SBC works because branching above ensured NC)
        ELSE
                        add hl,bc                               ; HL = dest-offset (SBC works because branching above ensured NC)
        ENDIF
                        ld c,(hl) : pop hl

WriteZero               ld (hl),c : NEXT_HL
                        ex de,hl : jr ResetLWM                  ; write one byte, reset LWM

;
;  branch "110"+[oooooool]: copy two or three bytes (bit "l") with the offset -1..-127 (bits "ooooooo"), or stop

CASE110:                ; "use 7 bit offset, length = 2 or 3"
                        ; "if a zero is found here, it's EOF"
                        ld c,(hl) : rr c : ret z                ; process EOF
                        NEXT_HL

                        push hl                                 ; save src
                        ld h,b : ld l,c                         ; HL = offset

                        ; flag NC means len=2, flag C means len=3
                        ld c,1 : rl c : jr SaveLWMOffset
                       
;
;  branch "10"+gamma(offset/256)+BYTE+gamma(length): the main matching mechanism

CASE10:                 ; save state of LWM into A'
                        exa : ld a,b : exa

                        ; "use a gamma code * 256 for offset, another gamma code for length"
                        call GetGammaCoded

                        ; the original decompressor contains
                        ;
                        ; if ((LWM == 0) && (offs == 2)) { ... }
                        ; else {
                        ;       if (LWM == 0) { offs -= 3; }
                        ;       else { offs -= 2; }
                        ; }
                        ;
                        ; so, the idea here is to use the fact that GetGammaCoded returns (offset/256)+2,
                        ; and to split the first condition by noticing that C-1 can never be zero
                        exa : add c : ld c,a : exa

                        ; "if gamma code is 2, use old r0 offset"
                        dec c : jr z,KickInLWM
                        dec c
                        ld b,c : ld c,(hl) : NEXT_HL            ; BC = offset

                        push bc                                 ; (SP) = offset
                        call GetGammaCoded                      ; BC = len*
                        ex (sp),hl                              ; HL = offset, (SP) = src

                        ; interpretation of length value is offset-dependent
                        exa : ld a,h
        IFDEF   SupportLongOffsets
                        ; NB offsets over 32000 require an additional check, which is skipped in most
                        ; Z80 decompressors (seemingly as a performance optimization)
                        cp 32000/256 : jr nc,.Add2
        ENDIF
                        cp 5 : jr nc,.Add1
                        or a : jr nz,.Add0
                        bit 7,l : jr nz,.Add0
.Add2                   inc bc
.Add1                   inc bc
.Add0                   exa

SaveLWMOffset:
                        push hl : pop ix                        ; save offset for future LWMs

CopyMatch:              ; this assumes that BC = len, DE = dest, HL = offset
                        ; and also that (SP) = src, while having NC
        IFNDEF BackwardDecompression
                        push de
                        ex de,hl : sbc hl,de                    ; HL = dest-offset
                        pop de                                  ; DE = dest
        ELSE
                        add hl,de                               ; HL = dest+offset
        ENDIF

                        COPY_BC
                        pop hl                                  ; recover src
                        jr MainLoop

;
;  the re-use of the previous offset (LWM magic)

KickInLWM:              ; "and a new gamma code for length"
                        call GetGammaCoded                      ; BC = len
                        push ix : ex (sp),hl                    ; DE = dest, HL = prev offset
                        jr CopyMatch

;
;  interlaced gamma code reader
;  x0 -> 1x
;  x1y0 -> 1xy
;  x1y1z0 -> 1xyz etc
;  (technically, this is a 2-based variation of Exp-Golomb-1)

GetGammaCoded:          ld bc,1
ReadGamma               GET_BIT : rl c : rl b
                        GET_BIT : ret nc
                        jr ReadGamma

;
;  pretty usual getbit for mixed datastreams

        IFNDEF FasterGetBit
GetOneBit:              add a : ret nz
        ENDIF
ReloadByte:             ld a,(hl) : NEXT_HL
                        rla : ret


        endmodule