From 36cb6d77b5088340f978871646fc62616f9d551d Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Sat, 10 Sep 2022 11:31:09 +0200 Subject: [PATCH 01/24] BE bitstream, flip bit encoding --- src/context_state.rs | 4 ++-- src/rans.rs | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/context_state.rs b/src/context_state.rs index 062d74d..92d5fb2 100644 --- a/src/context_state.rs +++ b/src/context_state.rs @@ -1,4 +1,4 @@ -use crate::rans::{PROB_BITS, ONE_PROB}; +use crate::rans::{ONE_PROB, PROB_BITS}; const INIT_PROB: u16 = 1 << (PROB_BITS - 1); const UPDATE_RATE: u32 = 4; @@ -33,7 +33,7 @@ impl<'a> Context<'a> { pub fn update(&mut self, bit: bool) { let old = self.state.contexts[self.index]; - self.state.contexts[self.index] = if bit { + self.state.contexts[self.index] = if !bit { old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8 } else { old - ((old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8 diff --git a/src/rans.rs b/src/rans.rs index e81308a..3250e8a 100644 --- a/src/rans.rs +++ b/src/rans.rs @@ -38,15 +38,15 @@ impl RansCoder { let mut state = 1 << l_bits; let mut byte = 0u8; - let mut bit = 8; + let mut bit = 0; let mut flush_state: Box = if self.use_bitstream { Box::new(|state: &mut u32| { - bit -= 1; byte |= ((*state & 1) as u8) << bit; - if bit == 0 { + bit += 1; + if bit == 8 { buffer.push(byte); byte = 0; - bit = 8; + bit = 0; } *state >>= 1; }) @@ -61,7 +61,7 @@ impl RansCoder { let max_state_factor: u32 = 1 << (l_bits + num_flush_bits - PROB_BITS); for step in self.bits.into_iter().rev() { let prob = step as u32 & 32767; - let (start, prob) = if step & 32768 != 0 { + let (start, prob) = if step & 32768 == 0 { (0, prob) } else { (prob, ONE_PROB - prob) @@ -118,7 +118,7 @@ impl CostCounter { impl EntropyCoder for CostCounter { fn encode_bit(&mut self, bit: bool, prob: u16) { - let prob = if bit { + let prob = if !bit { prob as u32 } else { ONE_PROB - prob as u32 @@ -163,8 +163,8 @@ impl<'a> RansDecoder<'a> { self.data = &self.data[1..]; self.bits_left = 8; } - self.state = (self.state << 1) | (self.byte & 1) as u32; - self.byte >>= 1; + self.state = (self.state << 1) | (self.byte >> 7) as u32; + self.byte <<= 1; self.bits_left -= 1; } } else { @@ -174,12 +174,12 @@ impl<'a> RansDecoder<'a> { } } - let bit = (self.state & PROB_MASK) < prob; + let bit = (self.state & PROB_MASK) >= prob; let (start, prob) = if bit { - (0, prob) - } else { (prob, ONE_PROB - prob) + } else { + (0, prob) }; self.state = prob * (self.state >> PROB_BITS) + (self.state & PROB_MASK) - start; From f1f1c64a767f23a64d3f26a575afbd2efe1f6a1d Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Sat, 10 Sep 2022 12:01:42 +0200 Subject: [PATCH 02/24] implement simplified prob update, update unpack.c --- c_unpacker/unpack.c | 25 ++++++++++++------------- src/context_state.rs | 13 ++++++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/c_unpacker/unpack.c b/c_unpacker/unpack.c index c893d56..7de3deb 100644 --- a/c_unpacker/unpack.c +++ b/c_unpacker/unpack.c @@ -19,8 +19,8 @@ int upkr_decode_bit(int context_index) { upkr_current_byte = *upkr_data_ptr++; upkr_bits_left = 8; } - upkr_state = (upkr_state << 1) + (upkr_current_byte & 1); - upkr_current_byte >>= 1; + upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7); + upkr_current_byte <<= 1; --upkr_bits_left; } #else @@ -30,19 +30,18 @@ int upkr_decode_bit(int context_index) { #endif int prob = upkr_probs[context_index]; - int bit = (upkr_state & 255) < prob ? 1 : 0; + int bit = (upkr_state & 255) >= prob ? 1 : 0; - int tmp = prob; - if(!bit) { - tmp = 256 - tmp; + int prob_offset = 16; + int state_offset = 0; + int state_scale = prob; + if(bit) { + state_offset = -prob; + state_scale = 256 - prob; + prob_offset = 0; } - upkr_state = tmp * (upkr_state >> 8) + (upkr_state & 255); - tmp += (256 - tmp + 8) >> 4; - if(!bit) { - upkr_state -= prob; - tmp = 256 - tmp; - } - upkr_probs[context_index] = tmp; + upkr_state = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255); + upkr_probs[context_index] = prob_offset + prob - ((prob + 8) >> 4); return bit; } diff --git a/src/context_state.rs b/src/context_state.rs index 92d5fb2..b7a75f2 100644 --- a/src/context_state.rs +++ b/src/context_state.rs @@ -1,8 +1,8 @@ use crate::rans::{ONE_PROB, PROB_BITS}; const INIT_PROB: u16 = 1 << (PROB_BITS - 1); -const UPDATE_RATE: u32 = 4; -const UPDATE_ADD: u32 = 8; +const UPDATE_RATE: i32 = 4; +const UPDATE_ADD: i32 = 8; #[derive(Clone)] pub struct ContextState { @@ -33,10 +33,13 @@ impl<'a> Context<'a> { pub fn update(&mut self, bit: bool) { let old = self.state.contexts[self.index]; - self.state.contexts[self.index] = if !bit { - old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8 + let offset = if !bit { + ONE_PROB as i32 >> UPDATE_RATE } else { - old - ((old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8 + 0 }; + + self.state.contexts[self.index] = + (offset + old as i32 - ((old as i32 + UPDATE_ADD) >> UPDATE_RATE)) as u8; } } From 7b051113e1b33a1be2b3e3845524adabb35338ac Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Tue, 13 Sep 2022 22:12:03 +0200 Subject: [PATCH 03/24] z80_unpacker: initial working version with screen-slideshow example --- z80_unpacker/.gitignore | 4 + z80_unpacker/Makefile | 11 + z80_unpacker/example/example.asm | 49 +++ .../screens/Grongy - ZX Spectrum (2022).scr | Bin 0 -> 6912 bytes .../Grongy - ZX Spectrum (2022).scr.upk | Bin 0 -> 1813 bytes .../screens/Schafft - Poison (2017).scr | Bin 0 -> 6912 bytes .../screens/Schafft - Poison (2017).scr.upk | Bin 0 -> 1393 bytes .../screens/diver - Back to Bjork (2015).scr | Bin 0 -> 6912 bytes .../diver - Back to Bjork (2015).scr.upk | Bin 0 -> 2834 bytes ...014) (Forever 2014 Olympic Edition, 1).scr | Bin 0 -> 6912 bytes ... (Forever 2014 Olympic Edition, 1).scr.upk | Bin 0 -> 4317 bytes z80_unpacker/readme.txt | 19 ++ z80_unpacker/unpack.asm | 310 ++++++++++++++++++ 13 files changed, 393 insertions(+) create mode 100644 z80_unpacker/.gitignore create mode 100644 z80_unpacker/Makefile create mode 100644 z80_unpacker/example/example.asm create mode 100644 z80_unpacker/example/screens/Grongy - ZX Spectrum (2022).scr create mode 100644 z80_unpacker/example/screens/Grongy - ZX Spectrum (2022).scr.upk create mode 100644 z80_unpacker/example/screens/Schafft - Poison (2017).scr create mode 100644 z80_unpacker/example/screens/Schafft - Poison (2017).scr.upk create mode 100644 z80_unpacker/example/screens/diver - Back to Bjork (2015).scr create mode 100644 z80_unpacker/example/screens/diver - Back to Bjork (2015).scr.upk create mode 100644 z80_unpacker/example/screens/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr create mode 100644 z80_unpacker/example/screens/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr.upk create mode 100644 z80_unpacker/readme.txt create mode 100644 z80_unpacker/unpack.asm diff --git a/z80_unpacker/.gitignore b/z80_unpacker/.gitignore new file mode 100644 index 0000000..01edc46 --- /dev/null +++ b/z80_unpacker/.gitignore @@ -0,0 +1,4 @@ +*.bin +*.tap +*.sna +*.lst diff --git a/z80_unpacker/Makefile b/z80_unpacker/Makefile new file mode 100644 index 0000000..f4fc974 --- /dev/null +++ b/z80_unpacker/Makefile @@ -0,0 +1,11 @@ +all: unpack.bin example/example.sna + +# binary is positioned from ORG 0, not usable, just assembling to verify the syntax +unpack.bin: unpack.asm + sjasmplus --msg=war --lst --lstlab=sort --raw=unpack.bin unpack.asm + +example/example.sna: unpack.asm example/example.asm + cd example && sjasmplus --msg=war --lst --lstlab=sort example.asm + +clean: + $(RM) unpack.bin unpack.lst example/example.sna example/example.lst diff --git a/z80_unpacker/example/example.asm b/z80_unpacker/example/example.asm new file mode 100644 index 0000000..e42d0e4 --- /dev/null +++ b/z80_unpacker/example/example.asm @@ -0,0 +1,49 @@ +;; Example using upkr depacker for screens slideshow + OPT --syntax=abf + DEVICE ZXSPECTRUM48,$8FFF + + ORG $9000 +compressed_scr_files: ; border color byte + upkr-packed .scr file + DB 1 + INCBIN "screens/Grongy - ZX Spectrum (2022).scr.upk" + DB 7 + INCBIN "screens/Schafft - Poison (2017).scr.upk" + DB 0 + INCBIN "screens/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr.upk" + DB 6 + INCBIN "screens/diver - Back to Bjork (2015).scr.upk" +.e: + +start: + di +; OPT --zxnext +; nextreg 7,3 ; ZX Next: switch to 28Mhz + ld ix,compressed_scr_files +.slideshow_loop + ; set BORDER for next image + ldi a,(ix) ; fake: ld a,(ix) : inc ix + out (254),a + ; call unpack of next image directly into VRAM + ld hl,$4000 ; target VRAM + exx + ; IX = packed data, HL' = destination ($4000) + ; returned IX will point right after the packed data + call upkr.unpack + ; do some busy loop with CPU to delay between images + ld bc,$AA00 +.delay: + .8 ex (sp),ix + dec c + jr nz,.delay + djnz .delay + ; check if all images were displayed, loop around from first one then + ld a,ixl + cp low compressed_scr_files.e + jr z,start + jr .slideshow_loop + + ; include the depacker library, optionally putting probs array buffer near end of RAM + DEFINE UPKR_PROBS_ORIGIN $FA00 ; if not defined, array will be put after unpack code + INCLUDE "../unpack.asm" + + SAVESNA "example.sna",start diff --git a/z80_unpacker/example/screens/Grongy - ZX Spectrum (2022).scr b/z80_unpacker/example/screens/Grongy - ZX Spectrum (2022).scr new file mode 100644 index 0000000000000000000000000000000000000000..6a6ce26b09265de39364db85825c15014955bad5 GIT binary patch literal 6912 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 e;sh@tyjfk$IOSalEko50PsV91Js=u8FX6 z^@>|pmN%+N7hzd-A;yLctO-+z)Jo~ zDbniX)z6%@;01Vf0V^h)!pUGXVW2A8rsT|ds88zqUNV0&j@ev$!^7s?gO?hh!pxcH zwdP_9b7B0o*gi7~$C2-sJT&r ze3I#7T%%=!;&mK9W1!laZpy$IO=bN;KqP@&BjtE`>LnrGjWy%Mg}*S|kRda~AMLiL zY0+yX2`{UA?v!4Ub2}hy$TznZK+y*yS(JOSQPIW`;|^FOUZ&${iQ=6j@L|q`^t45Z zz6x!c2Yo5`1s}0i^(@Oo+HS}&y9;9+8B4%E!;e<%Om+5l0bnh z=u8J);lDwh)12kh10|S$yagIdH~3^ucqo|Ut*-q!kpAnjacOsKs&XBY^;{vQh#Jw_ zfpZpSD9ME8E(m(br_$(UH_|Ea{m--lAq8xXQY#|)W^t{&EJ|~BCT)IpzUX@**6!&G zw(s@u*x}^2yW$+SR}pmzuL(rnCd^VTA2Q4kr9&wRSLl+s7mS%yO|}=|Q$D-tl9flR zaj*feI0%68z0ltrT<_03-7@iA2q*n2l8b(o1z=B-@En7QpT7yejxU#gwiq3?43jCv z0fCd^hWwRZT{@*%8qq5HGK}PJgjH%-7YQ1WcVE6YB{|+MzDpOBDEXI>=%txoL|6o2xwdol4CiWP^dFnI+&m|kX2JN;aq*^@_1@U%3arPj3cJEP)>cau8(;N? z1UABzKD&3xYTTqqeXsGTcldE7$2L>V8)v;$rqsxv+R zuRf=1W`6Wm9tk!ffwPjs2a`kq;tzcgMQ79ZYz|N`hr#Q!d>4jztUhV>YFEwY*r(sR z(j?&i1?t3pv$(+2bf=3~f=t0xg3A;QI(BYy2~ZU7 zuV;lR-L6g#Hk3!@Ti+xF08f=@?3*3kX-Q^ zSjgMV$@e+Qy`629?n(`lg$Jb^*+Fcij!%}z<*85sdc^sC&~9$!)}$?4w;711Zq?dS zJk43osJkL?QwMk14Ez7Q>Ak=jqUZvVV5f~MDCeRi;L-L5d52Oc(_Be}2q?6UPu9va z@jNxq&X@n$I0_Qnyy2-|h{1@4#tYl(>tCNotGhymZ;XFBQyz%V2*bSAz_03&IWC$? z#CYn#XS7=-aKDTsr z&mEU?ei%;{AYNTpP#rmjhsrV!{u3EgVNG~8Q8EJAlCC-h<@cVq_uWiP5jC0tW}S6mH`^kTO@oFdnrTMe_uYxFHHuI-5t*zGv1) zrj{Y(;KyyWi2nDWQmExe^Al==DWd=p+){!aMaLz`_8F9bR}+#UzgX0Mf(Ldw+K7EHLwRA{>OM@+Rq_FZOEqBwJwqSmlO zD?n@y24@!ED~%@-V1joG{nb6U2NNRvl$K}&sKoof4@(lu-i28RZDW&3xjzv=QzFs@ z*Ia-KX8cCQV}n$OI*+at#mNfx5q^L_MSm-zYW`DKX#&i3E;zgfI_>hknJBtb`De0!%>~l60<1_fKo6`XCY8gSL_m?<%ZdU}T z8K?uKd{Cs;eI1W%%$@_qsG^qq-?ZgY&Nt!4D{!e-Xgtsi#T5N~0Dyz#s-#nzEQHVK zFP|&G{9Zp%mzsy^VWnHBMh6K|u|wWMx87w}I9wKO!2&DEP0ZF5DE&HWm>i(;po zPEqkN`JmJ#j9Lb4ONd*1^ae{Ly9SRrL@eCT<-3!BSUQn|rV=teGLj~2EbEuNm;H-t zU%MioHTQ{_5jET(!pg+$-K-Oz^X2RLAZf$A;AP)c#80IJycR}yY8AnXi!sP zTPGb$vCM{$*JZ0;H+xIQysK9&!XjU#xq~70xEk{>BF#KXn75^taMC8A;)`fBtkH!@ zZbM>DJ-CB#2g86NT^90RY^n=ou^O?4MrJS8yNdd4yj({bpvZpEeKcl>P1Se8xn_CU kb|2r=121nRU~euLs04*GNs{jB1 literal 0 HcmV?d00001 diff --git a/z80_unpacker/example/screens/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr b/z80_unpacker/example/screens/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr new file mode 100644 index 0000000000000000000000000000000000000000..549041c2038e285c55b58798864e428eff585366 GIT binary patch literal 6912 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 L9Q~&U5bP?zRPslV literal 0 HcmV?d00001 diff --git a/z80_unpacker/readme.txt b/z80_unpacker/readme.txt new file mode 100644 index 0000000..b7fff1b --- /dev/null +++ b/z80_unpacker/readme.txt @@ -0,0 +1,19 @@ +Z80 asm implementation of C unpacker, code-size focused (not performance). + +**ONLY BITSTREAM** variant is currently supported, make sure to use "-b" in packer. + +The project is expected to further evolve, including possible changes to binary format, this is +initial version of Z80 unpacker to explore if/how it works and how it can be improved further. + +(copy full packer+depacker source to your project if you plan to use it, as future revisions +may be incompatible with files you will produce with current version) + +Asm syntax is z00m's sjasmplus: https://github.com/z00m128/sjasmplus + +TODO: +- build base corpus of test data to benchmark future changes in algorithm/format +- review first implementation to identify weak spots where the implementation can be shorter+faster +with acceptable small changes to the format +- review non-bitstream variant, if it's feasible to try to implement it with Z80 +- (@ped7g) Z80N version of unpacker for ZX Next devs +- (@exoticorn) add Z80 specific packer (to avoid confusion with original MicroW8 variant), and land it all to master branch, maybe in "z80" directory or something? (and overall decide how to organise+merge this upstream into main repo) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm new file mode 100644 index 0000000..de82b66 --- /dev/null +++ b/z80_unpacker/unpack.asm @@ -0,0 +1,310 @@ +;; https://github.com/exoticorn/upkr/blob/z80/c_unpacker/unpack.c - original C implementation +;; C source in comments ahead of asm - the C macros are removed to keep only bitstream variant +;; +;; initial version by Peter "Ped" Helcmanovsky (C) 2022, licensed same as upkr project ("unlicensed") +;; to assemble use z00m's sjasmplus: https://github.com/z00m128/sjasmplus +;; +;; you can define UPKR_PROBS_ORIGIN to specific 256 byte aligned address for probs array (386 bytes), +;; otherwise it will be positioned after the unpacker code (256 aligned) +;; +;; public API: +;; +;; upkr.unpack +;; IN: IX = packed data, HL' (shadow HL) = destination +;; OUT: IX = after packed data +;; modifies: all registers except IY, requires 14 bytes of stack space +;; + + OPT push reset --syntax=abf + MODULE upkr + +/* +u8* upkr_data_ptr; +u8 upkr_probs[1 + 255 + 1 + 2*32 + 2*32]; +u16 upkr_state; +u8 upkr_current_byte; +int upkr_bits_left; + +int upkr_unpack(void* destination, void* compressed_data) { + upkr_data_ptr = (u8*)compressed_data; + upkr_state = 0; + upkr_bits_left = 0; + for(int i = 0; i < sizeof(upkr_probs); ++i) + upkr_probs[i] = 128; + + u8* write_ptr = (u8*)destination; + + int prev_was_match = 0; + int offset = 0; + for(;;) { + if(upkr_decode_bit(0)) { + if(prev_was_match || upkr_decode_bit(256)) { + offset = upkr_decode_length(257) - 1; + if(offset == 0) { + break; + } + } + int length = upkr_decode_length(257 + 64); + while(length--) { + *write_ptr = write_ptr[-offset]; + ++write_ptr; + } + prev_was_match = 1; + } else { + int byte = 1; + while(byte < 256) { + int bit = upkr_decode_bit(byte); + byte = (byte << 1) + bit; + } + *write_ptr++ = byte; + prev_was_match = 0; + } + } + + return write_ptr - (u8*)destination; +} +*/ +; IN: IX = compressed_data, HL' = destination +unpack: + ; ** reset probs to 0x80, also reset HL (state) to zero, and set BC to probs+context 0 + ld hl,probs.c>>1 + ld bc,probs.e + ld a,$80 +.reset_probs: + dec bc + ld (bc),a ; will overwrite one extra byte after the array because of odd length + dec bc + ld (bc),a + dec l + jr nz,.reset_probs + exa + ; BC = probs (context_index 0), state HL = 0, A' = 0x80 (no source bits left in upkr_current_byte) + + ; ** main loop to decompress data + ld (.offset),hl ; offset = 0 +.decompress_data_reset_match: + ld d,0 ; prev_was_match = 0; +.decompress_data: + ld c,0 + call decode_bit ; if(upkr_decode_bit(0)) + jr c,.copy_chunk + + ; * extract byte from compressed data (literal) + ld e,1 ; E = byte = 1 +.decode_byte: + ld c,e + call decode_bit ; bit = upkr_decode_bit(byte); + rl e ; byte = (byte << 1) + bit; + jr nc,.decode_byte ; while(byte < 256) + ld a,e + exx + ld (hl),a ; *write_ptr++ = byte; + inc hl + exx + jr .decompress_data_reset_match + + ; * copy chunk of already decompressed data (match) +.copy_chunk: + inc b ; context_index = 256 + ; if(prev_was_match || upkr_decode_bit(256)) { + ; offset = upkr_decode_length(257) - 1; + ; if (0 == offset) break; + ; } + ld a,d ; A = prev_was_match + or a + jr nz,.decode_offset ; if(prev_was_match + call decode_bit ; upkr_decode_bit(256) + jr nc,.keep_offset +.decode_offset: + inc c + call decode_length + dec de ; offset = upkr_decode_length(257) - 1; + ld a,d + or e + ret z ; if(offset == 0) break + ld (.offset),de +.keep_offset: + ; int length = upkr_decode_length(257 + 64); + ; while(length--) { + ; *write_ptr = write_ptr[-offset]; + ; ++write_ptr; + ; } + ; prev_was_match = 1; + ld c,low(257+64) ; context_index = 257+64 + call decode_length ; length = upkr_decode_length(257 + 64); + push de + exx + push hl +.offset+*: ld de,0 + or a + sbc hl,de + pop de + pop bc + ldir + ex de,hl + exx + ld d,b ; prev_was_match = non-zero + djnz .decompress_data ; adjust context_index back to 0..255 range, go to main loop + +/* +int upkr_decode_bit(int context_index) { + while(upkr_state < 32768) { + if(upkr_bits_left == 0) { + upkr_current_byte = *upkr_data_ptr++; + upkr_bits_left = 8; + } + upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7); + upkr_current_byte <<= 1; + --upkr_bits_left; + } + + int prob = upkr_probs[context_index]; + int bit = (upkr_state & 255) >= prob ? 1 : 0; + + int prob_offset = 16; + int state_offset = 0; + int state_scale = prob; + if(bit) { + state_offset = -prob; + state_scale = 256 - prob; + prob_offset = 0; + } + upkr_state = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255); + upkr_probs[context_index] = prob_offset + prob - ((prob + 8) >> 4); + + return bit; +} +*/ +decode_bit: + ; HL = upkr_state + ; IX = upkr_data_ptr + ; BC = probs+context_index + ; A' = upkr_current_byte (!!! init to 0x80 at start, not 0x00) + ; preserves DE + ; ** while (state < 32768) - initial check + push de + bit 7,h + jr nz,.state_b15_set + exa + ; ** while body +.state_b15_zero: + ; HL = upkr_state + ; IX = upkr_data_ptr + ; A = upkr_current_byte (init to 0x80 at start, not 0x00) + add a,a ; upkr_current_byte <<= 1; // and testing if(upkr_bits_left == 0) + jr nz,.has_bit ; CF=data, ZF=0 -> some bits + stop bit still available + ; CF=1 (by stop bit) + ld a,(ix) + inc ix ; upkr_current_byte = *upkr_data_ptr++; + adc a,a ; CF=data, b0=1 as new stop bit +.has_bit: + adc hl,hl ; upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7); + jp p,.state_b15_zero ; while (state < 32768) + exa + ; ** set "bit" +.state_b15_set: + ld a,(bc) ; A = upkr_probs[context_index] + dec a ; prob is in ~7..249 range, never zero, safe to -1 + cp l ; CF = bit = prob-1 < (upkr_state & 255) <=> prob <= (upkr_state & 255) + inc a + ; ** adjust state + push af + push af + push hl + push af + jr nc,.bit_is_0 + neg ; A = -prob == (256-prob), CF=1 preserved +.bit_is_0: + ld d,0 + ld e,a ; DE = state_scale ; prob || (256-prob) + ld l,d ; H:L = (upkr_state>>8) : 0 + ld a,8 ; counter +.mulLoop: + add hl,hl + jr nc,.mul0 + add hl,de +.mul0: + dec a + jr nz,.mulLoop ; until HL = state_scale * (upkr_state>>8) + pop af + jr nc,.bit_is_0_2 + dec d ; D = 0xFF (DE = -prob) + add hl,de ; HL += -prob +.bit_is_0_2: ; HL = state_offset + state_scale * (upkr_state >> 8) + pop de + ld d,0 ; DE = (upkr_state & 255) + add hl,de ; HL = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255) ; new upkr_state + ; *** adjust probs[context_index] + pop af ; restore prob and bit + ld e,a + jr c,.bit_is_1 + ld d,-16 ; 0xF0 +.bit_is_1: ; D:E = -prob_offset:prob, A = prob + ;FIXME and + 4x rra will be probably shorter! + srl a + srl a + srl a + srl a + adc a,d ; A = -prob_offset + ((prob + 8) >> 4) + neg + add a,e ; A = prob_offset + prob - ((prob + 8) >> 4) + ld (bc),a ; update probs[context_index] + pop af ; restore resulting CF = bit + ; TODO: check if it's possible to `cpl` instead of neg, have +1 on original prob, + ; and get correct CF=bit from `add a,e` then (without extra push+pop AF) + ; !!! I think this will **NOT** work, because clamping of prob ends with +-0 at both ends (cpl 0 -> 255 -> CF=1) + pop de + ret + +/* +int upkr_decode_length(int context_index) { + int length = 0; + int bit_pos = 0; + while(upkr_decode_bit(context_index)) { + length |= upkr_decode_bit(context_index + 1) << bit_pos++; + context_index += 2; + } + return length | (1 << bit_pos); +} +*/ +decode_length: + ; HL = upkr_state + ; IX = upkr_data_ptr + ; BC = probs+context_index + ; A' = upkr_current_byte (!!! init to 0x80 at start, not 0x00) + ; return length in DE + ld de,$8000 ; length = 0 with positional-stop-bit + jr .loop_entry +.loop: + inc bc ; context_index + 1 ; TODO can be just `inc c` for 257.. and 257+64.. contexts + call decode_bit + rr d + rr e ; DE = length = (length >> 1) | (bit << 15); + inc bc ; context_index += 2 ; TODO can be just `inc c` for 257.. and 257+64.. contexts +.loop_entry: + call decode_bit + jr c,.loop + scf ; will become this final `| (1 << bit_pos)` bit +.fix_bit_pos: + rr d + rr e + jr nc,.fix_bit_pos ; until stop bit is reached (all bits did land to correct position) + ret + + DISPLAY "upkr.unpack total size: ",/D,$-unpack + + ; reserve space for probs array without emitting any machine code (using only EQU) + + IFDEF UPKR_PROBS_ORIGIN ; if specific address is defined by user, move probs array there + ORG UPKR_PROBS_ORIGIN + ENDIF + +probs: EQU ($+255) & -$100 ; probs array aligned to 256 +.real_c: EQU 1 + 255 + 1 + 2*32 + 2*32 ; real size of probs array +.c: EQU (.real_c + 1) & -2 ; padding to even size (required by init code) +.e: EQU probs + .c + + DISPLAY "upkr.unpack probs array placed at: ",/A,probs,",\tsize: ",/A,probs.c + + ENDMODULE + OPT pop From a19ec2abb795bf1dddfc25bfb7078edfe714c836 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Tue, 13 Sep 2022 22:53:15 +0200 Subject: [PATCH 04/24] z80_unpacker: optimisations: remove .offset init first offset is mandatory in packed data --- z80_unpacker/unpack.asm | 1 - 1 file changed, 1 deletion(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index de82b66..2e5f9eb 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -81,7 +81,6 @@ unpack: ; BC = probs (context_index 0), state HL = 0, A' = 0x80 (no source bits left in upkr_current_byte) ; ** main loop to decompress data - ld (.offset),hl ; offset = 0 .decompress_data_reset_match: ld d,0 ; prev_was_match = 0; .decompress_data: From ea5c0b1b15cffcde2736c752adfaf957b18a8a1e Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Tue, 13 Sep 2022 23:15:18 +0200 Subject: [PATCH 05/24] z80_unpacker: optimisations: shorter `>>4` in probs update --- z80_unpacker/unpack.asm | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 2e5f9eb..cdd800b 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -239,19 +239,16 @@ decode_bit: jr c,.bit_is_1 ld d,-16 ; 0xF0 .bit_is_1: ; D:E = -prob_offset:prob, A = prob - ;FIXME and + 4x rra will be probably shorter! - srl a - srl a - srl a - srl a + and $F8 + rra + rra + rra + rra adc a,d ; A = -prob_offset + ((prob + 8) >> 4) neg add a,e ; A = prob_offset + prob - ((prob + 8) >> 4) ld (bc),a ; update probs[context_index] pop af ; restore resulting CF = bit - ; TODO: check if it's possible to `cpl` instead of neg, have +1 on original prob, - ; and get correct CF=bit from `add a,e` then (without extra push+pop AF) - ; !!! I think this will **NOT** work, because clamping of prob ends with +-0 at both ends (cpl 0 -> 255 -> CF=1) pop de ret From 919a892ef0f4eec37883db5e6634e3cbff1841fa Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Tue, 13 Sep 2022 23:25:03 +0200 Subject: [PATCH 06/24] z80_unpacker: optimisations: -1B by decode_length returning CF=0 --- z80_unpacker/unpack.asm | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index cdd800b..d103a43 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -135,8 +135,7 @@ unpack: exx push hl .offset+*: ld de,0 - or a - sbc hl,de + sbc hl,de ; CF=0 from decode_length pop de pop bc ldir @@ -268,8 +267,8 @@ decode_length: ; IX = upkr_data_ptr ; BC = probs+context_index ; A' = upkr_current_byte (!!! init to 0x80 at start, not 0x00) - ; return length in DE - ld de,$8000 ; length = 0 with positional-stop-bit + ; return length in DE, CF=0 + ld de,$7FFF ; length = 0 with positional-stop-bit jr .loop_entry .loop: inc bc ; context_index + 1 ; TODO can be just `inc c` for 257.. and 257+64.. contexts @@ -280,12 +279,12 @@ decode_length: .loop_entry: call decode_bit jr c,.loop - scf ; will become this final `| (1 << bit_pos)` bit .fix_bit_pos: + ccf ; NC will become this final `| (1 << bit_pos)` bit rr d rr e - jr nc,.fix_bit_pos ; until stop bit is reached (all bits did land to correct position) - ret + jr c,.fix_bit_pos ; until stop bit is reached (all bits did land to correct position) + ret ; return with CF=0 (important for unpack routine) DISPLAY "upkr.unpack total size: ",/D,$-unpack From d30baaa91f0c5b2aafcac96f3297d2ffb9a59376 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Tue, 13 Sep 2022 23:57:59 +0200 Subject: [PATCH 07/24] z80_unpacker: optimisations: -1B by keeping write_ptr in DE' --- z80_unpacker/example/example.asm | 4 ++-- z80_unpacker/unpack.asm | 19 +++++++++---------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/z80_unpacker/example/example.asm b/z80_unpacker/example/example.asm index e42d0e4..80fd790 100644 --- a/z80_unpacker/example/example.asm +++ b/z80_unpacker/example/example.asm @@ -24,9 +24,9 @@ start: ldi a,(ix) ; fake: ld a,(ix) : inc ix out (254),a ; call unpack of next image directly into VRAM - ld hl,$4000 ; target VRAM + ld de,$4000 ; target VRAM exx - ; IX = packed data, HL' = destination ($4000) + ; IX = packed data, DE' = destination ($4000) ; returned IX will point right after the packed data call upkr.unpack ; do some busy loop with CPU to delay between images diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index d103a43..587d88f 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -10,7 +10,7 @@ ;; public API: ;; ;; upkr.unpack -;; IN: IX = packed data, HL' (shadow HL) = destination +;; IN: IX = packed data, DE' (shadow DE) = destination ;; OUT: IX = after packed data ;; modifies: all registers except IY, requires 14 bytes of stack space ;; @@ -64,7 +64,7 @@ int upkr_unpack(void* destination, void* compressed_data) { return write_ptr - (u8*)destination; } */ -; IN: IX = compressed_data, HL' = destination +; IN: IX = compressed_data, DE' = destination unpack: ; ** reset probs to 0x80, also reset HL (state) to zero, and set BC to probs+context 0 ld hl,probs.c>>1 @@ -97,8 +97,8 @@ unpack: jr nc,.decode_byte ; while(byte < 256) ld a,e exx - ld (hl),a ; *write_ptr++ = byte; - inc hl + ld (de),a ; *write_ptr++ = byte; + inc de exx jr .decompress_data_reset_match @@ -133,13 +133,12 @@ unpack: call decode_length ; length = upkr_decode_length(257 + 64); push de exx - push hl -.offset+*: ld de,0 - sbc hl,de ; CF=0 from decode_length - pop de - pop bc + ld h,d ; DE = write_ptr + ld l,e +.offset+*: ld bc,0 + sbc hl,bc ; CF=0 from decode_length ; HL = write_ptr - offset + pop bc ; BC = length ldir - ex de,hl exx ld d,b ; prev_was_match = non-zero djnz .decompress_data ; adjust context_index back to 0..255 range, go to main loop From 511ddefc08161125b78e81d7444900150ad21980 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 00:01:51 +0200 Subject: [PATCH 08/24] z80_unpacker: optimisations: -4T per offset/length bit decoded making the 256-alignment of probs array even more baked-in, but there was no real chance to get rid of that any way --- z80_unpacker/unpack.asm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 587d88f..7167242 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -270,11 +270,11 @@ decode_length: ld de,$7FFF ; length = 0 with positional-stop-bit jr .loop_entry .loop: - inc bc ; context_index + 1 ; TODO can be just `inc c` for 257.. and 257+64.. contexts + inc c ; context_index + 1 call decode_bit rr d rr e ; DE = length = (length >> 1) | (bit << 15); - inc bc ; context_index += 2 ; TODO can be just `inc c` for 257.. and 257+64.. contexts + inc c ; context_index += 2 .loop_entry: call decode_bit jr c,.loop From 02d20867eecb2d1f1d769f6f966a84923806ec68 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 01:01:07 +0200 Subject: [PATCH 09/24] z80_unpacker: optimisations: -2B in unpack implementation = 185B --- z80_unpacker/unpack.asm | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 7167242..d179888 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -109,12 +109,10 @@ unpack: ; offset = upkr_decode_length(257) - 1; ; if (0 == offset) break; ; } - ld a,d ; A = prev_was_match - or a - jr nz,.decode_offset ; if(prev_was_match - call decode_bit ; upkr_decode_bit(256) - jr nc,.keep_offset -.decode_offset: + xor a + cp d ; CF = prev_was_match + call nc,decode_bit ; if not prev_was_match, then upkr_decode_bit(256) + jr nc,.keep_offset ; if neither, keep old offset inc c call decode_length dec de ; offset = upkr_decode_length(257) - 1; From c7ea11bce3184f101543d127b644dad221007fa5 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 01:44:04 +0200 Subject: [PATCH 10/24] z80_unpacker: optimisations: -2B in unpack implementation = 183B --- z80_unpacker/unpack.asm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index d179888..cd2d507 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -89,13 +89,12 @@ unpack: jr c,.copy_chunk ; * extract byte from compressed data (literal) - ld e,1 ; E = byte = 1 + inc c ; C = byte = 1 (and also context_index) .decode_byte: - ld c,e call decode_bit ; bit = upkr_decode_bit(byte); - rl e ; byte = (byte << 1) + bit; + rl c ; byte = (byte << 1) + bit; jr nc,.decode_byte ; while(byte < 256) - ld a,e + ld a,c exx ld (de),a ; *write_ptr++ = byte; inc de From a1dabaf7f9fc5d01d24c2a0dac017ccaff04bbcf Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Wed, 14 Sep 2022 23:41:14 +0200 Subject: [PATCH 11/24] add simple script to compare compression of variants --- compare-variants | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100755 compare-variants diff --git a/compare-variants b/compare-variants new file mode 100755 index 0000000..87cee5e --- /dev/null +++ b/compare-variants @@ -0,0 +1,48 @@ +#!/bin/env ruby + +configs = [ + [:master, '-b'], + [:z80, '-b'] +] + +files = Dir[ARGV[0] + '/*'].select {|f| !(f =~ /\.txt$/) } +short_names = files.map {|f| File.basename(f)[..16] } +results = [] + +def print_results(configs, names, results) + configs.each_with_index do |config, i| + printf "%d: %s\n", i + 1, config + end + + print ' ' + configs.each_index do |i| + printf " %-4d", i + 1 + end + puts + names.each_with_index do |name, i| + printf "%16s", name + for res in results + res = res[i] + printf " %-4s", res if res + end + puts + end +end + +for config in configs + raise unless system('git', 'checkout', config[0].to_s) + config_results = [] + results << config_results + for file in files + if system('cargo', 'run', '--release', 'pack', '-l', '9', config[1], file, '/tmp/out.upk') && + system('cargo', 'run', '--release', 'unpack', config[1], '/tmp/out.upk', '/tmp/out.bin') && + File.read(file) == File.read('/tmp/out.bin') + size = File.size('/tmp/out.upk') + config_results << size + else + config_results << 'ERR' + end + print_results(configs, short_names, results) + end +end + From e7aaf1491a96fa0406cef718996e38347b46d7dd Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Wed, 14 Sep 2022 23:51:38 +0200 Subject: [PATCH 12/24] add old-prob-update to compare script, add reverse option --- compare-variants | 8 +++++--- src/main.rs | 12 ++++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/compare-variants b/compare-variants index 87cee5e..f31260c 100755 --- a/compare-variants +++ b/compare-variants @@ -2,7 +2,9 @@ configs = [ [:master, '-b'], - [:z80, '-b'] + [:z80, '-b'], + [:z80, ['-b', '-r']], + ['old-prob-update', '-b'] ] files = Dir[ARGV[0] + '/*'].select {|f| !(f =~ /\.txt$/) } @@ -34,8 +36,8 @@ for config in configs config_results = [] results << config_results for file in files - if system('cargo', 'run', '--release', 'pack', '-l', '9', config[1], file, '/tmp/out.upk') && - system('cargo', 'run', '--release', 'unpack', config[1], '/tmp/out.upk', '/tmp/out.bin') && + if system(*['cargo', 'run', '--release', 'pack', config[1], file, '/tmp/out.upk'].flatten) && + system(*['cargo', 'run', '--release', 'unpack', config[1], '/tmp/out.upk', '/tmp/out.bin'].flatten) && File.read(file) == File.read('/tmp/out.bin') size = File.size('/tmp/out.upk') config_results << size diff --git a/src/main.rs b/src/main.rs index 711cb32..b809969 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,16 @@ fn main() -> Result<()> { Some("pack") => { let level = args.opt_value_from_str(["-l", "--level"])?.unwrap_or(2u8); let use_bitstream = args.contains(["-b", "--bitstream"]); + let reverse = args.contains(["-r", "--reverse"]); let infile = args.free_from_os_str::(|s| Ok(s.into()))?; let outfile = args.free_from_os_str::(|s| Ok(s.into()))?; let mut data = vec![]; File::open(infile)?.read_to_end(&mut data)?; + if reverse { + data.reverse(); + } let mut pb = pbr::ProgressBar::new(data.len() as u64); pb.set_units(pbr::Units::Bytes); @@ -39,14 +43,18 @@ fn main() -> Result<()> { } Some("unpack") => { let use_bitstream = args.contains(["-b", "--bitstream"]); + let reverse = args.contains(["-r", "--reverse"]); let infile = args.free_from_os_str::(|s| Ok(s.into()))?; let outfile = args.free_from_os_str::(|s| Ok(s.into()))?; let mut data = vec![]; File::open(infile)?.read_to_end(&mut data)?; - let packed_data = upkr::unpack(&data, use_bitstream); - File::create(outfile)?.write_all(&packed_data)?; + let mut unpacked_data = upkr::unpack(&data, use_bitstream); + if reverse { + unpacked_data.reverse(); + } + File::create(outfile)?.write_all(&unpacked_data)?; } Some(other) => { bail!("Unknown subcommand '{}'", other); From 540a91d1ba619bf06fd1a2867a8bc2762e670ca1 Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Thu, 15 Sep 2022 00:18:30 +0200 Subject: [PATCH 13/24] forgot to add back -l 9 --- compare-variants | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compare-variants b/compare-variants index f31260c..55533e2 100755 --- a/compare-variants +++ b/compare-variants @@ -36,7 +36,7 @@ for config in configs config_results = [] results << config_results for file in files - if system(*['cargo', 'run', '--release', 'pack', config[1], file, '/tmp/out.upk'].flatten) && + if system(*['cargo', 'run', '--release', 'pack', '-l', '9', config[1], file, '/tmp/out.upk'].flatten) && system(*['cargo', 'run', '--release', 'unpack', config[1], '/tmp/out.upk', '/tmp/out.bin'].flatten) && File.read(file) == File.read('/tmp/out.bin') size = File.size('/tmp/out.upk') From a75a35efb2a21ce5fe199cf49c23c6853fa4b730 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 19:53:50 +0200 Subject: [PATCH 14/24] z80_unpacker: probs context-size for offset/length numbers as EQU --- z80_unpacker/unpack.asm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index cd2d507..0de80a9 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -18,6 +18,9 @@ OPT push reset --syntax=abf MODULE upkr +NUMBER_BITS EQU 16+15 ; context-bits per offset/length (16+15 for 16bit offsets/pointers) + ; numbers (offsets/lengths) are encoded like: 1a1b1c1d1e0 = 0000'0000'001e'dbca + /* u8* upkr_data_ptr; u8 upkr_probs[1 + 255 + 1 + 2*32 + 2*32]; @@ -112,8 +115,8 @@ unpack: cp d ; CF = prev_was_match call nc,decode_bit ; if not prev_was_match, then upkr_decode_bit(256) jr nc,.keep_offset ; if neither, keep old offset - inc c - call decode_length + inc c ; context_index to first "number" set for offsets decoding (257) + call decode_number dec de ; offset = upkr_decode_length(257) - 1; ld a,d or e @@ -126,14 +129,14 @@ unpack: ; ++write_ptr; ; } ; prev_was_match = 1; - ld c,low(257+64) ; context_index = 257+64 - call decode_length ; length = upkr_decode_length(257 + 64); + ld c,low(257 + NUMBER_BITS) ; context_index to second "number" set for lengths decoding + call decode_number ; length = upkr_decode_length(257 + 64); push de exx ld h,d ; DE = write_ptr ld l,e .offset+*: ld bc,0 - sbc hl,bc ; CF=0 from decode_length ; HL = write_ptr - offset + sbc hl,bc ; CF=0 from decode_number ; HL = write_ptr - offset pop bc ; BC = length ldir exx @@ -258,7 +261,7 @@ int upkr_decode_length(int context_index) { return length | (1 << bit_pos); } */ -decode_length: +decode_number: ; HL = upkr_state ; IX = upkr_data_ptr ; BC = probs+context_index @@ -291,7 +294,7 @@ decode_length: ENDIF probs: EQU ($+255) & -$100 ; probs array aligned to 256 -.real_c: EQU 1 + 255 + 1 + 2*32 + 2*32 ; real size of probs array +.real_c: EQU 1 + 255 + 1 + 2*NUMBER_BITS ; real size of probs array .c: EQU (.real_c + 1) & -2 ; padding to even size (required by init code) .e: EQU probs + .c From c3a9773e5c4afbaa4f1f3680f6350218ad3a17e6 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 20:14:13 +0200 Subject: [PATCH 15/24] z80_unpacker: optimisations: -1B in unpack implementation = 182B --- z80_unpacker/unpack.asm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 0de80a9..4939993 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -84,8 +84,8 @@ unpack: ; BC = probs (context_index 0), state HL = 0, A' = 0x80 (no source bits left in upkr_current_byte) ; ** main loop to decompress data -.decompress_data_reset_match: - ld d,0 ; prev_was_match = 0; + ; D = prev_was_match = uninitialised, literal is expected first => will reset D to "false" + ; values for false/true of prev_was_match are: false = high(probs), true = 1 + high(probs) .decompress_data: ld c,0 call decode_bit ; if(upkr_decode_bit(0)) @@ -102,16 +102,17 @@ unpack: ld (de),a ; *write_ptr++ = byte; inc de exx - jr .decompress_data_reset_match + ld d,b ; prev_was_match = false + jr .decompress_data ; * copy chunk of already decompressed data (match) .copy_chunk: + ld a,b inc b ; context_index = 256 ; if(prev_was_match || upkr_decode_bit(256)) { ; offset = upkr_decode_length(257) - 1; ; if (0 == offset) break; ; } - xor a cp d ; CF = prev_was_match call nc,decode_bit ; if not prev_was_match, then upkr_decode_bit(256) jr nc,.keep_offset ; if neither, keep old offset @@ -140,7 +141,7 @@ unpack: pop bc ; BC = length ldir exx - ld d,b ; prev_was_match = non-zero + ld d,b ; prev_was_match = true djnz .decompress_data ; adjust context_index back to 0..255 range, go to main loop /* From 6624940ed924d938a0fc2b8e6b17085df9d6ddc5 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 23:17:07 +0200 Subject: [PATCH 16/24] z80_unpacker: optimisations: -2B and -27T in decode_bit = 180B --- z80_unpacker/unpack.asm | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 4939993..7a26eb3 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -207,8 +207,9 @@ decode_bit: inc a ; ** adjust state push af + push bc + ld c,l ; C = (upkr_state & 255); (preserving the value) push af - push hl push af jr nc,.bit_is_0 neg ; A = -prob == (256-prob), CF=1 preserved @@ -216,22 +217,19 @@ decode_bit: ld d,0 ld e,a ; DE = state_scale ; prob || (256-prob) ld l,d ; H:L = (upkr_state>>8) : 0 - ld a,8 ; counter + ld b,8 ; counter .mulLoop: add hl,hl jr nc,.mul0 add hl,de .mul0: - dec a - jr nz,.mulLoop ; until HL = state_scale * (upkr_state>>8) + djnz .mulLoop ; until HL = state_scale * (upkr_state>>8), also BC becomes (upkr_state & 255) + add hl,bc ; HL = state_scale * (upkr_state >> 8) + (upkr_state & 255) pop af jr nc,.bit_is_0_2 - dec d ; D = 0xFF (DE = -prob) - add hl,de ; HL += -prob -.bit_is_0_2: ; HL = state_offset + state_scale * (upkr_state >> 8) - pop de - ld d,0 ; DE = (upkr_state & 255) - add hl,de ; HL = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255) ; new upkr_state + dec h + add hl,de ; HL += -prob (HL += (256 - prob) - 256) +.bit_is_0_2: ; HL = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255) ; new upkr_state ; *** adjust probs[context_index] pop af ; restore prob and bit ld e,a @@ -246,6 +244,7 @@ decode_bit: adc a,d ; A = -prob_offset + ((prob + 8) >> 4) neg add a,e ; A = prob_offset + prob - ((prob + 8) >> 4) + pop bc ld (bc),a ; update probs[context_index] pop af ; restore resulting CF = bit pop de From aa3fad4d80ecadbfd7a9b282cf052ca92b8d5064 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Wed, 14 Sep 2022 23:48:59 +0200 Subject: [PATCH 17/24] z80_unpacker: optimisations: -3B and ~-24T in decode_bit = 177B --- z80_unpacker/unpack.asm | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 7a26eb3..a779c23 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -210,7 +210,6 @@ decode_bit: push bc ld c,l ; C = (upkr_state & 255); (preserving the value) push af - push af jr nc,.bit_is_0 neg ; A = -prob == (256-prob), CF=1 preserved .bit_is_0: @@ -226,16 +225,14 @@ decode_bit: djnz .mulLoop ; until HL = state_scale * (upkr_state>>8), also BC becomes (upkr_state & 255) add hl,bc ; HL = state_scale * (upkr_state >> 8) + (upkr_state & 255) pop af + ld d,-16 ; D = -prob_offset (-16 0xF0 when bit = 0) jr nc,.bit_is_0_2 + ld d,b ; D = -prob_offset (0 when bit = 1) (also does fix following ADD) dec h add hl,de ; HL += -prob (HL += (256 - prob) - 256) .bit_is_0_2: ; HL = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255) ; new upkr_state ; *** adjust probs[context_index] - pop af ; restore prob and bit - ld e,a - jr c,.bit_is_1 - ld d,-16 ; 0xF0 -.bit_is_1: ; D:E = -prob_offset:prob, A = prob + ld e,a ; D:E = -prob_offset:prob, A = prob and $F8 rra rra From 3fa9e0fa12ff534c340e85c4ba7567435fbf834b Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Thu, 15 Sep 2022 01:25:57 +0200 Subject: [PATCH 18/24] z80_unpacker: optimisations: 0B, -13T in decode_bit (stays 177B) --- z80_unpacker/unpack.asm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index a779c23..6dc9097 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -12,7 +12,7 @@ ;; upkr.unpack ;; IN: IX = packed data, DE' (shadow DE) = destination ;; OUT: IX = after packed data -;; modifies: all registers except IY, requires 14 bytes of stack space +;; modifies: all registers except IY, requires 10 bytes of stack space ;; OPT push reset --syntax=abf @@ -206,7 +206,6 @@ decode_bit: cp l ; CF = bit = prob-1 < (upkr_state & 255) <=> prob <= (upkr_state & 255) inc a ; ** adjust state - push af push bc ld c,l ; C = (upkr_state & 255); (preserving the value) push af @@ -243,7 +242,8 @@ decode_bit: add a,e ; A = prob_offset + prob - ((prob + 8) >> 4) pop bc ld (bc),a ; update probs[context_index] - pop af ; restore resulting CF = bit + add a,d ; bit=0: A = 23..249, D = 240 -> CF=1 || bit=1: D=0 -> CF=0 + ccf ; resulting CF = bit restored pop de ret From 9211544cb962a062ab66c26763456f39648f6d38 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Thu, 15 Sep 2022 18:37:06 +0200 Subject: [PATCH 19/24] z80_unpacker: add resulting snapshot file to example --- z80_unpacker/.gitignore | 1 - z80_unpacker/example/example.sna | Bin 0 -> 49179 bytes 2 files changed, 1 deletion(-) create mode 100644 z80_unpacker/example/example.sna diff --git a/z80_unpacker/.gitignore b/z80_unpacker/.gitignore index 01edc46..298ab93 100644 --- a/z80_unpacker/.gitignore +++ b/z80_unpacker/.gitignore @@ -1,4 +1,3 @@ *.bin *.tap -*.sna *.lst diff --git a/z80_unpacker/example/example.sna b/z80_unpacker/example/example.sna new file mode 100644 index 0000000000000000000000000000000000000000..a1f9f56b4e1b2fcf97dd33ac8ef27758dea43e6a GIT binary patch literal 49179 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 Date: Fri, 16 Sep 2022 02:50:23 +0200 Subject: [PATCH 20/24] z80_unpacker: add performance variant of depacker --- z80_unpacker/unpack.asm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index 6dc9097..c788a70 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -15,6 +15,8 @@ ;; modifies: all registers except IY, requires 10 bytes of stack space ;; +; DEFINE UPKR_UNPACK_SPEED ; uncomment to get larger but faster unpack routine + OPT push reset --syntax=abf MODULE upkr @@ -215,6 +217,10 @@ decode_bit: ld d,0 ld e,a ; DE = state_scale ; prob || (256-prob) ld l,d ; H:L = (upkr_state>>8) : 0 + + IFNDEF UPKR_UNPACK_SPEED + ;; looped MUL for minimum unpack size + ld b,8 ; counter .mulLoop: add hl,hl @@ -222,6 +228,19 @@ decode_bit: add hl,de .mul0: djnz .mulLoop ; until HL = state_scale * (upkr_state>>8), also BC becomes (upkr_state & 255) + ELSE + ;;; unrolled MUL for better performance, +25 bytes unpack size + + ld b,d + DUP 8 + add hl,hl + jr nc,000_f + add hl,de +000: + EDUP + + ENDIF + add hl,bc ; HL = state_scale * (upkr_state >> 8) + (upkr_state & 255) pop af ld d,-16 ; D = -prob_offset (-16 0xF0 when bit = 0) From b13fa05413a11686604240753f154b62bd5259ff Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Sun, 18 Sep 2022 00:23:14 +0200 Subject: [PATCH 21/24] z80_unpacker: add backward variant of unpacker + example extended --- z80_unpacker/example/example.asm | 77 +++++++++++++++--- z80_unpacker/example/example.sna | Bin 49179 -> 49179 bytes .../Grongy - ZX Spectrum (2022).scr.upk | Bin 0 -> 1760 bytes .../Schafft - Poison (2017).scr.upk | Bin 0 -> 1400 bytes .../diver - Back to Bjork (2015).scr.upk | Bin 0 -> 2869 bytes ... (Forever 2014 Olympic Edition, 1).scr.upk | Bin 0 -> 4294 bytes z80_unpacker/unpack.asm | 24 ++++-- 7 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 z80_unpacker/example/screens.reversed/Grongy - ZX Spectrum (2022).scr.upk create mode 100644 z80_unpacker/example/screens.reversed/Schafft - Poison (2017).scr.upk create mode 100644 z80_unpacker/example/screens.reversed/diver - Back to Bjork (2015).scr.upk create mode 100644 z80_unpacker/example/screens.reversed/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr.upk diff --git a/z80_unpacker/example/example.asm b/z80_unpacker/example/example.asm index 80fd790..e3e6570 100644 --- a/z80_unpacker/example/example.asm +++ b/z80_unpacker/example/example.asm @@ -3,7 +3,8 @@ DEVICE ZXSPECTRUM48,$8FFF ORG $9000 -compressed_scr_files: ; border color byte + upkr-packed .scr file + ;; forward example data +compressed_scr_files.fwd: ; border color byte + upkr-packed .scr file DB 1 INCBIN "screens/Grongy - ZX Spectrum (2022).scr.upk" DB 7 @@ -13,37 +14,87 @@ compressed_scr_files: ; border color byte + upkr-packed .scr file DB 6 INCBIN "screens/diver - Back to Bjork (2015).scr.upk" .e: + ;; backward example data (unpacker goes from the end of the data!) +compressed_scr_files.rwd.e: EQU $-1 ; the final IX will point one byte ahead of "$" here + INCBIN "screens.reversed/diver - Back to Bjork (2015).scr.upk" + DB 6 + INCBIN "screens.reversed/diver - Mercenary 4. The Heaven's Devil (2014) (Forever 2014 Olympic Edition, 1).scr.upk" + DB 0 + INCBIN "screens.reversed/Schafft - Poison (2017).scr.upk" + DB 7 + INCBIN "screens.reversed/Grongy - ZX Spectrum (2022).scr.upk" +compressed_scr_files.rwd: ; border color byte + upkr-packed .scr file (backward) + DB 1 start: di ; OPT --zxnext -; nextreg 7,3 ; ZX Next: switch to 28Mhz - ld ix,compressed_scr_files -.slideshow_loop +; nextreg 7,3 ; ZX Next: switch to 28Mhz + + ;;; FORWARD packed/unpacked data demo + ld ix,compressed_scr_files.fwd +.slideshow_loop.fwd: ; set BORDER for next image - ldi a,(ix) ; fake: ld a,(ix) : inc ix + ld a,(ix) + inc ix out (254),a ; call unpack of next image directly into VRAM - ld de,$4000 ; target VRAM + ld de,$4000 ; target VRAM exx ; IX = packed data, DE' = destination ($4000) ; returned IX will point right after the packed data - call upkr.unpack + call fwd.upkr.unpack ; do some busy loop with CPU to delay between images + call delay + ; check if all images were displayed, loop around from first one then + ld a,ixl + cp low compressed_scr_files.fwd.e + jr nz,.slideshow_loop.fwd + + ;;; BACKWARD packed/unpacked data demo + ld ix,compressed_scr_files.rwd +.slideshow_loop.rwd: + ; set BORDER for next image + ld a,(ix) + dec ix + out (254),a + ; call unpack of next image directly into VRAM + ld de,$5AFF ; target VRAM + exx + ; IX = packed data, DE' = destination + ; returned IX will point right ahead of the packed data + call rwd.upkr.unpack + ; do some busy loop with CPU to delay between images + call delay + ; check if all images were displayed, loop around from first one then + ld a,ixl + cp low compressed_scr_files.rwd.e + jr nz,.slideshow_loop.rwd + + jr start + +delay: ld bc,$AA00 .delay: .8 ex (sp),ix dec c jr nz,.delay djnz .delay - ; check if all images were displayed, loop around from first one then - ld a,ixl - cp low compressed_scr_files.e - jr z,start - jr .slideshow_loop + ret ; include the depacker library, optionally putting probs array buffer near end of RAM DEFINE UPKR_PROBS_ORIGIN $FA00 ; if not defined, array will be put after unpack code - INCLUDE "../unpack.asm" + + MODULE fwd + INCLUDE "../unpack.asm" + ENDMODULE + + MODULE rwd + DEFINE BACKWARDS_UNPACK ; defined to build backwards unpack + ; initial IX points at last byte of compressed data + ; initial DE' points at last byte of unpacked data + + INCLUDE "../unpack.asm" + ENDMODULE SAVESNA "example.sna",start diff --git a/z80_unpacker/example/example.sna b/z80_unpacker/example/example.sna index a1f9f56b4e1b2fcf97dd33ac8ef27758dea43e6a..78df3d8a2cf9da82d8237271b0d006240e53706b 100644 GIT binary patch delta 10753 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 diff --git a/z80_unpacker/example/screens.reversed/Grongy - ZX Spectrum (2022).scr.upk b/z80_unpacker/example/screens.reversed/Grongy - ZX Spectrum (2022).scr.upk new file mode 100644 index 0000000000000000000000000000000000000000..0e8777ca71bea9bd53ddb8b703f453a8566c2f0f GIT binary patch literal 1760 zcmV<61|RuAL&E+wP`Njy?RTOVMy1>6$+CyhG3WJ)#NizUu%54!cWJ;$uhuuaiSF%W zL|};2iLmOA*Ip4R+*yZItYx*iUMl*y>CS2Si-66DbKtU~ImPqSU+sR98aKsus{ni$ z%yt*Jg?TUMyc#G{wW)0@a)yX=erljZx1y54=_W)buAlBTrW{~zKl;aN_OGW-MH0%I zhYUp`HlKWFJIf#NS7s3#u}KcG*o((T`x;t3`%xq=J`_ZE=d2vXu+2o_l_Cvj(=0AZ znKowV-UF=+LVFVVG<&pC_=0#WKNAqOE_|a9IanPay!n*k^MExYpGF>p;l^tCybOT4c=p1t=AG0JX8c4v@v776OHKF$KQ6R|glS z6oYh_-QRZ_OJY|=M=}y`GXDAgdWZyow9>S&CW(Z;O+DT4Yu#bd=VPKI)1p$cvxhdu za`^>6$vwJNu`64`4CdGRzt!T}9aKcH#Ii@5BzwIxlN5S6acdV#(`Q!A74VvB%Po4{ zQi$T!!6nu2TZg$-5Bb16=2w_n#M(AjOdWzX>jn;f(pfGTC)(_~!%+t*7L5Q{qeTAm znI^qYG2AF=SC^*&!40W68tDX#o5Ir}MGP3VaHZTcC*t&5B`D83elxgQ7T@WSIuulE0Ai+KSg8$`pQT%|u2LE3Ggp5QR-hOj z0Tx1}p?4;wX?<&$i!ig#EMzA($FokzlG51J^NE5$Fj+jIXvO5PN`e&>O zB-nqsPJA?Dvx&R3k)ka#Yezy+n3BZ~K#}$xtD!C(TsQ&Jok=)`awrDw8j9bd38o67H& zT3cFidN(G_k=>u(> z*eU%X-150r2ssouZRKS^w0;27UoAV#*0d6-1P?}haiTs3Dv;_V!EnHJF`aJ@6IV{O`*?XxawGbR~>= z4IOxT6K3P4|Gd-?HBzI!`7VZOKUqNlLBF)H@?a&<0%X(J)*I2Pxj8Fkjyhc>8oo_A znq+F?RxDmblNtDxdQrsGIBLB4`k4%$FLsowQ3{tZPZhvsRsAh=HMeT;$t_sq6=K#2 z5-v6o$zjy-O{WN8(G1H>^!CjJ#r-FJ`F^C#n4vMLmh$LZXZvPV?~Nf3p^bTSR|xC2#4|`U5*c4 zWP2I;A$+U$&T>-3w4m=kcx;&91&_7Rbquy?=HWR5%HKY%HglT7xa8+MX*NJS6&=87 z$Vcp07ux#AdZj|-E93Rmpp9%oLps)@%E(&|u10x!U57Jd`G^k^df#Y^Qgyo!RQO3~ zKF-{KNf5MSs{r`k%|=bemqDM8*%>w-u?$SX_P)3$36GlHSQPodu5~d0KP>gQ^ zbC5*Zf3Ef(iXfZu@U1ZCDu&rRNfa)l*8TAQOx7QPamgYth%_#iV?%DAbWr~OV?7t+)3mM@&YSjrOb!-plQ}<0J^Z1tk+%ht@g?Rc)y@eNM~^BUR%u zg%SgAZGJ=ExC4X=I3=C&{nG?G^WM>{thx{PiK{2nXR~?whT(b@xa!+qP}tF>a|{CI CiDSY5 literal 0 HcmV?d00001 diff --git a/z80_unpacker/example/screens.reversed/Schafft - Poison (2017).scr.upk b/z80_unpacker/example/screens.reversed/Schafft - Poison (2017).scr.upk new file mode 100644 index 0000000000000000000000000000000000000000..32f540144e1f9afba3a593429cbbbfc19b19a5f8 GIT binary patch literal 1400 zcmV-;1&8`T=q>8Yw7aFTw3lEQq`9S}%^d3TZ*uZ*ow6A+9w^ntD>58yG&LZ4ikwpi z{~Pihe1q{k})!dLC5{Ji}e7g3OA95IQVIeTVor=wqNY`f_ zljmONA^j0Z^mh0k%ikjQbKLJSO0w4a@@U})CwRClR!JqSWSD#2dd_(M3zPi)XyXpU z_D#1j#!xBir{j4mI8Ilu3dco?2is*rx;jhFJ6j=>4$d0c;z$TN8MOGgH*(I$lN5k& zp)@1SnYh91`2f~h11ebbVpjrJOMauuY;%(m4MRhAgoO-TeNsPXS`28HdQ?D>#Fj9I zmUYei1K9NG?n>MqrR~d$fFOCR!1C&IfWVU2W$ESupkpe7<{rbFOS+)#<tC;1 zAm$awFn0~in&i9&lAff#S0P1EfvY5youldw9-pZ_hquYG0x@KMp?|rB zAMKAijt7VF)%?!snxz@edgRrLFgfr?lcA+-r8s_6aJ)mV&Pgg>MM4Yg*meHuF+fU= zt_HQ_3w`^;pv73$Xqi(_$>t$4jO^mS+0&Z0n`pBh%22P?lX>UYwop(=zW+8g zoD5#Fn0>qf9)iRJc?>A9)%QxnT6nkZ*F_Yp13GFrh@mjsJn5LMH)w(UwurZg`Bn*Y zlu7uXL=inmqXEs}6$ljig}~b;Qh(PkWJ}o;VZzYWaN=K2X#w* zyqG=y+8AGqViHRidL{;TO~7stOplJ(^hhdf308+yNgCSWaSF1ui-NT&5{QMAEI9(! zU(yAkB#Vv}%3eE6TL!QqCFwtm{7iQLQDhOfdyZki671(XR6@y7dv& G^ABeJ>${@> literal 0 HcmV?d00001 diff --git a/z80_unpacker/example/screens.reversed/diver - Back to Bjork (2015).scr.upk b/z80_unpacker/example/screens.reversed/diver - Back to Bjork (2015).scr.upk new file mode 100644 index 0000000000000000000000000000000000000000..423885a734913b562da69b2de445e22139268d87 GIT binary patch literal 2869 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 some bits + stop bit still available ; CF=1 (by stop bit) ld a,(ix) - inc ix ; upkr_current_byte = *upkr_data_ptr++; + IFNDEF BACKWARDS_UNPACK : inc ix : ELSE : dec ix : ENDIF ; upkr_current_byte = *upkr_data_ptr++; adc a,a ; CF=data, b0=1 as new stop bit .has_bit: adc hl,hl ; upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7); @@ -306,12 +314,12 @@ decode_number: ; reserve space for probs array without emitting any machine code (using only EQU) IFDEF UPKR_PROBS_ORIGIN ; if specific address is defined by user, move probs array there - ORG UPKR_PROBS_ORIGIN +probs: EQU ((UPKR_PROBS_ORIGIN) + 255) & -$100 ; probs array aligned to 256 + ELSE +probs: EQU ($ + 255) & -$100 ; probs array aligned to 256 ENDIF - -probs: EQU ($+255) & -$100 ; probs array aligned to 256 -.real_c: EQU 1 + 255 + 1 + 2*NUMBER_BITS ; real size of probs array -.c: EQU (.real_c + 1) & -2 ; padding to even size (required by init code) +.real_c: EQU 1 + 255 + 1 + 2*NUMBER_BITS ; real size of probs array +.c: EQU (.real_c + 1) & -2 ; padding to even size (required by init code) .e: EQU probs + .c DISPLAY "upkr.unpack probs array placed at: ",/A,probs,",\tsize: ",/A,probs.c From d4bce4bf7cb1095a797ca5853c0c01fcba4f603f Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Sun, 18 Sep 2022 22:54:10 +0200 Subject: [PATCH 22/24] z80_unpacker: optimisation: -3B and ~-10T in decode_bit = 174B unpack zx48.rom is now ~22.6s (from 23.0s) (performance version is now 199 bytes, zx48.rom unpack 19.4s -> 19.0s) --- z80_unpacker/example/example.sna | Bin 49179 -> 49179 bytes z80_unpacker/unpack.asm | 31 +++++++++++++++---------------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/z80_unpacker/example/example.sna b/z80_unpacker/example/example.sna index 78df3d8a2cf9da82d8237271b0d006240e53706b..515f5d68690562b02a92e501cbcea636569d53df 100644 GIT binary patch delta 175 zcmbQ;z&yKwd4u>u#tV}r7KZU{eJEYE`NZ4c^oM*3XSYtSS!ge8z$7XeFaPY1yuAFm z50l@z9As*md~uD(w zP|{@GMM_Mnk0vi%C}n`fyo)CV|JO@!f=!b;Ee)bAKG~lJGA(|d+^G0Ve51i5mdPdi GmjeKk{!YRG delta 167 zcmbQ;z&yKwd4u>u#>7KZWdcqm=9`NZ4c^oM*3XLn4lS!gdN_Q8NTKvgpS*$;Uj z=z8nYe2}The)74620BWQEChJYDm^+aXz;W0rjYc_0Ew3sEW55KJu=|oIh*kaVwS`s upjjD{wH7HcX*`;|aG{h2b|WvI6#QQ=!3j1{>a;YF-rT$Bq0Hox{mTKZu}H!I diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index b7e5120..b1bcbce 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -250,27 +250,26 @@ decode_bit: ENDIF add hl,bc ; HL = state_scale * (upkr_state >> 8) + (upkr_state & 255) - pop af - ld d,-16 ; D = -prob_offset (-16 0xF0 when bit = 0) + pop af ; restore prob and CF=bit jr nc,.bit_is_0_2 - ld d,b ; D = -prob_offset (0 when bit = 1) (also does fix following ADD) - dec h - add hl,de ; HL += -prob (HL += (256 - prob) - 256) -.bit_is_0_2: ; HL = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255) ; new upkr_state + dec d ; DE = -prob (also D = bit ? $FF : $00) + add hl,de ; HL += -prob + ; ^ this always preserves CF=1, because (state>>8) >= 128, state_scale: 7..250, prob: 7..250, + ; so 7*128 > 250 and thus edge case `ADD hl=(7*128+0),de=(-250)` => CF=1 +.bit_is_0_2: ; *** adjust probs[context_index] - ld e,a ; D:E = -prob_offset:prob, A = prob - and $F8 + ld e,a ; preserve prob + rra ; + (bit<<4) ; part of -prob_offset, needs another -16 + and $FC ; clear/keep correct bits to get desired (prob>>4) + extras, CF=0 rra rra - rra - rra - adc a,d ; A = -prob_offset + ((prob + 8) >> 4) - neg - add a,e ; A = prob_offset + prob - ((prob + 8) >> 4) + rra ; A = (bit<<4) + (prob>>4), CF=(prob & 8) + adc a,-16 ; A = (bit<<4) - 16 + ((prob + 8)>>4) ; -prob_offset = (bit<<4) - 16 + sub e ; A = (bit<<4) - 16 + ((prob + 8)>>4) - prob ; = ((prob + 8)>>4) - prob_offset - prob + neg ; A = prob_offset + prob - ((prob + 8)>>4) pop bc - ld (bc),a ; update probs[context_index] - add a,d ; bit=0: A = 23..249, D = 240 -> CF=1 || bit=1: D=0 -> CF=0 - ccf ; resulting CF = bit restored + ld (bc),a ; probs[context_index] = prob_offset + prob - ((prob + 8) >> 4); + add a,d ; restore CF = bit (D = bit ? $FF : $00 && A > 0) pop de ret From 165f593a11f176c544cfda0a6dcd9614d47671d1 Mon Sep 17 00:00:00 2001 From: "Peter Helcmanovsky (Ped)" Date: Sun, 18 Sep 2022 23:04:37 +0200 Subject: [PATCH 23/24] z80_unpacker: (codestyle) whitespace + temporary label rename --- z80_unpacker/unpack.asm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/z80_unpacker/unpack.asm b/z80_unpacker/unpack.asm index b1bcbce..26e47b7 100644 --- a/z80_unpacker/unpack.asm +++ b/z80_unpacker/unpack.asm @@ -227,8 +227,8 @@ decode_bit: ld l,d ; H:L = (upkr_state>>8) : 0 IFNDEF UPKR_UNPACK_SPEED - ;; looped MUL for minimum unpack size + ;; looped MUL for minimum unpack size ld b,8 ; counter .mulLoop: add hl,hl @@ -236,15 +236,16 @@ decode_bit: add hl,de .mul0: djnz .mulLoop ; until HL = state_scale * (upkr_state>>8), also BC becomes (upkr_state & 255) - ELSE - ;;; unrolled MUL for better performance, +25 bytes unpack size + ELSE + + ;;; unrolled MUL for better performance, +25 bytes unpack size ld b,d DUP 8 add hl,hl - jr nc,000_f + jr nc,0_f add hl,de -000: +0: EDUP ENDIF From c8924456aa81b5bfd0ca8d2a7b62592647cadc84 Mon Sep 17 00:00:00 2001 From: Dennis Ranke Date: Sun, 18 Sep 2022 23:38:41 +0200 Subject: [PATCH 24/24] -r reverses both input and output --- src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b809969..4c4faa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ fn main() -> Result<()> { let mut pb = pbr::ProgressBar::new(data.len() as u64); pb.set_units(pbr::Units::Bytes); - let packed_data = upkr::pack( + let mut packed_data = upkr::pack( &data, level, use_bitstream, @@ -33,6 +33,10 @@ fn main() -> Result<()> { ); pb.finish(); + if reverse { + packed_data.reverse(); + } + println!( "Compressed {} bytes to {} bytes ({}%)", data.len(), @@ -50,6 +54,9 @@ fn main() -> Result<()> { let mut data = vec![]; File::open(infile)?.read_to_end(&mut data)?; + if reverse { + data.reverse(); + } let mut unpacked_data = upkr::unpack(&data, use_bitstream); if reverse { unpacked_data.reverse();