; ; 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 ; or ; apultra.exe ; ; 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 ; ; 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