3 Commits

Author SHA1 Message Date
8c9e4311b9 first (poorly optimized) risc-v unpacker 2022-09-23 22:40:47 +02:00
31c31bdcfb clean up command line interface 2022-09-21 22:45:06 +02:00
8f33ae0b1e add reverse compression option 2022-09-21 21:37:30 +02:00
7 changed files with 292 additions and 86 deletions

14
Cargo.lock generated
View File

@@ -62,6 +62,12 @@ 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"
@@ -89,12 +95,6 @@ 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"
@@ -121,8 +121,8 @@ 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.1.0" version = "0.2.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"
pico-args = "0.4" lexopt = "0.2.1"
anyhow = "1" anyhow = "1"
pbr = "1" pbr = "1"

View File

@@ -1,4 +1,4 @@
build/unpack_riscv64: ../c_unpacker/main.c ../c_unpacker/unpack.c build/unpack_riscv64: ../c_unpacker/main.c unpack_riscv.S
mkdir -p build mkdir -p build
riscv64-linux-gnu-gcc -g -static -o $@ $^ riscv64-linux-gnu-gcc -g -static -o $@ $^
@@ -6,6 +6,11 @@ test_riscv64: build/unpack_riscv64
qemu-riscv64 $< test_data.upk /tmp/out.bin qemu-riscv64 $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin cmp test_data.bin /tmp/out.bin
build/unpack_riscv64.bin: unpack_riscv.S
mkdir -p build
riscv64-linux-gnu-gcc -c -o build/unpack_riscv64.o $?
riscv64-linux-gnu-objcopy -O binary --only-section=.text build/unpack_riscv64.o $@
build/unpack_armv6m: ../c_unpacker/main.c unpack_armv6m.S build/unpack_armv6m: ../c_unpacker/main.c unpack_armv6m.S
mkdir -p build mkdir -p build
arm-linux-gnueabihf-gcc -g -static -o $@ $^ arm-linux-gnueabihf-gcc -g -static -o $@ $^
@@ -15,6 +20,7 @@ test_armv6m: build/unpack_armv6m
cmp test_data.bin /tmp/out.bin cmp test_data.bin /tmp/out.bin
build/unpack_armv6m.bin: unpack_armv6m.S 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-gcc -march=armv6-m -c -o build/unpack_armv6m.o $?
arm-none-eabi-objcopy -O binary --only-section=.text build/unpack_armv6m.o $@ arm-none-eabi-objcopy -O binary --only-section=.text build/unpack_armv6m.o $@
@@ -26,5 +32,5 @@ test_c: build/unpack_c
$< test_data.upk /tmp/out.bin $< test_data.upk /tmp/out.bin
cmp test_data.bin /tmp/out.bin cmp test_data.bin /tmp/out.bin
sizes: build/unpack_armv6m.bin sizes: build/unpack_armv6m.bin build/unpack_riscv64.bin
ls -l build/*.bin ls -l build/*.bin

View File

@@ -0,0 +1,144 @@
.section .text
#define FRAME_SIZE (256+64*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
li x14, 256
beqz x9, .Lread_offset
jal upkr_decode_bit
beqz x15, .Lskip_offset
.Lread_offset:
jal t3, upkr_decode_number
addi x12, x9, -1
beqz x12, .Ldone
.Lskip_offset:
li x14, 256+64
jal t3, upkr_decode_number
1:
sub x15, x10, x12
lbu x15, (x15)
sb x15, (x10)
addi x10, x10, 1
addi x9, x9, -1
bnez x9, 1b
j .Lmainloop
.Lliteral:
li x14, 1
1:
jal upkr_decode_bit
slli x14, x14, 1
add x14, x14, x15
srli x9, x14, 8
beqz x9, 1b
sb x14, 0(x10)
addi x10, x10, 1
j .Lmainloop
.Ldone:
addi sp, sp, FRAME_SIZE
mv x8, x17
mv x9, t6
jr t4
// x14 context index
// return: x9 decoded number
upkr_decode_number:
mv t5, x14
li x9, 0
li x8, 1
1:
addi x14, x14, 1
jal upkr_decode_bit
beqz x15, 1f
addi x14, x14, 1
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: x15 decoded bit
upkr_decode_bit:
srli x15, x13, 12
beqz x15, upkr_load_byte
mv t0, x9
mv t1, x14
mv t2, x10
add x14, x14, sp
lbu x9, 0(x14)
andi x10, x13, 255
sltu x15, x10, x9
srli x13, x13, 8
beqz x15, .Lelse
mul x13, x13, x9
add x13, x13, x10
li x10, 256 + 8
sub x10, x10, x9
srli x10, x10, 4
add x9, x9, x10
j .Lendif
.Lelse:
li x16, 256
sub x16, x16, x9
mul x13, x13, x16
add x13, x13, x10
sub x13, x13, x9
addi x10, x9, 8
srli x10, x10, 4
sub x9, x9, x10
.Lendif:
sb x9, 0(x14)
mv x9, t0
mv x14, t1
mv x10, t2
ret

View File

@@ -9,21 +9,39 @@ 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,
}
impl Default for Config {
fn default() -> Config {
Config {
use_bitstream: false,
parity_contexts: 1,
}
}
}
pub fn pack( pub fn pack(
data: &[u8], data: &[u8],
level: u8, level: u8,
use_bitstream: bool, config: Config,
parity_contexts: usize,
progress_callback: Option<ProgressCallback>, progress_callback: Option<ProgressCallback>,
) -> Vec<u8> { ) -> Vec<u8> {
if level == 0 { if level == 0 {
greedy_packer::pack(data, use_bitstream, parity_contexts, progress_callback) greedy_packer::pack(
data,
config.use_bitstream,
config.parity_contexts,
progress_callback,
)
} else { } else {
parsing_packer::pack( parsing_packer::pack(
data, data,
level, level,
use_bitstream, config.use_bitstream,
parity_contexts, config.parity_contexts,
progress_callback, progress_callback,
) )
} }

View File

@@ -106,9 +106,9 @@ impl CoderState {
} }
} }
pub fn unpack(packed_data: &[u8], use_bitstream: bool, parity_contexts: usize) -> Vec<u8> { pub fn unpack(packed_data: &[u8], config: crate::Config) -> Vec<u8> {
let mut decoder = RansDecoder::new(packed_data, use_bitstream); let mut decoder = RansDecoder::new(packed_data, config.use_bitstream);
let mut contexts = ContextState::new((1 + 255) * parity_contexts + 1 + 64 + 64); let mut contexts = ContextState::new((1 + 255) * config.parity_contexts + 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;
@@ -131,17 +131,26 @@ pub fn unpack(packed_data: &[u8], use_bitstream: bool, parity_contexts: usize) -
} }
loop { loop {
let literal_base = result.len() % parity_contexts * 256; let literal_base = result.len() % config.parity_contexts * 256;
if decoder.decode_with_context(&mut contexts.context_mut(literal_base)) { if decoder.decode_with_context(&mut contexts.context_mut(literal_base)) {
if prev_was_match if prev_was_match
|| decoder.decode_with_context(&mut contexts.context_mut(256 * parity_contexts)) || decoder
.decode_with_context(&mut contexts.context_mut(256 * config.parity_contexts))
{ {
offset = decode_length(&mut decoder, &mut contexts, 256 * parity_contexts + 1) - 1; offset = decode_length(
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 1,
) - 1;
if offset == 0 { if offset == 0 {
break; break;
} }
} }
let length = decode_length(&mut decoder, &mut contexts, 256 * parity_contexts + 65); let length = decode_length(
&mut decoder,
&mut contexts,
256 * config.parity_contexts + 65,
);
for _ in 0..length { for _ in 0..length {
result.push(result[result.len() - offset]); result.push(result[result.len() - offset]);
} }

View File

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