mirror of
https://github.com/exoticorn/upkr.git
synced 2026-06-29 06:19:41 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 22582d43a8 | |||
| f467b6a454 | |||
| a1dabaf7f9 | |||
| 75e375fb1f | |||
| c7ea11bce3 | |||
| 02d20867ee | |||
| 511ddefc08 | |||
| d30baaa91f | |||
| 919a892ef0 | |||
| ea5c0b1b15 | |||
| a19ec2abb7 | |||
| 7b051113e1 | |||
| f1f1c64a76 | |||
| 36cb6d77b5 | |||
| 629c5fce7d | |||
| a205473ad6 | |||
| 4903ac3786 | |||
| f817dc9254 | |||
| d93aec186c | |||
| 3902425922 | |||
| 2e7983fc65 | |||
| f7f891e154 |
@@ -0,0 +1,5 @@
|
|||||||
|
unpack
|
||||||
|
unpack_bitstream
|
||||||
|
unpack_debug
|
||||||
|
*.upk
|
||||||
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
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 !!!
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
Executable
+50
@@ -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
|
||||||
|
|
||||||
+12
-10
@@ -1,12 +1,12 @@
|
|||||||
use crate::rans::{PROB_BITS, ONE_PROB};
|
use crate::rans::{ONE_PROB, PROB_BITS};
|
||||||
|
|
||||||
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<u16>,
|
contexts: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Context<'a> {
|
pub struct Context<'a> {
|
||||||
@@ -17,7 +17,7 @@ pub struct Context<'a> {
|
|||||||
impl ContextState {
|
impl ContextState {
|
||||||
pub fn new(size: usize) -> ContextState {
|
pub fn new(size: usize) -> ContextState {
|
||||||
ContextState {
|
ContextState {
|
||||||
contexts: vec![INIT_PROB; size],
|
contexts: vec![INIT_PROB as u8; size],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +28,17 @@ impl ContextState {
|
|||||||
|
|
||||||
impl<'a> Context<'a> {
|
impl<'a> Context<'a> {
|
||||||
pub fn prob(&self) -> u16 {
|
pub fn prob(&self) -> u16 {
|
||||||
self.state.contexts[self.index]
|
self.state.contexts[self.index] as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
self.state.contexts[self.index] = if bit {
|
if bit {
|
||||||
old + ((ONE_PROB - old as u32 + UPDATE_ADD) >> UPDATE_RATE) as u16
|
self.state.contexts[self.index] =
|
||||||
|
old - ((old as i32 + UPDATE_ADD) >> UPDATE_RATE) as u8;
|
||||||
} else {
|
} else {
|
||||||
old - ((old + UPDATE_ADD as u16) >> UPDATE_RATE)
|
self.state.contexts[self.index] =
|
||||||
};
|
old + (((ONE_PROB as i32 - old as i32) + UPDATE_ADD) >> UPDATE_RATE) as u8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,13 @@ use crate::match_finder::MatchFinder;
|
|||||||
use crate::rans::RansCoder;
|
use crate::rans::RansCoder;
|
||||||
use crate::ProgressCallback;
|
use crate::ProgressCallback;
|
||||||
|
|
||||||
pub fn pack(data: &[u8], mut progress_callback: Option<ProgressCallback>) -> Vec<u8> {
|
pub fn pack(
|
||||||
|
data: &[u8],
|
||||||
|
use_bitstream: bool,
|
||||||
|
mut progress_callback: Option<ProgressCallback>,
|
||||||
|
) -> Vec<u8> {
|
||||||
let mut match_finder = MatchFinder::new(data);
|
let mut match_finder = MatchFinder::new(data);
|
||||||
let mut rans_coder = RansCoder::new();
|
let mut rans_coder = RansCoder::new(use_bitstream);
|
||||||
let mut state = lz::CoderState::new();
|
let mut state = lz::CoderState::new();
|
||||||
|
|
||||||
let mut pos = 0;
|
let mut pos = 0;
|
||||||
|
|||||||
+18
-4
@@ -2,17 +2,31 @@ mod context_state;
|
|||||||
mod greedy_packer;
|
mod greedy_packer;
|
||||||
mod lz;
|
mod lz;
|
||||||
mod match_finder;
|
mod match_finder;
|
||||||
mod rans;
|
|
||||||
mod parsing_packer;
|
mod parsing_packer;
|
||||||
|
mod rans;
|
||||||
|
|
||||||
pub use lz::unpack;
|
pub use lz::unpack;
|
||||||
|
|
||||||
pub type ProgressCallback<'a> = &'a mut dyn FnMut(usize);
|
pub type ProgressCallback<'a> = &'a mut dyn FnMut(usize);
|
||||||
|
|
||||||
pub fn pack(data: &[u8], level: u8, progress_callback: Option<ProgressCallback>) -> Vec<u8> {
|
pub fn pack(
|
||||||
|
data: &[u8],
|
||||||
|
level: u8,
|
||||||
|
use_bitstream: bool,
|
||||||
|
progress_callback: Option<ProgressCallback>,
|
||||||
|
) -> Vec<u8> {
|
||||||
if level == 0 {
|
if level == 0 {
|
||||||
greedy_packer::pack(data, progress_callback)
|
greedy_packer::pack(data, use_bitstream, progress_callback)
|
||||||
} else {
|
} else {
|
||||||
parsing_packer::pack(data, level, progress_callback)
|
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.
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@ impl Op {
|
|||||||
|
|
||||||
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState) {
|
pub fn encode_eof(coder: &mut dyn EntropyCoder, state: &mut CoderState) {
|
||||||
encode_bit(coder, state, 0, true);
|
encode_bit(coder, state, 0, true);
|
||||||
|
if !state.prev_was_match {
|
||||||
encode_bit(coder, state, 256, true);
|
encode_bit(coder, state, 256, true);
|
||||||
|
}
|
||||||
encode_length(coder, state, 257, 1);
|
encode_length(coder, state, 257, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ fn encode_length(
|
|||||||
pub struct CoderState {
|
pub struct CoderState {
|
||||||
contexts: ContextState,
|
contexts: ContextState,
|
||||||
last_offset: u32,
|
last_offset: u32,
|
||||||
prev_was_match: bool
|
prev_was_match: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CoderState {
|
impl CoderState {
|
||||||
@@ -83,7 +85,7 @@ impl CoderState {
|
|||||||
CoderState {
|
CoderState {
|
||||||
contexts: ContextState::new(1 + 255 + 1 + 64 + 64),
|
contexts: ContextState::new(1 + 255 + 1 + 64 + 64),
|
||||||
last_offset: 0,
|
last_offset: 0,
|
||||||
prev_was_match: false
|
prev_was_match: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,8 +94,8 @@ impl CoderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unpack(packed_data: &[u8]) -> Vec<u8> {
|
pub fn unpack(packed_data: &[u8], use_bitstream: bool) -> Vec<u8> {
|
||||||
let mut decoder = RansDecoder::new(packed_data);
|
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 + 1 + 64 + 64);
|
||||||
let mut result = vec![];
|
let mut result = vec![];
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
|
|||||||
+10
-3
@@ -9,6 +9,7 @@ fn main() -> Result<()> {
|
|||||||
None => print_help(),
|
None => print_help(),
|
||||||
Some("pack") => {
|
Some("pack") => {
|
||||||
let level = args.opt_value_from_str(["-l", "--level"])?.unwrap_or(2u8);
|
let level = args.opt_value_from_str(["-l", "--level"])?.unwrap_or(2u8);
|
||||||
|
let use_bitstream = args.contains(["-b", "--bitstream"]);
|
||||||
|
|
||||||
let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
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 outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
@@ -21,6 +22,7 @@ fn main() -> Result<()> {
|
|||||||
let packed_data = upkr::pack(
|
let packed_data = upkr::pack(
|
||||||
&data,
|
&data,
|
||||||
level,
|
level,
|
||||||
|
use_bitstream,
|
||||||
Some(&mut |pos| {
|
Some(&mut |pos| {
|
||||||
pb.set(pos as u64);
|
pb.set(pos as u64);
|
||||||
}),
|
}),
|
||||||
@@ -36,12 +38,14 @@ fn main() -> Result<()> {
|
|||||||
File::create(outfile)?.write_all(&packed_data)?;
|
File::create(outfile)?.write_all(&packed_data)?;
|
||||||
}
|
}
|
||||||
Some("unpack") => {
|
Some("unpack") => {
|
||||||
|
let use_bitstream = args.contains(["-b", "--bitstream"]);
|
||||||
|
|
||||||
let infile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
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 outfile = args.free_from_os_str::<PathBuf, bool>(|s| Ok(s.into()))?;
|
||||||
|
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
File::open(infile)?.read_to_end(&mut data)?;
|
File::open(infile)?.read_to_end(&mut data)?;
|
||||||
let packed_data = upkr::unpack(&data);
|
let packed_data = upkr::unpack(&data, use_bitstream);
|
||||||
File::create(outfile)?.write_all(&packed_data)?;
|
File::create(outfile)?.write_all(&packed_data)?;
|
||||||
}
|
}
|
||||||
Some(other) => {
|
Some(other) => {
|
||||||
@@ -54,7 +58,10 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
fn print_help() {
|
fn print_help() {
|
||||||
eprintln!("Usage:");
|
eprintln!("Usage:");
|
||||||
eprintln!(" upkr pack [-l level(0-9)] <infile> <outfile>");
|
eprintln!(" upkr pack [-b] [-l level(0-9)] <infile> <outfile>");
|
||||||
eprintln!(" upkr unpack <infile> <outfile>");
|
eprintln!(" upkr unpack [-b] <infile> <outfile>");
|
||||||
|
eprintln!();
|
||||||
|
eprintln!(" -b, --bitstream bitstream mode");
|
||||||
|
eprintln!(" -l, --level N compression level 0-9");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ 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(data: &[u8], level: u8, progress_cb: Option<ProgressCallback>) -> Vec<u8> {
|
pub fn pack(data: &[u8], level: u8, use_bitstream: bool, progress_cb: Option<ProgressCallback>) -> Vec<u8> {
|
||||||
let mut parse = parse(data, Config::from_level(level), progress_cb);
|
let mut parse = parse(data, Config::from_level(level), progress_cb);
|
||||||
let mut ops = vec![];
|
let mut ops = vec![];
|
||||||
while let Some(link) = parse {
|
while let Some(link) = parse {
|
||||||
@@ -14,7 +14,7 @@ pub fn pack(data: &[u8], level: u8, progress_cb: Option<ProgressCallback>) -> Ve
|
|||||||
parse = link.prev.clone();
|
parse = link.prev.clone();
|
||||||
}
|
}
|
||||||
let mut state = lz::CoderState::new();
|
let mut state = lz::CoderState::new();
|
||||||
let mut coder = RansCoder::new();
|
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);
|
op.encode(&mut coder, &mut state);
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-22
@@ -1,6 +1,5 @@
|
|||||||
use crate::context_state::Context;
|
use crate::context_state::Context;
|
||||||
|
|
||||||
const L_BITS: u32 = 12;
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -13,43 +12,75 @@ pub trait EntropyCoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RansCoder(Vec<u16>);
|
pub struct RansCoder {
|
||||||
|
bits: Vec<u16>,
|
||||||
|
use_bitstream: 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.0.push(prob | ((bit as u16) << 15));
|
self.bits.push(prob | ((bit as u16) << 15));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RansCoder {
|
impl RansCoder {
|
||||||
pub fn new() -> RansCoder {
|
pub fn new(use_bitstream: bool) -> RansCoder {
|
||||||
RansCoder(Vec::new())
|
RansCoder {
|
||||||
|
bits: Vec::new(),
|
||||||
|
use_bitstream,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self) -> Vec<u8> {
|
pub fn finish(self) -> Vec<u8> {
|
||||||
let mut buffer = vec![];
|
let mut buffer = vec![];
|
||||||
let mut state = 1 << L_BITS;
|
let l_bits: u32 = if self.use_bitstream { 15 } else { 12 };
|
||||||
|
let mut state = 1 << l_bits;
|
||||||
|
|
||||||
const MAX_STATE_FACTOR: u32 = 1 << (L_BITS + 8 - PROB_BITS);
|
let mut byte = 0u8;
|
||||||
for step in self.0.into_iter().rev() {
|
let mut bit = 0;
|
||||||
|
let mut flush_state: Box<dyn FnMut(&mut u32)> = if self.use_bitstream {
|
||||||
|
Box::new(|state: &mut u32| {
|
||||||
|
byte |= ((*state & 1) as u8) << bit;
|
||||||
|
bit += 1;
|
||||||
|
if bit == 8 {
|
||||||
|
buffer.push(byte);
|
||||||
|
byte = 0;
|
||||||
|
bit = 0;
|
||||||
|
}
|
||||||
|
*state >>= 1;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Box::new(|state: &mut u32| {
|
||||||
|
buffer.push(*state as u8);
|
||||||
|
*state >>= 8;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let num_flush_bits = if self.use_bitstream { 1 } else { 8 };
|
||||||
|
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 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)
|
||||||
};
|
};
|
||||||
let max_state = MAX_STATE_FACTOR * prob;
|
let max_state = max_state_factor * prob;
|
||||||
while state >= max_state {
|
while state >= max_state {
|
||||||
buffer.push(state as u8);
|
flush_state(&mut state);
|
||||||
state >>= 8;
|
|
||||||
}
|
}
|
||||||
state = ((state / prob) << PROB_BITS) + (state % prob) + start;
|
state = ((state / prob) << PROB_BITS) + (state % prob) + start;
|
||||||
}
|
}
|
||||||
|
|
||||||
while state > 0 {
|
while state > 0 {
|
||||||
buffer.push(state as u8);
|
flush_state(&mut state);
|
||||||
state >>= 8;
|
}
|
||||||
|
|
||||||
|
drop(flush_state);
|
||||||
|
|
||||||
|
if self.use_bitstream && byte != 0 {
|
||||||
|
buffer.push(byte);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.reverse();
|
buffer.reverse();
|
||||||
@@ -87,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 {
|
let prob = if !bit {
|
||||||
prob as u32
|
prob as u32
|
||||||
} else {
|
} else {
|
||||||
ONE_PROB - prob as u32
|
ONE_PROB - prob as u32
|
||||||
@@ -99,14 +130,22 @@ impl EntropyCoder for CostCounter {
|
|||||||
pub struct RansDecoder<'a> {
|
pub struct RansDecoder<'a> {
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
state: u32,
|
state: u32,
|
||||||
|
use_bitstream: bool,
|
||||||
|
byte: u8,
|
||||||
|
bits_left: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PROB_MASK: u32 = ONE_PROB - 1;
|
const PROB_MASK: u32 = ONE_PROB - 1;
|
||||||
const L: u32 = 1 << L_BITS;
|
|
||||||
|
|
||||||
impl<'a> RansDecoder<'a> {
|
impl<'a> RansDecoder<'a> {
|
||||||
pub fn new(data: &'a [u8]) -> RansDecoder<'a> {
|
pub fn new(data: &'a [u8], use_bitstream: bool) -> RansDecoder<'a> {
|
||||||
RansDecoder { data, state: 0 }
|
RansDecoder {
|
||||||
|
data,
|
||||||
|
state: 0,
|
||||||
|
use_bitstream,
|
||||||
|
byte: 0,
|
||||||
|
bits_left: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decode_with_context(&mut self, context: &mut Context) -> bool {
|
pub fn decode_with_context(&mut self, context: &mut Context) -> bool {
|
||||||
@@ -117,17 +156,30 @@ impl<'a> RansDecoder<'a> {
|
|||||||
|
|
||||||
pub fn decode_bit(&mut self, prob: u16) -> bool {
|
pub fn decode_bit(&mut self, prob: u16) -> bool {
|
||||||
let prob = prob as u32;
|
let prob = prob as u32;
|
||||||
while self.state < L {
|
if self.use_bitstream {
|
||||||
|
while self.state < 32768 {
|
||||||
|
if self.bits_left == 0 {
|
||||||
|
self.byte = self.data[0];
|
||||||
|
self.data = &self.data[1..];
|
||||||
|
self.bits_left = 8;
|
||||||
|
}
|
||||||
|
self.state = (self.state << 1) | (self.byte >> 7) as u32;
|
||||||
|
self.byte <<= 1;
|
||||||
|
self.bits_left -= 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while self.state < 4096 {
|
||||||
self.state = (self.state << 8) | self.data[0] as u32;
|
self.state = (self.state << 8) | self.data[0] as u32;
|
||||||
self.data = &self.data[1..];
|
self.data = &self.data[1..];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
*.bin
|
||||||
|
*.tap
|
||||||
|
*.sna
|
||||||
|
*.lst
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user