14 Commits

Author SHA1 Message Date
22582d43a8 z80 branch, but with original (slightly more accurate) prob update 2022-09-15 00:17:21 +02:00
f467b6a454 z80 branch, but with original (slightly more accurate) prob update 2022-09-14 23:49:43 +02:00
a1dabaf7f9 add simple script to compare compression of variants 2022-09-14 23:41:14 +02:00
75e375fb1f Merge branch 'ped7g-z80_ped7g' into z80 2022-09-14 09:03:28 +02:00
Peter Helcmanovsky (Ped)
c7ea11bce3 z80_unpacker: optimisations: -2B in unpack implementation = 183B 2022-09-14 01:44:04 +02:00
Peter Helcmanovsky (Ped)
02d20867ee z80_unpacker: optimisations: -2B in unpack implementation = 185B 2022-09-14 01:01:56 +02:00
Peter Helcmanovsky (Ped)
511ddefc08 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
2022-09-14 00:01:51 +02:00
Peter Helcmanovsky (Ped)
d30baaa91f z80_unpacker: optimisations: -1B by keeping write_ptr in DE' 2022-09-13 23:57:59 +02:00
Peter Helcmanovsky (Ped)
919a892ef0 z80_unpacker: optimisations: -1B by decode_length returning CF=0 2022-09-13 23:25:03 +02:00
Peter Helcmanovsky (Ped)
ea5c0b1b15 z80_unpacker: optimisations: shorter >>4 in probs update 2022-09-13 23:15:18 +02:00
Peter Helcmanovsky (Ped)
a19ec2abb7 z80_unpacker: optimisations: remove .offset init
first offset is mandatory in packed data
2022-09-13 22:53:15 +02:00
Peter Helcmanovsky (Ped)
7b051113e1 z80_unpacker: initial working version with screen-slideshow example 2022-09-13 22:12:03 +02:00
f1f1c64a76 implement simplified prob update, update unpack.c 2022-09-10 12:01:42 +02:00
36cb6d77b5 BE bitstream, flip bit encoding 2022-09-10 11:31:09 +02:00
32 changed files with 587 additions and 858 deletions

16
Cargo.lock generated
View File

@@ -62,12 +62,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexopt"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478ee9e62aaeaf5b140bd4138753d1f109765488581444218d3ddda43234f3e8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.108" version = "0.2.108"
@@ -95,6 +89,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]] [[package]]
name = "sacabase" name = "sacabase"
version = "2.0.0" version = "2.0.0"
@@ -117,12 +117,12 @@ dependencies = [
[[package]] [[package]]
name = "upkr" name = "upkr"
version = "0.2.0-pre1" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"cdivsufsort", "cdivsufsort",
"lexopt",
"pbr", "pbr",
"pico-args",
] ]
[[package]] [[package]]

View File

@@ -1,12 +1,12 @@
[package] [package]
name = "upkr" name = "upkr"
version = "0.2.0-pre1" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
cdivsufsort = "2" cdivsufsort = "2"
lexopt = "0.2.1" pico-args = "0.4"
anyhow = "1" anyhow = "1"
pbr = "1" pbr = "1"

View File

@@ -1 +0,0 @@
/build/

View File

@@ -1,51 +0,0 @@
build/unpack_riscv64: ../c_unpacker/main.c unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -g -static -o $@ $^
test_riscv64: build/unpack_riscv64
qemu-riscv64 $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
build/unpack_riscv64.o: unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -c -o $@ $?
build/unpack_riscv64.bin: build/unpack_riscv64.o
riscv64-linux-gnu-objcopy -O binary --only-section=.text $? $@
disas-riscv64: build/unpack_riscv64.o
riscv64-linux-gnu-objdump -d $?
build/unpack_riscv32.o: unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -march=rv32imc -mabi=ilp32 -c -o $@ $?
build/unpack_riscv32.bin: build/unpack_riscv32.o
riscv64-linux-gnu-objcopy -O binary --only-section=.text $? $@
disas-riscv32: build/unpack_riscv32.o
riscv64-linux-gnu-objdump -d $?
build/unpack_armv6m: ../c_unpacker/main.c unpack_armv6m.S
mkdir -p build
arm-linux-gnueabihf-gcc -g -static -o $@ $^
test_armv6m: build/unpack_armv6m
qemu-arm $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
build/unpack_armv6m.bin: unpack_armv6m.S
mkdir -p build
arm-none-eabi-gcc -march=armv6-m -c -o build/unpack_armv6m.o $?
arm-none-eabi-objcopy -O binary --only-section=.text build/unpack_armv6m.o $@
build/unpack_c: ../c_unpacker/main.c ../c_unpacker/unpack.c
mkdir -p build
gcc -g -o $@ $^
test_c: build/unpack_c
$< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin
sizes: build/unpack_armv6m.bin build/unpack_riscv64.bin build/unpack_riscv32.bin
ls -l build/*.bin

View File

@@ -1,99 +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 & 1);
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 tmp = prob;
if(!bit) {
tmp = 256 - tmp;
}
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;
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);
}
void* 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;
}

Binary file not shown.

View File

@@ -1,162 +0,0 @@
// armv6-m upkr unpacker by yrlf
// some optimizations by exoticorn
.syntax unified
.thumb
.section .text
#define ALIGNUP(n, align) (((n) + (align) - 1) & ~((align) - 1))
#define PROB_LEN (1 + 255 + 1 + 2*32 + 2*32)
#define FRAME_SIZE ALIGNUP(PROB_LEN, 4)
// auto upkr_unpack(uint8_t * out, uint8_t * in) -> tuple<uint8_t *, uint8_t *>
.global upkr_unpack
.type upkr_unpack, %function
// r0 .. out_ptr (returned)
// r1 .. in_ptr (returned)
// r2 .. state
// r3 .. offset
// r4 .. prev_was_literal / decode_length ret
// r5 .. subroutine arg (preserved)
// r6 .. decode_bit ret
// r7 .. probs ptr
upkr_unpack:
push { r4, r5, r6, r7, lr }
sub sp, sp, #FRAME_SIZE
mov r7, sp
movs r2, #255
adds r2, r2, #(PROB_LEN - 255)
movs r3, #128
.Lclear:
subs r2, r2, #1
strb r3, [r7, r2]
bne .Lclear
.Lloop:
movs r5, #0
bl upkr_decode_bit
beq .Ldata
.Lmatch:
// r6 = 1
lsls r5, r6, #8
cmp r4, #0
beq 1f
bl upkr_decode_bit
beq 2f
1:
bl upkr_decode_length
adds r3, r4, #1
beq .Lend
2:
adds r5, r5, #64
bl upkr_decode_length
.Lcopy_loop:
ldrb r5, [r0, r3]
.Lstore:
strb r5, [r0]
adds r0, r0, #1
adds r4, r4, #1
blt .Lcopy_loop
b .Lloop
.Ldata:
movs r5, #1
.Ldata_loop:
bl upkr_decode_bit
adcs r5, r5, r5
lsrs r4, r5, #8
beq .Ldata_loop
b .Lstore
.Lend:
add sp, sp, #FRAME_SIZE
pop { r4, r5, r6, r7, pc }
.type upkr_decode_length, %function
// r0 .. -length tmp (saved)
// r1 ..
// r2 ..
// r3 ..
// r4 .. -length (returned)
// r5 .. context index (saved)
// r6 .. (saved)
// r7 ..
upkr_decode_length:
push { r0, r5, r6, lr }
movs r0, #0
subs r4, r0, #1
.Lbit_loop:
adds r5, r5, #1
bl upkr_decode_bit
beq 1f
adds r5, r5, #1
bl upkr_decode_bit
beq 2f
adds r0, r0, r4
2:
lsls r4, r4, #1
b .Lbit_loop
1:
adds r4, r4, r0
pop { r0, r5, r6, pc }
.type upkr_decode_bit, %function
// r0 .. tmp / prob (saved)
// r1 .. in_ptr (modified)
// r2 .. state (modified)
// r3 .. scratch (saved)
// r4 ..
// r5 .. context index (preserved)
// r6 .. bit (returned)
// r7 .. probs ptr (preserved)
upkr_fill_state:
lsls r2, r2, #8
ldrb r6, [r1]
adds r1, r1, #1
orrs r2, r2, r6
upkr_decode_bit:
lsrs r6, r2, #12
beq upkr_fill_state
push { r0, r1, r3, lr }
ldrb r0, [r7, r5]
lsrs r3, r2, #8
uxtb r1, r2
subs r6, r1, r0
blt 1f
subs r1, r2, r0
rsbs r0, r0, #0
1:
muls r3, r3, r0
adds r2, r1, r3
rsbs r3, r0, #0
uxtb r3, r3
lsrs r3, r3, #4
adcs r0, r0, r3
cmp r6, #0
blt 1f
rsbs r0, r0, #0
1:
strb r0, [r7, r5]
lsrs r6, r6, #31
pop { r0, r1, r3, pc }

View File

@@ -1,142 +0,0 @@
.section .text
#define FRAME_SIZE (256+32*4+4)
// x8 prob array ptr
// x9 prev was literal
// x10 out ptr
// x11 in ptr
// x12 offset
// x13 state
.global upkr_unpack
.type upkr_unpack, %function
upkr_unpack:
mv t4, ra
mv x17, x8
mv t6, x9
li x13, FRAME_SIZE
li x9, 128
1:
addi sp, sp, -1
sb x9, 0(sp)
addi x13, x13, -1
bnez x13, 1b
.Lmainloop:
li x14, 0
jal upkr_decode_bit
beqz x15, .Lliteral
slli x14, x14, 8
beqz x9, .Lread_offset_inc_x14
jal upkr_decode_bit
bnez x15, .Lread_offset
.Lfinished_offset:
addi x14, x14, 64
jal t3, upkr_decode_number
1:
add x14, x10, t0
lbu x14, (x14)
.Lstore_byte:
sb x14, (x10)
addi x10, x10, 1
addi x9, x9, 1
blt x9, x0, 1b
j .Lmainloop
.Lliteral:
jal upkr_decode_bit
addi x14, x14, -1
slli x14, x14, 1
add x14, x14, x15
srli x9, x14, 8
beqz x9, .Lliteral
j .Lstore_byte
.Lread_offset_inc_x14:
addi x14, x14, 1
.Lread_offset:
jal t3, upkr_decode_number
addi t0, x9, 1
bnez t0, .Lfinished_offset
.Ldone:
addi sp, sp, FRAME_SIZE
mv x8, x17
mv x9, t6
jr t4
// x14 context index
// return: x9 negtive decoded number
upkr_decode_number:
mv t5, x14
li x9, 0
li x8, -1
1:
jal upkr_decode_bit
beqz x15, 1f
jal upkr_decode_bit
beqz x15, 2f
add x9, x9, x8
2:
slli x8, x8, 1
j 1b
1:
add x9, x9, x8
mv x14, t5
jr t3
upkr_load_byte:
lbu x15, 0(x11)
addi x11, x11, 1
slli x13, x13, 8
add x13, x13, x15
// x8 prob array ptr
// x11 in ptr
// x13 state
// x14 context index
// return:
// x14 context index + 1
// x15 decoded bit
upkr_decode_bit:
srli x15, x13, 12
beqz x15, upkr_load_byte
mv t1, x14
mv t2, x10
add x14, x14, sp
lbu x12, 0(x14)
andi x10, x13, 255
sltu x15, x10, x12
srli x13, x13, 8
beqz x15, .Lelse
mul x13, x13, x12
add x13, x13, x10
li x10, 256 + 8
sub x10, x10, x12
srli x10, x10, 4
add x12, x12, x10
j .Lendif
.Lelse:
li x16, 256
sub x16, x16, x12
mul x13, x13, x16
add x13, x13, x10
sub x13, x13, x12
addi x10, x12, 8
srli x10, x10, 4
sub x12, x12, x10
.Lendif:
sb x12, 0(x14)
addi x14, t1, 1
mv x10, t2
ret

View File

@@ -1,33 +0,0 @@
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 & 1);
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;
if(bit) {
prob = 256 - prob;
}
upkr_state -= prob * ((upkr_state >> 8) + (bit ^ 1));
prob -= (prob + 8) >> 4;
if(bit) {
prob = -prob;
}
upkr_probs[context_index] = prob;
return bit;
}

View File

@@ -1,7 +1,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
void* upkr_unpack(void* destination, void* compressed_data); int upkr_unpack(void* destination, void* compressed_data);
int main(int argn, char** argv) { int main(int argn, char** argv) {
void* input_buffer = malloc(1024*1024); void* input_buffer = malloc(1024*1024);
@@ -13,8 +13,7 @@ int main(int argn, char** argv) {
printf("Compressed size: %d\n", in_size); printf("Compressed size: %d\n", in_size);
void* end_ptr = upkr_unpack(output_buffer, input_buffer); int out_size = upkr_unpack(output_buffer, input_buffer);
int out_size = (char*)end_ptr - (char*)output_buffer;
printf("Uncompressed size: %d\n", out_size); printf("Uncompressed size: %d\n", out_size);

View File

@@ -19,8 +19,8 @@ int upkr_decode_bit(int context_index) {
upkr_current_byte = *upkr_data_ptr++; upkr_current_byte = *upkr_data_ptr++;
upkr_bits_left = 8; upkr_bits_left = 8;
} }
upkr_state = (upkr_state << 1) + (upkr_current_byte & 1); upkr_state = (upkr_state << 1) + (upkr_current_byte >> 7);
upkr_current_byte >>= 1; upkr_current_byte <<= 1;
--upkr_bits_left; --upkr_bits_left;
} }
#else #else
@@ -30,16 +30,18 @@ int upkr_decode_bit(int context_index) {
#endif #endif
int prob = upkr_probs[context_index]; int prob = upkr_probs[context_index];
int bit = (upkr_state & 255) < prob ? 1 : 0; int bit = (upkr_state & 255) >= prob ? 1 : 0;
int prob_offset = 16;
int state_offset = 0;
int state_scale = prob;
if(bit) { if(bit) {
upkr_state = prob * (upkr_state >> 8) + (upkr_state & 255); state_offset = -prob;
prob += (256 - prob + 8) >> 4; state_scale = 256 - prob;
} else { prob_offset = 0;
upkr_state = (256 - prob) * (upkr_state >> 8) + (upkr_state & 255) - prob;
prob -= (prob + 8) >> 4;
} }
upkr_probs[context_index] = prob; upkr_state = state_offset + state_scale * (upkr_state >> 8) + (upkr_state & 255);
upkr_probs[context_index] = prob_offset + prob - ((prob + 8) >> 4);
return bit; return bit;
} }
@@ -54,7 +56,7 @@ int upkr_decode_length(int context_index) {
return length | (1 << bit_pos); return length | (1 << bit_pos);
} }
void* upkr_unpack(void* destination, void* compressed_data) { int upkr_unpack(void* destination, void* compressed_data) {
upkr_data_ptr = (u8*)compressed_data; upkr_data_ptr = (u8*)compressed_data;
upkr_state = 0; upkr_state = 0;
#ifdef UPKR_BITSTREAM #ifdef UPKR_BITSTREAM
@@ -92,5 +94,5 @@ void* upkr_unpack(void* destination, void* compressed_data) {
} }
} }
return write_ptr; return write_ptr - (u8*)destination;
} }

50
compare-variants Executable file
View File

@@ -0,0 +1,50 @@
#!/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,17 +1,12 @@
use crate::{ use crate::rans::{ONE_PROB, PROB_BITS};
rans::{ONE_PROB, PROB_BITS},
Config,
};
const INIT_PROB: u16 = 1 << (PROB_BITS - 1); const INIT_PROB: u16 = 1 << (PROB_BITS - 1);
const UPDATE_RATE: u32 = 4; const UPDATE_RATE: i32 = 4;
const UPDATE_ADD: u32 = 8; const UPDATE_ADD: i32 = 8;
#[derive(Clone)] #[derive(Clone)]
pub struct ContextState { pub struct ContextState {
contexts: Vec<u8>, contexts: Vec<u8>,
invert_bit_encoding: bool,
simplified_prob_update: bool,
} }
pub struct Context<'a> { pub struct Context<'a> {
@@ -20,11 +15,9 @@ pub struct Context<'a> {
} }
impl ContextState { impl ContextState {
pub fn new(size: usize, config: &Config) -> ContextState { pub fn new(size: usize) -> ContextState {
ContextState { ContextState {
contexts: vec![INIT_PROB as u8; size], contexts: vec![INIT_PROB as u8; size],
invert_bit_encoding: config.invert_bit_encoding,
simplified_prob_update: config.simplified_prob_update,
} }
} }
@@ -40,21 +33,12 @@ impl<'a> Context<'a> {
pub fn update(&mut self, bit: bool) { pub fn update(&mut self, bit: bool) {
let old = self.state.contexts[self.index]; let old = self.state.contexts[self.index];
if bit {
self.state.contexts[self.index] = if self.state.simplified_prob_update { self.state.contexts[self.index] =
let offset = if bit ^ self.state.invert_bit_encoding { old - ((old as i32 + UPDATE_ADD) >> UPDATE_RATE) as u8;
ONE_PROB as i32 >> UPDATE_RATE
} else {
0
};
(offset + old as i32 - ((old as i32 + UPDATE_ADD as i32) >> UPDATE_RATE)) as u8
} else { } else {
if bit ^ self.state.invert_bit_encoding { self.state.contexts[self.index] =
old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8 old + (((ONE_PROB as i32 - old as i32) + UPDATE_ADD) >> UPDATE_RATE) as u8;
} else { }
old - ((old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u8
}
};
} }
} }

View File

@@ -1,16 +1,16 @@
use crate::lz;
use crate::match_finder::MatchFinder; use crate::match_finder::MatchFinder;
use crate::rans::RansCoder; use crate::rans::RansCoder;
use crate::ProgressCallback; use crate::ProgressCallback;
use crate::{lz, Config};
pub fn pack( pub fn pack(
data: &[u8], data: &[u8],
config: &Config, use_bitstream: bool,
mut progress_callback: Option<ProgressCallback>, mut progress_callback: Option<ProgressCallback>,
) -> Vec<u8> { ) -> Vec<u8> {
let mut match_finder = MatchFinder::new(data); let mut match_finder = MatchFinder::new(data);
let mut rans_coder = RansCoder::new(config); let mut rans_coder = RansCoder::new(use_bitstream);
let mut state = lz::CoderState::new(config); let mut state = lz::CoderState::new();
let mut pos = 0; let mut pos = 0;
while pos < data.len() { while pos < data.len() {
@@ -26,7 +26,7 @@ pub fn pack(
offset: offset as u32, offset: offset as u32,
len: m.length as u32, len: m.length as u32,
} }
.encode(&mut rans_coder, &mut state, config); .encode(&mut rans_coder, &mut state);
pos += m.length; pos += m.length;
encoded_match = true; encoded_match = true;
} }
@@ -45,7 +45,7 @@ pub fn pack(
offset: offset as u32, offset: offset as u32,
len: length as u32, len: length as u32,
} }
.encode(&mut rans_coder, &mut state, config); .encode(&mut rans_coder, &mut state);
pos += length; pos += length;
encoded_match = true; encoded_match = true;
} }
@@ -53,11 +53,11 @@ pub fn pack(
} }
if !encoded_match { if !encoded_match {
lz::Op::Literal(data[pos]).encode(&mut rans_coder, &mut state, config); lz::Op::Literal(data[pos]).encode(&mut rans_coder, &mut state);
pos += 1; pos += 1;
} }
} }
lz::encode_eof(&mut rans_coder, &mut state, config); lz::encode_eof(&mut rans_coder, &mut state);
rans_coder.finish() rans_coder.finish()
} }

View File

@@ -9,46 +9,16 @@ pub use lz::unpack;
pub type ProgressCallback<'a> = &'a mut dyn FnMut(usize); pub type ProgressCallback<'a> = &'a mut dyn FnMut(usize);
pub struct Config {
pub use_bitstream: bool,
pub parity_contexts: usize,
pub invert_bit_encoding: bool,
pub is_match_bit: bool,
pub new_offset_bit: bool,
pub continue_value_bit: bool,
pub bitstream_is_big_endian: bool,
pub simplified_prob_update: bool,
}
impl Default for Config {
fn default() -> Config {
Config {
use_bitstream: false,
parity_contexts: 1,
invert_bit_encoding: false,
is_match_bit: true,
new_offset_bit: true,
continue_value_bit: true,
bitstream_is_big_endian: false,
simplified_prob_update: false,
}
}
}
pub fn pack( pub fn pack(
data: &[u8], data: &[u8],
level: u8, level: u8,
config: Config, use_bitstream: bool,
progress_callback: Option<ProgressCallback>, progress_callback: Option<ProgressCallback>,
) -> Vec<u8> { ) -> Vec<u8> {
if level == 0 { if level == 0 {
greedy_packer::pack(data, &config, progress_callback) greedy_packer::pack(data, use_bitstream, progress_callback)
} else { } else {
parsing_packer::pack(data, level, &config, progress_callback) parsing_packer::pack(data, level, use_bitstream, progress_callback)
} }
} }

View File

@@ -1,6 +1,5 @@
use crate::context_state::ContextState; use crate::context_state::ContextState;
use crate::rans::{EntropyCoder, RansDecoder}; use crate::rans::{EntropyCoder, RansDecoder};
use crate::Config;
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum Op { pub enum Op {
@@ -9,66 +8,42 @@ pub enum Op {
} }
impl Op { impl Op {
pub fn encode(&self, coder: &mut dyn EntropyCoder, state: &mut CoderState, config: &Config) { pub fn encode(&self, coder: &mut dyn EntropyCoder, state: &mut CoderState) {
let literal_base = state.pos % state.parity_contexts * 256;
match self { match self {
&Op::Literal(lit) => { &Op::Literal(lit) => {
encode_bit(coder, state, literal_base, !config.is_match_bit); encode_bit(coder, state, 0, false);
let mut context_index = 1; let mut context_index = 1;
for i in (0..8).rev() { for i in (0..8).rev() {
let bit = (lit >> i) & 1 != 0; let bit = (lit >> i) & 1 != 0;
encode_bit(coder, state, literal_base + context_index, bit); encode_bit(coder, state, context_index, bit);
context_index = (context_index << 1) | bit as usize; context_index = (context_index << 1) | bit as usize;
} }
state.prev_was_match = false; state.prev_was_match = false;
state.pos += 1;
} }
&Op::Match { offset, len } => { &Op::Match { offset, len } => {
encode_bit(coder, state, literal_base, config.is_match_bit); encode_bit(coder, state, 0, true);
if !state.prev_was_match { if !state.prev_was_match {
encode_bit( encode_bit(coder, state, 256, offset != state.last_offset);
coder,
state,
256 * state.parity_contexts,
(offset != state.last_offset) == config.new_offset_bit,
);
} else { } else {
assert!(offset != state.last_offset); assert!(offset != state.last_offset);
} }
if offset != state.last_offset { if offset != state.last_offset {
encode_length( encode_length(coder, state, 257, offset + 1);
coder,
state,
256 * state.parity_contexts + 1,
offset + 1,
config,
);
state.last_offset = offset; state.last_offset = offset;
} }
encode_length(coder, state, 256 * state.parity_contexts + 65, len, config); encode_length(coder, state, 257 + 64, len);
state.prev_was_match = true; state.prev_was_match = true;
state.pos += len as usize;
} }
} }
} }
} }
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState, config: &Config) { pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState) {
encode_bit( encode_bit(coder, state, 0, true);
coder,
state,
state.pos % state.parity_contexts * 256,
config.is_match_bit,
);
if !state.prev_was_match { if !state.prev_was_match {
encode_bit( encode_bit(coder, state, 256, true);
coder,
state,
256 * state.parity_contexts,
config.new_offset_bit,
);
} }
encode_length(coder, state, 256 * state.parity_contexts + 1, 1, config); encode_length(coder, state, 257, 1);
} }
fn encode_bit( fn encode_bit(
@@ -85,18 +60,17 @@ fn encode_length(
state: &mut CoderState, state: &mut CoderState,
context_start: usize, context_start: usize,
mut value: u32, mut value: u32,
config: &Config,
) { ) {
assert!(value >= 1); assert!(value >= 1);
let mut context_index = context_start; let mut context_index = context_start;
while value >= 2 { while value >= 2 {
encode_bit(coder, state, context_index, config.continue_value_bit); encode_bit(coder, state, context_index, true);
encode_bit(coder, state, context_index + 1, value & 1 != 0); encode_bit(coder, state, context_index + 1, value & 1 != 0);
context_index += 2; context_index += 2;
value >>= 1; value >>= 1;
} }
encode_bit(coder, state, context_index, !config.continue_value_bit); encode_bit(coder, state, context_index, false);
} }
#[derive(Clone)] #[derive(Clone)]
@@ -104,18 +78,14 @@ pub struct CoderState {
contexts: ContextState, contexts: ContextState,
last_offset: u32, last_offset: u32,
prev_was_match: bool, prev_was_match: bool,
pos: usize,
parity_contexts: usize,
} }
impl CoderState { impl CoderState {
pub fn new(config: &Config) -> CoderState { pub fn new() -> CoderState {
CoderState { CoderState {
contexts: ContextState::new((1 + 255) * config.parity_contexts + 1 + 64 + 64, config), contexts: ContextState::new(1 + 255 + 1 + 64 + 64),
last_offset: 0, last_offset: 0,
prev_was_match: false, prev_was_match: false,
pos: 0,
parity_contexts: config.parity_contexts,
} }
} }
@@ -124,9 +94,9 @@ impl CoderState {
} }
} }
pub fn unpack(packed_data: &[u8], config: Config) -> Vec<u8> { pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
let mut decoder = RansDecoder::new(packed_data, &config); let mut decoder = RansDecoder::new(packed_data, use_bitstream);
let mut contexts = ContextState::new((1 + 255) * config.parity_contexts + 1 + 64 + 64, &config); let mut contexts = ContextState::new(1 + 255 + 1 + 64 + 64);
let mut result = vec![]; let mut result = vec![];
let mut offset = 0; let mut offset = 0;
let mut prev_was_match = false; let mut prev_was_match = false;
@@ -135,13 +105,10 @@ pub fn unpack(packed_data: &[u8], config: Config) -> Vec<u8> {
decoder: &mut RansDecoder, decoder: &mut RansDecoder,
contexts: &mut ContextState, contexts: &mut ContextState,
mut context_index: usize, mut context_index: usize,
config: &Config,
) -> usize { ) -> usize {
let mut length = 0; let mut length = 0;
let mut bit_pos = 0; let mut bit_pos = 0;
while decoder.decode_with_context(&mut contexts.context_mut(context_index)) while decoder.decode_with_context(&mut contexts.context_mut(context_index)) {
== config.continue_value_bit
{
length |= (decoder.decode_with_context(&mut contexts.context_mut(context_index + 1)) length |= (decoder.decode_with_context(&mut contexts.context_mut(context_index + 1))
as usize) as usize)
<< bit_pos; << bit_pos;
@@ -152,31 +119,14 @@ pub fn unpack(packed_data: &[u8], config: Config) -> Vec<u8> {
} }
loop { loop {
let literal_base = result.len() % config.parity_contexts * 256; if decoder.decode_with_context(&mut contexts.context_mut(0)) {
if decoder.decode_with_context(&mut contexts.context_mut(literal_base)) if prev_was_match || decoder.decode_with_context(&mut contexts.context_mut(256)) {
== config.is_match_bit offset = decode_length(&mut decoder, &mut contexts, 257) - 1;
{
if prev_was_match
|| decoder
.decode_with_context(&mut contexts.context_mut(256 * config.parity_contexts))
== config.new_offset_bit
{
offset = decode_length(
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 1,
&config,
) - 1;
if offset == 0 { if offset == 0 {
break; break;
} }
} }
let length = decode_length( let length = decode_length(&mut decoder, &mut contexts, 257 + 64);
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 65,
&config,
);
for _ in 0..length { for _ in 0..length {
result.push(result[result.len() - offset]); result.push(result[result.len() - offset]);
} }
@@ -185,8 +135,7 @@ pub fn unpack(packed_data: &[u8], config: Config) -> Vec<u8> {
let mut context_index = 1; let mut context_index = 1;
let mut byte = 0; let mut byte = 0;
for i in (0..8).rev() { for i in (0..8).rev() {
let bit = decoder let bit = decoder.decode_with_context(&mut contexts.context_mut(context_index));
.decode_with_context(&mut contexts.context_mut(literal_base + context_index));
context_index = (context_index << 1) | bit as usize; context_index = (context_index << 1) | bit as usize;
byte |= (bit as u8) << i; byte |= (bit as u8) << i;
} }

View File

@@ -1,149 +1,67 @@
use anyhow::Result; use anyhow::{bail, Result};
use std::ffi::OsStr;
use std::io::prelude::*; use std::io::prelude::*;
use std::process;
use std::{fs::File, path::PathBuf}; use std::{fs::File, path::PathBuf};
fn main() -> Result<()> { fn main() -> Result<()> {
let mut config = upkr::Config::default(); let mut args = pico_args::Arguments::from_env();
let mut reverse = false;
let mut unpack = false;
let mut level = 2;
let mut infile: Option<PathBuf> = None;
let mut outfile: Option<PathBuf> = None;
let mut parser = lexopt::Parser::from_env(); match args.subcommand()?.as_ref().map(|s| s.as_str()) {
while let Some(arg) = parser.next()? { None => print_help(),
use lexopt::prelude::*; Some("pack") => {
match arg { let level = args.opt_value_from_str(["-l", "--level"])?.unwrap_or(2u8);
Short('b') | Long("bitstream") => config.use_bitstream = true, let use_bitstream = args.contains(["-b", "--bitstream"]);
Short('p') | Long("parity") => config.parity_contexts = parser.value()?.parse()?,
Short('r') | Long("reverse") => reverse = true,
Long("invert-is-match-bit") => config.is_match_bit = false,
Long("invert-new-offset-bit") => config.new_offset_bit = false,
Long("invert-continue-value-bit") => config.continue_value_bit = false,
Long("invert-bit-encoding") => config.invert_bit_encoding = true,
Long("simplified-prob-update") => config.simplified_prob_update = true,
Long("big-endian-bitstream") => {
config.use_bitstream = true;
config.bitstream_is_big_endian = true;
}
Long("z80") => { let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
config.use_bitstream = true; let outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
config.bitstream_is_big_endian = true;
config.invert_bit_encoding = true;
config.simplified_prob_update = true;
}
Long("x86") => {
config.use_bitstream = true;
config.continue_value_bit = false;
config.is_match_bit = false;
}
Short('u') | Long("unpack") => unpack = true, let mut data = vec![];
Short('l') | Long("level") => level = parser.value()?.parse()?, File::open(infile)?.read_to_end(&mut data)?;
Short('h') | Long("help") => print_help(0),
Value(val) if infile.is_none() => infile = Some(val.try_into()?), let mut pb = pbr::ProgressBar::new(data.len() as u64);
Value(val) if outfile.is_none() => outfile = Some(val.try_into()?), pb.set_units(pbr::Units::Bytes);
_ => return Err(arg.unexpected().into()), let packed_data = upkr::pack(
&data,
level,
use_bitstream,
Some(&mut |pos| {
pb.set(pos as u64);
}),
);
pb.finish();
println!(
"Compressed {} bytes to {} bytes ({}%)",
data.len(),
packed_data.len(),
packed_data.len() as f32 * 100. / data.len() as f32
);
File::create(outfile)?.write_all(&packed_data)?;
} }
} Some("unpack") => {
let use_bitstream = args.contains(["-b", "--bitstream"]);
let infile = infile.unwrap_or_else(|| print_help(1)); let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let outfile = outfile.unwrap_or_else(|| { let outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
let mut name = infile.clone();
if unpack { let mut data = vec![];
if name.extension().filter(|&e| e == "upk").is_some() { File::open(infile)?.read_to_end(&mut data)?;
name.set_extension(""); let packed_data = upkr::unpack(&data, use_bitstream);
} else { File::create(outfile)?.write_all(&packed_data)?;
name.set_extension("bin");
}
} else {
let mut filename = name
.file_name()
.unwrap_or_else(|| OsStr::new(""))
.to_os_string();
filename.push(".upk");
name.set_file_name(filename);
} }
name Some(other) => {
}); bail!("Unknown subcommand '{}'", other);
if config.parity_contexts != 1 && config.parity_contexts != 2 && config.parity_contexts != 4 {
eprintln!("--parity has to be 1, 2, or 4");
process::exit(1);
}
if !unpack {
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(
&data,
level,
config,
Some(&mut |pos| {
pb.set(pos as u64);
}),
);
pb.finish();
if reverse {
packed_data.reverse();
}
println!(
"Compressed {} bytes to {} bytes ({}%)",
data.len(),
packed_data.len(),
packed_data.len() as f32 * 100. / data.len() as f32
);
File::create(outfile)?.write_all(&packed_data)?;
} else {
let mut data = vec![];
File::open(infile)?.read_to_end(&mut data)?;
if reverse {
data.reverse();
}
let mut unpacked_data = upkr::unpack(&data, config);
if reverse {
unpacked_data.reverse();
}
File::create(outfile)?.write_all(&unpacked_data)?;
} }
Ok(()) Ok(())
} }
fn print_help(exit_code: i32) -> ! { fn print_help() {
eprintln!("Usage:"); eprintln!("Usage:");
eprintln!(" upkr [-l level(0-9)] [config options] <infile> [<outfile>]"); eprintln!(" upkr pack [-b] [-l level(0-9)] <infile> <outfile>");
eprintln!(" upkr -u [config options] <infile> [<outfile>]"); eprintln!(" upkr unpack [-b] <infile> <outfile>");
eprintln!(); eprintln!();
eprintln!(" -l, --level N compression level 0-9");
eprintln!(" -u, --unpack unpack infile");
eprintln!();
eprintln!("Config presets for specific unpackers:");
eprintln!(" --z80 --big-endian-bitstream --invert-bit-encoding --simplified-prob-update");
eprintln!(" --x86 --bitstream --invert-is-match-bit --invert-continue-value-bit");
eprintln!();
eprintln!("Config options (need to match when packing/unpacking):");
eprintln!(" -b, --bitstream bitstream mode"); eprintln!(" -b, --bitstream bitstream mode");
eprintln!(" -p, --parity N use N (2/4) parity contexts"); eprintln!(" -l, --level N compression level 0-9");
eprintln!(" -r, --reverse reverse input & output"); std::process::exit(1);
eprintln!();
eprintln!("Config options to tailor output to specific optimized unpackers:");
eprintln!(" --invert-is-match-bit");
eprintln!(" --invert-new-offset-bit");
eprintln!(" --invert-continue-value-bit");
eprintln!(" --invert-bit-encoding");
eprintln!(" --simplified-prob-update");
eprintln!(" --big-endian-bitstream (implies --bitstream)");
process::exit(exit_code);
} }

View File

@@ -6,24 +6,19 @@ use crate::match_finder::MatchFinder;
use crate::rans::{CostCounter, RansCoder}; use crate::rans::{CostCounter, RansCoder};
use crate::{lz, ProgressCallback}; use crate::{lz, ProgressCallback};
pub fn pack( pub fn pack(data: &[u8], level: u8, use_bitstream: bool, progress_cb: Option<ProgressCallback>) -> Vec<u8> {
data: &[u8], let mut parse = parse(data, Config::from_level(level), progress_cb);
level: u8,
config: &crate::Config,
progress_cb: Option<ProgressCallback>,
) -> Vec<u8> {
let mut parse = parse(data, Config::from_level(level), config, progress_cb);
let mut ops = vec![]; let mut ops = vec![];
while let Some(link) = parse { while let Some(link) = parse {
ops.push(link.op); ops.push(link.op);
parse = link.prev.clone(); parse = link.prev.clone();
} }
let mut state = lz::CoderState::new(config); let mut state = lz::CoderState::new();
let mut coder = RansCoder::new(config); let mut coder = RansCoder::new(use_bitstream);
for op in ops.into_iter().rev() { for op in ops.into_iter().rev() {
op.encode(&mut coder, &mut state, config); op.encode(&mut coder, &mut state);
} }
lz::encode_eof(&mut coder, &mut state, config); lz::encode_eof(&mut coder, &mut state);
coder.finish() coder.finish()
} }
@@ -43,7 +38,6 @@ type Arrivals = HashMap<usize, Vec<Arrival>>;
fn parse( fn parse(
data: &[u8], data: &[u8],
config: Config, config: Config,
encoding_config: &crate::Config,
mut progress_cb: Option<ProgressCallback>, mut progress_cb: Option<ProgressCallback>,
) -> Option<Rc<Parse>> { ) -> Option<Rc<Parse>> {
let mut match_finder = MatchFinder::new(data) let mut match_finder = MatchFinder::new(data)
@@ -108,7 +102,6 @@ fn parse(
length: usize, length: usize,
arrival: &Arrival, arrival: &Arrival,
max_arrivals: usize, max_arrivals: usize,
config: &crate::Config,
) { ) {
cost_counter.reset(); cost_counter.reset();
let mut state = arrival.state.clone(); let mut state = arrival.state.clone();
@@ -116,7 +109,7 @@ fn parse(
offset: offset as u32, offset: offset as u32,
len: length as u32, len: length as u32,
}; };
op.encode(cost_counter, &mut state, config); op.encode(cost_counter, &mut state);
add_arrival( add_arrival(
arrivals, arrivals,
pos + length, pos + length,
@@ -136,13 +129,13 @@ fn parse(
0, 0,
Arrival { Arrival {
parse: None, parse: None,
state: lz::CoderState::new(encoding_config), state: lz::CoderState::new(),
cost: 0.0, cost: 0.0,
}, },
max_arrivals, max_arrivals,
); );
let cost_counter = &mut CostCounter::new(encoding_config); let cost_counter = &mut CostCounter::new();
let mut best_per_offset = HashMap::new(); let mut best_per_offset = HashMap::new();
for pos in 0..data.len() { for pos in 0..data.len() {
let match_length = |offset: usize| { let match_length = |offset: usize| {
@@ -192,7 +185,6 @@ fn parse(
m.length, m.length,
&arrival, &arrival,
max_arrivals, max_arrivals,
encoding_config,
); );
if m.length >= config.greedy_size { if m.length >= config.greedy_size {
break 'arrival_loop; break 'arrival_loop;
@@ -216,7 +208,6 @@ fn parse(
length, length,
&arrival, &arrival,
max_arrivals, max_arrivals,
encoding_config,
); );
found_last_offset |= offset as u32 == arrival.state.last_offset(); found_last_offset |= offset as u32 == arrival.state.last_offset();
if offset < near_matches.len() { if offset < near_matches.len() {
@@ -237,7 +228,6 @@ fn parse(
length, length,
&arrival, &arrival,
max_arrivals, max_arrivals,
encoding_config,
); );
} }
} }
@@ -245,7 +235,7 @@ fn parse(
cost_counter.reset(); cost_counter.reset();
let mut state = arrival.state; let mut state = arrival.state;
let op = lz::Op::Literal(data[pos]); let op = lz::Op::Literal(data[pos]);
op.encode(cost_counter, &mut state, encoding_config); op.encode(cost_counter, &mut state);
add_arrival( add_arrival(
&mut arrivals, &mut arrivals,
pos + 1, pos + 1,

View File

@@ -1,4 +1,4 @@
use crate::{context_state::Context, Config}; use crate::context_state::Context;
pub const PROB_BITS: u32 = 8; pub const PROB_BITS: u32 = 8;
pub const ONE_PROB: u32 = 1 << PROB_BITS; pub const ONE_PROB: u32 = 1 << PROB_BITS;
@@ -15,25 +15,20 @@ pub trait EntropyCoder {
pub struct RansCoder { pub struct RansCoder {
bits: Vec<u16>, bits: Vec<u16>,
use_bitstream: bool, use_bitstream: bool,
bitstream_is_big_endian: bool,
invert_bit_encoding: bool,
} }
impl EntropyCoder for RansCoder { impl EntropyCoder for RansCoder {
fn encode_bit(&mut self, bit: bool, prob: u16) { fn encode_bit(&mut self, bit: bool, prob: u16) {
assert!(prob < 32768); assert!(prob < 32768);
self.bits self.bits.push(prob | ((bit as u16) << 15));
.push(prob | (((bit ^ self.invert_bit_encoding) as u16) << 15));
} }
} }
impl RansCoder { impl RansCoder {
pub fn new(config: &Config) -> RansCoder { pub fn new(use_bitstream: bool) -> RansCoder {
RansCoder { RansCoder {
bits: Vec::new(), bits: Vec::new(),
use_bitstream: config.use_bitstream, use_bitstream,
bitstream_is_big_endian: config.bitstream_is_big_endian,
invert_bit_encoding: config.invert_bit_encoding,
} }
} }
@@ -43,31 +38,18 @@ impl RansCoder {
let mut state = 1 << l_bits; let mut state = 1 << l_bits;
let mut byte = 0u8; let mut byte = 0u8;
let mut bit = if self.bitstream_is_big_endian { 0 } else { 8 }; let mut bit = 0;
let mut flush_state: Box<dyn FnMut(&mut u32)> = if self.use_bitstream { let mut flush_state: Box<dyn FnMut(&mut u32)> = if self.use_bitstream {
if self.bitstream_is_big_endian { Box::new(|state: &mut u32| {
Box::new(|state: &mut u32| { byte |= ((*state & 1) as u8) << bit;
byte |= ((*state & 1) as u8) << bit; bit += 1;
bit += 1; if bit == 8 {
if bit == 8 { buffer.push(byte);
buffer.push(byte); byte = 0;
byte = 0; bit = 0;
bit = 0; }
} *state >>= 1;
*state >>= 1; })
})
} else {
Box::new(|state: &mut u32| {
bit -= 1;
byte |= ((*state & 1) as u8) << bit;
if bit == 0 {
buffer.push(byte);
byte = 0;
bit = 8;
}
*state >>= 1;
})
}
} else { } else {
Box::new(|state: &mut u32| { Box::new(|state: &mut u32| {
buffer.push(*state as u8); buffer.push(*state as u8);
@@ -79,7 +61,7 @@ impl RansCoder {
let max_state_factor: u32 = 1 << (l_bits + num_flush_bits - PROB_BITS); let max_state_factor: u32 = 1 << (l_bits + num_flush_bits - PROB_BITS);
for step in self.bits.into_iter().rev() { for step in self.bits.into_iter().rev() {
let prob = step as u32 & 32767; let prob = step as u32 & 32767;
let (start, prob) = if step & 32768 != 0 { let (start, prob) = if step & 32768 == 0 {
(0, prob) (0, prob)
} else { } else {
(prob, ONE_PROB - prob) (prob, ONE_PROB - prob)
@@ -109,11 +91,10 @@ impl RansCoder {
pub struct CostCounter { pub struct CostCounter {
cost: f64, cost: f64,
log2_table: Vec<f64>, log2_table: Vec<f64>,
invert_bit_encoding: bool,
} }
impl CostCounter { impl CostCounter {
pub fn new(config: &Config) -> CostCounter { pub fn new() -> CostCounter {
let log2_table = (0..ONE_PROB) let log2_table = (0..ONE_PROB)
.map(|prob| { .map(|prob| {
let inv_prob = ONE_PROB as f64 / prob as f64; let inv_prob = ONE_PROB as f64 / prob as f64;
@@ -123,7 +104,6 @@ impl CostCounter {
CostCounter { CostCounter {
cost: 0.0, cost: 0.0,
log2_table, log2_table,
invert_bit_encoding: config.invert_bit_encoding,
} }
} }
@@ -138,7 +118,7 @@ impl CostCounter {
impl EntropyCoder for CostCounter { impl EntropyCoder for CostCounter {
fn encode_bit(&mut self, bit: bool, prob: u16) { fn encode_bit(&mut self, bit: bool, prob: u16) {
let prob = if bit ^ self.invert_bit_encoding { let prob = if !bit {
prob as u32 prob as u32
} else { } else {
ONE_PROB - prob as u32 ONE_PROB - prob as u32
@@ -153,22 +133,18 @@ pub struct RansDecoder<'a> {
use_bitstream: bool, use_bitstream: bool,
byte: u8, byte: u8,
bits_left: u8, bits_left: u8,
invert_bit_encoding: bool,
bitstream_is_big_endian: bool,
} }
const PROB_MASK: u32 = ONE_PROB - 1; const PROB_MASK: u32 = ONE_PROB - 1;
impl<'a> RansDecoder<'a> { impl<'a> RansDecoder<'a> {
pub fn new(data: &'a [u8], config: &Config) -> RansDecoder<'a> { pub fn new(data: &'a [u8], use_bitstream: bool) -> RansDecoder<'a> {
RansDecoder { RansDecoder {
data, data,
state: 0, state: 0,
use_bitstream: config.use_bitstream, use_bitstream,
byte: 0, byte: 0,
bits_left: 0, bits_left: 0,
invert_bit_encoding: config.invert_bit_encoding,
bitstream_is_big_endian: config.bitstream_is_big_endian,
} }
} }
@@ -187,13 +163,8 @@ impl<'a> RansDecoder<'a> {
self.data = &self.data[1..]; self.data = &self.data[1..];
self.bits_left = 8; self.bits_left = 8;
} }
if self.bitstream_is_big_endian { self.state = (self.state << 1) | (self.byte >> 7) as u32;
self.state = (self.state << 1) | (self.byte >> 7) as u32; self.byte <<= 1;
self.byte <<= 1;
} else {
self.state = (self.state << 1) | (self.byte & 1) as u32;
self.byte >>= 1;
}
self.bits_left -= 1; self.bits_left -= 1;
} }
} else { } else {
@@ -203,15 +174,15 @@ impl<'a> RansDecoder<'a> {
} }
} }
let bit = (self.state & PROB_MASK) < prob; let bit = (self.state & PROB_MASK) >= prob;
let (start, prob) = if bit { let (start, prob) = if bit {
(0, prob)
} else {
(prob, ONE_PROB - prob) (prob, ONE_PROB - prob)
} else {
(0, prob)
}; };
self.state = prob * (self.state >> PROB_BITS) + (self.state & PROB_MASK) - start; self.state = prob * (self.state >> PROB_BITS) + (self.state & PROB_MASK) - start;
bit ^ self.invert_bit_encoding bit
} }
} }

4
z80_unpacker/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*.bin
*.tap
*.sna
*.lst

11
z80_unpacker/Makefile Normal file
View File

@@ -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

View File

@@ -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 de,$4000 ; target VRAM
exx
; 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
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

19
z80_unpacker/readme.txt Normal file
View File

@@ -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)

301
z80_unpacker/unpack.asm Normal file
View File

@@ -0,0 +1,301 @@
;; 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, DE' (shadow DE) = 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, 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
.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)
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;
inc de
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;
; }
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;
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
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
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
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
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, CF=0
ld de,$7FFF ; length = 0 with positional-stop-bit
jr .loop_entry
.loop:
inc c ; context_index + 1
call decode_bit
rr d
rr e ; DE = length = (length >> 1) | (bit << 15);
inc c ; context_index += 2
.loop_entry:
call decode_bit
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
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