1 Commits

Author SHA1 Message Date
7d40bb8123 hacked parity context version for arm code 2021-12-30 11:49:59 +01:00
29 changed files with 45 additions and 788 deletions

View File

@@ -1,5 +0,0 @@
unpack
unpack_bitstream
unpack_debug
*.upk

View File

@@ -1,10 +0,0 @@
all: unpack unpack_bitstream
unpack: main.c unpack.c
cc -O2 -o unpack main.c unpack.c
unpack_bitstream: main.c unpack.c
cc -O2 -D UPKR_BITSTREAM -o unpack_bitstream main.c unpack.c
unpack_debug: main.c unpack.c
cc -g -o unpack_debug main.c unpack.c

View File

@@ -1,25 +0,0 @@
#include <stdio.h>
#include <stdlib.h>
int upkr_unpack(void* destination, void* compressed_data);
int main(int argn, char** argv) {
void* input_buffer = malloc(1024*1024);
void* output_buffer = malloc(1024*1024);
FILE* in_file = fopen(argv[1], "rb");
int in_size = fread(input_buffer, 1, 1024*1024, in_file);
fclose(in_file);
printf("Compressed size: %d\n", in_size);
int out_size = upkr_unpack(output_buffer, input_buffer);
printf("Uncompressed size: %d\n", out_size);
FILE* out_file = fopen(argv[2], "wb");
fwrite(output_buffer, 1, out_size, out_file);
fclose(out_file);
return 0;
}

View File

@@ -1,4 +0,0 @@
a very simple unpacker in c, as a reference for people wanting to implement their own unpacker.
absolutely not production ready, it makes no effort to ensure the output buffer can actually
hold the uncompressed data.
!!! Never run on untrusted input !!!

View File

@@ -1,98 +0,0 @@
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned long u32;
u8* upkr_data_ptr;
u8 upkr_probs[1 + 255 + 1 + 2*32 + 2*32];
#ifdef UPKR_BITSTREAM
u16 upkr_state;
u8 upkr_current_byte;
int upkr_bits_left;
#else
u32 upkr_state;
#endif
int upkr_decode_bit(int context_index) {
#ifdef UPKR_BITSTREAM
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;
}
#else
while(upkr_state < 4096) {
upkr_state = (upkr_state << 8) | *upkr_data_ptr++;
}
#endif
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;
}
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);
}
int upkr_unpack(void* destination, void* compressed_data) {
upkr_data_ptr = (u8*)compressed_data;
upkr_state = 0;
#ifdef UPKR_BITSTREAM
upkr_bits_left = 0;
#endif
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;
}

View File

@@ -1,50 +0,0 @@
#!/bin/env ruby
configs = [
[:master, '-b'],
[:z80, '-b'],
[:z80, ['-b', '-r']],
['old-prob-update', '-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'].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
else
config_results << 'ERR'
end
print_results(configs, short_names, results)
end
end

View File

@@ -1,8 +1,8 @@
use crate::rans::{ONE_PROB, PROB_BITS};
use crate::rans::{PROB_BITS, ONE_PROB};
const INIT_PROB: u16 = 1 << (PROB_BITS - 1);
const UPDATE_RATE: i32 = 4;
const UPDATE_ADD: i32 = 8;
const UPDATE_RATE: u32 = 4;
const UPDATE_ADD: u32 = 8;
#[derive(Clone)]
pub struct ContextState {
@@ -33,13 +33,10 @@ impl<'a> Context<'a> {
pub fn update(&mut self, bit: bool) {
let old = self.state.contexts[self.index];
let offset = if !bit {
ONE_PROB as i32 >> UPDATE_RATE
self.state.contexts[self.index] = if bit {
old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8
} else {
0
old - ((old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8
};
self.state.contexts[self.index] =
(offset + old as i32 - ((old as i32 + UPDATE_ADD) >> UPDATE_RATE)) as u8;
}
}

View File

@@ -21,12 +21,3 @@ pub fn pack(
parsing_packer::pack(data, level, use_bitstream, progress_callback)
}
}
pub fn compressed_size(mut data: &[u8]) -> f32 {
let mut state = 0;
while state < 4096 {
state = (state << 8) | data[0] as u32;
data = &data[1..];
}
data.len() as f32 + (state as f32).log2() / 8.
}

View File

@@ -9,29 +9,32 @@ pub enum Op {
impl Op {
pub fn encode(&self, coder: &mut dyn EntropyCoder, state: &mut CoderState) {
let base_context = 256 * (state.pos & 3);
match self {
&Op::Literal(lit) => {
encode_bit(coder, state, 0, false);
encode_bit(coder, state, base_context, false);
let mut context_index = 1;
for i in (0..8).rev() {
let bit = (lit >> i) & 1 != 0;
encode_bit(coder, state, context_index, bit);
encode_bit(coder, state, base_context + context_index, bit);
context_index = (context_index << 1) | bit as usize;
}
state.pos += 1;
state.prev_was_match = false;
}
&Op::Match { offset, len } => {
encode_bit(coder, state, 0, true);
encode_bit(coder, state, base_context, true);
if !state.prev_was_match {
encode_bit(coder, state, 256, offset != state.last_offset);
encode_bit(coder, state, 1024, offset != state.last_offset);
} else {
assert!(offset != state.last_offset);
}
if offset != state.last_offset {
encode_length(coder, state, 257, offset + 1);
encode_length(coder, state, 1025, offset + 1);
state.last_offset = offset;
}
encode_length(coder, state, 257 + 64, len);
encode_length(coder, state, 1025 + 64, len);
state.pos += len as usize;
state.prev_was_match = true;
}
}
@@ -39,11 +42,11 @@ impl Op {
}
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState) {
encode_bit(coder, state, 0, true);
encode_bit(coder, state, 256 * (state.pos & 3), true);
if !state.prev_was_match {
encode_bit(coder, state, 256, true);
encode_bit(coder, state, 1024, true);
}
encode_length(coder, state, 257, 1);
encode_length(coder, state, 1025, 1);
}
fn encode_bit(
@@ -77,14 +80,16 @@ fn encode_length(
pub struct CoderState {
contexts: ContextState,
last_offset: u32,
pos: usize,
prev_was_match: bool,
}
impl CoderState {
pub fn new() -> CoderState {
CoderState {
contexts: ContextState::new(1 + 255 + 1 + 64 + 64),
contexts: ContextState::new((1 + 255) * 4 + 1 + 64 + 64),
last_offset: 0,
pos: 0,
prev_was_match: false,
}
}
@@ -96,7 +101,7 @@ impl CoderState {
pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
let mut decoder = RansDecoder::new(packed_data, use_bitstream);
let mut contexts = ContextState::new(1 + 255 + 1 + 64 + 64);
let mut contexts = ContextState::new((1 + 255) * 4 + 1 + 64 + 64);
let mut result = vec![];
let mut offset = 0;
let mut prev_was_match = false;
@@ -119,14 +124,15 @@ pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
}
loop {
if decoder.decode_with_context(&mut contexts.context_mut(0)) {
if prev_was_match || decoder.decode_with_context(&mut contexts.context_mut(256)) {
offset = decode_length(&mut decoder, &mut contexts, 257) - 1;
let base_context = 256 * (result.len() & 3);
if decoder.decode_with_context(&mut contexts.context_mut(base_context)) {
if prev_was_match || decoder.decode_with_context(&mut contexts.context_mut(1024)) {
offset = decode_length(&mut decoder, &mut contexts, 1025) - 1;
if offset == 0 {
break;
}
}
let length = decode_length(&mut decoder, &mut contexts, 257 + 64);
let length = decode_length(&mut decoder, &mut contexts, 1025 + 64);
for _ in 0..length {
result.push(result[result.len() - offset]);
}
@@ -135,7 +141,7 @@ pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
let mut context_index = 1;
let mut byte = 0;
for i in (0..8).rev() {
let bit = decoder.decode_with_context(&mut contexts.context_mut(context_index));
let bit = decoder.decode_with_context(&mut contexts.context_mut(base_context + context_index));
context_index = (context_index << 1) | bit as usize;
byte |= (bit as u8) << i;
}

View File

@@ -10,20 +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::<PathBuf, bool>(|s| Ok(s.into()))?;
let outfile = args.free_from_os_str::<PathBuf, bool>(|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);
let mut packed_data = upkr::pack(
let packed_data = upkr::pack(
&data,
level,
use_bitstream,
@@ -33,10 +29,6 @@ fn main() -> Result<()> {
);
pb.finish();
if reverse {
packed_data.reverse();
}
println!(
"Compressed {} bytes to {} bytes ({}%)",
data.len(),
@@ -47,21 +39,14 @@ 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::<PathBuf, bool>(|s| Ok(s.into()))?;
let outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
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();
}
File::create(outfile)?.write_all(&unpacked_data)?;
let packed_data = upkr::unpack(&data, use_bitstream);
File::create(outfile)?.write_all(&packed_data)?;
}
Some(other) => {
bail!("Unknown subcommand '{}'", other);
@@ -73,10 +58,7 @@ fn main() -> Result<()> {
fn print_help() {
eprintln!("Usage:");
eprintln!(" upkr pack [-b] [-l level(0-9)] <infile> <outfile>");
eprintln!(" upkr unpack [-b] <infile> <outfile>");
eprintln!();
eprintln!(" -b, --bitstream bitstream mode");
eprintln!(" -l, --level N compression level 0-9");
eprintln!(" upkr pack [-l level(0-9)] <infile> <outfile>");
eprintln!(" upkr unpack <infile> <outfile>");
std::process::exit(1);
}

View File

@@ -38,15 +38,15 @@ impl RansCoder {
let mut state = 1 << l_bits;
let mut byte = 0u8;
let mut bit = 0;
let mut bit = 8;
let mut flush_state: Box<dyn FnMut(&mut u32)> = if self.use_bitstream {
Box::new(|state: &mut u32| {
bit -= 1;
byte |= ((*state & 1) as u8) << bit;
bit += 1;
if bit == 8 {
if bit == 0 {
buffer.push(byte);
byte = 0;
bit = 0;
bit = 8;
}
*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 >> 7) as u32;
self.byte <<= 1;
self.state = (self.state << 1) | (self.byte & 1) 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 {
(prob, ONE_PROB - prob)
} else {
(0, prob)
} else {
(prob, ONE_PROB - prob)
};
self.state = prob * (self.state >> PROB_BITS) + (self.state & PROB_MASK) - start;

View File

@@ -1,3 +0,0 @@
*.bin
*.tap
*.lst

View File

@@ -1,11 +0,0 @@
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

View File

@@ -1,100 +0,0 @@
;; Example using upkr depacker for screens slideshow
OPT --syntax=abf
DEVICE ZXSPECTRUM48,$8FFF
ORG $9000
;; 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
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:
;; 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
;;; FORWARD packed/unpacked data demo
ld ix,compressed_scr_files.fwd
.slideshow_loop.fwd:
; set BORDER for next image
ld a,(ix)
inc ix
out (254),a
; call unpack of next image directly into VRAM
ld de,$4000 ; target VRAM
exx
; IX = packed data, DE' = destination ($4000)
; returned IX will point right after the packed data
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
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
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

Binary file not shown.

View File

@@ -1,32 +0,0 @@
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
Backward direction unpacker added as compile-time option, see example for both forward/backward
depacker in action.
The packed/unpacked data-overlap has to be tested per-case, in worst case the packed data
may need even more than 7 bytes to unpack final byte, but usually 1-4 bytes may suffice.
TODO:
- build bigger corpus of test data to benchmark future changes in algorithm/format (example and zx48.rom was used to do initial tests)
- maybe try to beat double-loop `decode_number` with different encoding format
- (@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)
- (@exoticorn) add to packer output with possible packed/unpacked region overlap
DONE:
* review non-bitstream variant, if it's feasible to try to implement it with Z80
- Ped7g: IMHO nope, the 12b x 8b MUL code would probably quickly cancel any gains from the simpler state update
* review first implementation to identify weak spots where the implementation can be shorter+faster
with acceptable small changes to the format
- Ped7g: the decode_bit settled down and now doesn't feel so confused and redundant, the code seems pretty on point to me, no obvious simplification from format change
- Ped7g: the decode_number double-loop is surprisingly resilient, especially in terms of code size I failed to beat it, speed wise only negligible gains

View File

@@ -1,381 +0,0 @@
;; 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 (320 bytes),
;; otherwise it will be positioned after the unpacker code (256 aligned)
;;
;; public API:
;;
;; upkr.unpack
;; IN: IX = packed data, DE' (shadow DE) = destination
;; OUT: IX = after packed data
;; modifies: all registers except IY, requires 10 bytes of stack space
;;
; DEFINE BACKWARDS_UNPACK ; uncomment to build backwards depacker (write_ptr--, upkr_data_ptr--)
; initial IX points at last byte of compressed data
; initial DE' points at last byte of unpacked data
; DEFINE UPKR_UNPACK_SPEED ; uncomment to get larger but faster unpack routine
; code size hint: if you put probs array just ahead of BASIC entry point, you will get BC
; initialised to probs.e by BASIC `USR` command and you can remove it from unpack init (-3B)
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];
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, 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
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
; 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))
jr c,.copy_chunk
; * extract byte from compressed data (literal)
inc c ; C = byte = 1 (and also context_index)
.decode_byte:
call decode_bit ; bit = upkr_decode_bit(byte);
rl c ; byte = (byte << 1) + bit;
jr nc,.decode_byte ; while(byte < 256)
ld a,c
exx
ld (de),a ; *write_ptr++ = byte;
IFNDEF BACKWARDS_UNPACK : inc de : ELSE : dec de : ENDIF
exx
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;
; }
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
call decode_number ; context_index is already 257-1 as needed by decode_number
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 + NUMBER_BITS - 1) ; context_index to second "number" set for lengths decoding
call decode_number ; length = upkr_decode_length(257 + 64);
push de
exx
IFNDEF BACKWARDS_UNPACK
; forward unpack (write_ptr++, upkr_data_ptr++)
ld h,d ; DE = write_ptr
ld l,e
.offset+*: ld bc,0
sbc hl,bc ; CF=0 from decode_number ; HL = write_ptr - offset
pop bc ; BC = length
ldir
ELSE
; backward unpack (write_ptr--, upkr_data_ptr--)
.offset+*: ld hl,0
add hl,de ; HL = write_ptr + offset
pop bc ; BC = length
lddr
ENDIF
exx
ld d,b ; prev_was_match = true
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;
}
*/
inc_c_decode_bit:
; ++low(context_index) before decode_bit (to get -1B by two calls in decode_number)
inc c
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)
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);
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 bc
ld c,l ; C = (upkr_state & 255); (preserving the value)
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
IFNDEF UPKR_UNPACK_SPEED
;; looped MUL for minimum unpack size
ld b,8 ; counter
.mulLoop:
add hl,hl
jr nc,.mul0
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,0_f
add hl,de
0:
EDUP
ENDIF
add hl,bc ; HL = state_scale * (upkr_state >> 8) + (upkr_state & 255)
pop af ; restore prob and CF=bit
jr nc,.bit_is_0_2
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]
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 ; A = (bit<<4) + (prob>>4), CF=(prob & 8)
adc a,-16 ; A = (bit<<4) - 16 + ((prob + 8)>>4) ; -prob_offset = (bit<<4) - 16
ld e,a
pop bc
ld a,(bc) ; A = prob (cheaper + shorter to re-read again from memory)
sub e ; A = 16 - (bit<<4) + prob - ((prob + 8)>>4) ; = prob_offset + prob - ((prob + 8)>>4)
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
/*
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_number:
; HL = upkr_state
; IX = upkr_data_ptr
; BC = probs+context_index-1
; A' = upkr_current_byte (!!! init to 0x80 at start, not 0x00)
; return length in DE, CF=0
ld de,$FFFF ; length = 0 with positional-stop-bit
or a ; CF=0 to skip getting data bit and use only `rr d : rr e` to fix init DE
.loop:
call c,inc_c_decode_bit ; get data bit, context_index + 1 / if CF=0 just add stop bit into DE init
rr d
rr e ; DE = length = (length >> 1) | (bit << 15);
call inc_c_decode_bit ; context_index += 2
jr c,.loop
.fix_bit_pos:
ccf ; NC will become this final `| (1 << bit_pos)` bit
rr d
rr e
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
; 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
probs: EQU ((UPKR_PROBS_ORIGIN) + 255) & -$100 ; probs array aligned to 256
ELSE
probs: EQU ($ + 255) & -$100 ; probs array aligned to 256
ENDIF
.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
/*
archived: negligibly faster but +6B longer decode_number variant using HL' and BC' to
do `number|=(1<<bit_pos);` type of logic in single loop.
*/
; decode_number:
; exx
; ld bc,1
; ld l,b
; ld h,b ; HL = 0
; .loop
; exx
; inc c
; call decode_bit
; jr nc,.done
; inc c
; call decode_bit
; exx
; jr nc,.b0
; add hl,bc
; .b0:
; sla c
; rl b
; jr .loop
; .done:
; exx
; add hl,bc
; push hl
; exx
; pop de
; ret
/*
archived: possible LUT variant of updating probs value, requires 512-aligned 512B table (not tested)
*/
; code is replacing decode_bit from "; *** adjust probs[context_index]", followed by `ld (bc),a : add a,d ...`
; ld c,a
; ld a,high(probs_update_table)/2 ; must be 512 aligned
; rla
; ld b,a
; ld a,(bc)
; pop bc
; -------------------------------------------
; probs_update_table: EQU probs-512
; -------------------------------------------
; table generator is not obvious and probably not short either, 20+ bytes almost for sure, maybe even 30-40
ENDMODULE
OPT pop