pub fn decompress_ac21(source: &[u8], initial_offset: u32, length: u32, buffer: &mut [u8]) {
const MIN_SAFE: usize = 32;
if buffer.len() < MIN_SAFE {
let mut padded = vec![0u8; MIN_SAFE];
decompress_ac21_inner(source, initial_offset, length, &mut padded);
buffer.copy_from_slice(&padded[..buffer.len()]);
} else {
decompress_ac21_inner(source, initial_offset, length, buffer);
}
}
fn decompress_ac21_inner(source: &[u8], initial_offset: u32, length: u32, buffer: &mut [u8]) {
let mut source_offset: u32 = 0;
let mut match_length: u32 = 0;
let mut source_index = initial_offset;
let mut op_code: u32 = source[source_index as usize] as u32;
let mut dest_index: u32 = 0;
let end_index = source_index + length;
source_index += 1;
if source_index >= end_index {
return;
}
if (op_code & 0xF0) == 0x20 {
source_index += 3;
match_length = source[source_index as usize - 1] as u32;
match_length &= 0x07;
}
while source_index < end_index {
if match_length == 0 {
read_literal_length(source, &mut source_index, &mut op_code, &mut match_length);
}
copy_literal(source, &mut source_index, buffer, &mut dest_index, match_length);
if source_index >= end_index {
break;
}
dest_index = copy_decompressed_chunks(
source,
end_index,
buffer,
dest_index,
&mut source_index,
&mut match_length,
&mut op_code,
&mut source_offset,
);
}
}
fn copy_literal(
src: &[u8],
src_index: &mut u32,
dst: &mut [u8],
dst_index: &mut u32,
length: u32,
) {
let mut remaining = length;
while remaining >= 32 {
let si = *src_index as usize;
let di = *dst_index as usize;
copy_4b(src, si + 24, dst, di);
copy_4b(src, si + 28, dst, di + 4);
copy_4b(src, si + 16, dst, di + 8);
copy_4b(src, si + 20, dst, di + 12);
copy_4b(src, si + 8, dst, di + 16);
copy_4b(src, si + 12, dst, di + 20);
copy_4b(src, si, dst, di + 24);
copy_4b(src, si + 4, dst, di + 28);
*src_index += 32;
*dst_index += 32;
remaining -= 32;
}
if remaining > 0 {
let si = *src_index as usize;
let di = *dst_index as usize;
copy_n_reordered(src, si, dst, di, remaining as usize);
*src_index += remaining;
*dst_index += remaining;
}
}
fn copy_decompressed_chunks(
src: &[u8],
end_index: u32,
dst: &mut [u8],
mut dest_index: u32,
source_index: &mut u32,
match_length: &mut u32,
op_code: &mut u32,
source_offset: &mut u32,
) -> u32 {
*match_length = 0;
*op_code = src[*source_index as usize] as u32;
*source_index += 1;
read_instructions(src, source_index, op_code, match_length, source_offset);
loop {
copy_back_reference(dst, dest_index, *match_length, *source_offset);
dest_index += *match_length;
*match_length = *op_code & 0x07;
if *match_length != 0 || *source_index >= end_index {
break;
}
*op_code = src[*source_index as usize] as u32;
*source_index += 1;
if (*op_code >> 4) == 0 {
break;
}
if (*op_code >> 4) == 15 {
*op_code &= 15;
}
read_instructions(src, source_index, op_code, match_length, source_offset);
}
dest_index
}
fn read_instructions(
buffer: &[u8],
source_index: &mut u32,
op_code: &mut u32,
length: &mut u32,
source_offset: &mut u32,
) {
match *op_code >> 4 {
0 => {
*length = (*op_code & 0x0F) + 0x13;
*source_offset = buffer[*source_index as usize] as u32;
*source_index += 1;
*op_code = buffer[*source_index as usize] as u32;
*source_index += 1;
*length = (*op_code >> 3 & 0x10) + *length;
*source_offset = ((*op_code & 0x78) << 5) + 1 + *source_offset;
}
1 => {
*length = (*op_code & 0x0F) + 3;
*source_offset = buffer[*source_index as usize] as u32;
*source_index += 1;
*op_code = buffer[*source_index as usize] as u32;
*source_index += 1;
*source_offset = ((*op_code & 0xF8) << 5) + 1 + *source_offset;
}
2 => {
*source_offset = buffer[*source_index as usize] as u32;
*source_index += 1;
*source_offset =
((buffer[*source_index as usize] as u32) << 8 & 0xFF00) | *source_offset;
*source_index += 1;
*length = *op_code & 7;
if (*op_code & 8) == 0 {
*op_code = buffer[*source_index as usize] as u32;
*source_index += 1;
*length = (*op_code & 0xF8) + *length;
} else {
*source_offset += 1;
*length = ((buffer[*source_index as usize] as u32) << 3) + *length;
*source_index += 1;
*op_code = buffer[*source_index as usize] as u32;
*source_index += 1;
*length = ((*op_code & 0xF8) << 8) + *length + 0x100;
}
}
_ => {
*length = *op_code >> 4;
*source_offset = *op_code & 0x0F;
*op_code = buffer[*source_index as usize] as u32;
*source_index += 1;
*source_offset = ((*op_code & 0xF8) << 1) + *source_offset + 1;
}
}
}
fn read_literal_length(
buffer: &[u8],
source_index: &mut u32,
op_code: &mut u32,
length: &mut u32,
) {
*length = *op_code + 8;
if *length == 0x17 {
let mut n = buffer[*source_index as usize] as u32;
*source_index += 1;
*length += n;
if n == 0xFF {
loop {
n = buffer[*source_index as usize] as u32;
*source_index += 1;
n |= (buffer[*source_index as usize] as u32) << 8;
*source_index += 1;
*length += n;
if n != 0xFFFF {
break;
}
}
}
}
}
fn copy_back_reference(dst: &mut [u8], dst_index: u32, length: u32, src_offset: u32) {
if src_offset > dst_index || length == 0 {
return;
}
let mut initial_index = dst_index - src_offset;
let max_index = initial_index + length;
let mut di = dst_index;
while initial_index < max_index {
if di as usize >= dst.len() || initial_index as usize >= dst.len() {
break;
}
dst[di as usize] = dst[initial_index as usize];
di += 1;
initial_index += 1;
}
}
#[doc(hidden)]
pub fn decompress_copy_n_reordered(src: &[u8], si: usize, dst: &mut [u8], di: usize, length: usize) {
copy_n_reordered(src, si, dst, di, length);
}
fn copy_n_reordered(src: &[u8], si: usize, dst: &mut [u8], di: usize, length: usize) {
match length {
0 => {}
1 => copy_1b(src, si, dst, di),
2 => copy_2b(src, si, dst, di),
3 => copy_3b(src, si, dst, di),
4 => copy_4b(src, si, dst, di),
5 => {
copy_1b(src, si + 4, dst, di);
copy_4b(src, si, dst, di + 1);
}
6 => {
copy_1b(src, si + 5, dst, di);
copy_4b(src, si + 1, dst, di + 1);
copy_1b(src, si, dst, di + 5);
}
7 => {
copy_2b(src, si + 5, dst, di);
copy_4b(src, si + 1, dst, di + 2);
copy_1b(src, si, dst, di + 6);
}
8 => copy_8b(src, si, dst, di),
9 => {
copy_1b(src, si + 8, dst, di);
copy_8b(src, si, dst, di + 1);
}
10 => {
copy_1b(src, si + 9, dst, di);
copy_8b(src, si + 1, dst, di + 1);
copy_1b(src, si, dst, di + 9);
}
11 => {
copy_2b(src, si + 9, dst, di);
copy_8b(src, si + 1, dst, di + 2);
copy_1b(src, si, dst, di + 10);
}
12 => {
copy_4b(src, si + 8, dst, di);
copy_8b(src, si, dst, di + 4);
}
13 => {
copy_1b(src, si + 12, dst, di);
copy_4b(src, si + 8, dst, di + 1);
copy_8b(src, si, dst, di + 5);
}
14 => {
copy_1b(src, si + 13, dst, di);
copy_4b(src, si + 9, dst, di + 1);
copy_8b(src, si + 1, dst, di + 5);
copy_1b(src, si, dst, di + 13);
}
15 => {
copy_2b(src, si + 13, dst, di);
copy_4b(src, si + 9, dst, di + 2);
copy_8b(src, si + 1, dst, di + 6);
copy_1b(src, si, dst, di + 14);
}
16 => copy_16b(src, si, dst, di),
17 => {
copy_8b(src, si + 9, dst, di);
copy_1b(src, si + 8, dst, di + 8);
copy_8b(src, si, dst, di + 9);
}
18 => {
copy_1b(src, si + 17, dst, di);
copy_16b(src, si + 1, dst, di + 1);
copy_1b(src, si, dst, di + 17);
}
19 => {
copy_3b(src, si + 16, dst, di);
copy_16b(src, si, dst, di + 3);
}
20 => {
copy_4b(src, si + 16, dst, di);
copy_8b(src, si + 8, dst, di + 4);
copy_8b(src, si, dst, di + 12);
}
21 => {
copy_1b(src, si + 20, dst, di);
copy_4b(src, si + 16, dst, di + 1);
copy_8b(src, si + 8, dst, di + 5);
copy_8b(src, si, dst, di + 13);
}
22 => {
copy_2b(src, si + 20, dst, di);
copy_4b(src, si + 16, dst, di + 2);
copy_8b(src, si + 8, dst, di + 6);
copy_8b(src, si, dst, di + 14);
}
23 => {
copy_3b(src, si + 20, dst, di);
copy_4b(src, si + 16, dst, di + 3);
copy_8b(src, si + 8, dst, di + 7);
copy_8b(src, si, dst, di + 15);
}
24 => {
copy_8b(src, si + 16, dst, di);
copy_16b(src, si, dst, di + 8);
}
25 => {
copy_8b(src, si + 17, dst, di);
copy_1b(src, si + 16, dst, di + 8);
copy_16b(src, si, dst, di + 9);
}
26 => {
copy_1b(src, si + 25, dst, di);
copy_8b(src, si + 17, dst, di + 1);
copy_1b(src, si + 16, dst, di + 9);
copy_16b(src, si, dst, di + 10);
}
27 => {
copy_2b(src, si + 25, dst, di);
copy_8b(src, si + 17, dst, di + 2);
copy_1b(src, si + 16, dst, di + 10);
copy_16b(src, si, dst, di + 11);
}
28 => {
copy_4b(src, si + 24, dst, di);
copy_8b(src, si + 16, dst, di + 4);
copy_8b(src, si + 8, dst, di + 12);
copy_8b(src, si, dst, di + 20);
}
29 => {
copy_1b(src, si + 28, dst, di);
copy_4b(src, si + 24, dst, di + 1);
copy_8b(src, si + 16, dst, di + 5);
copy_8b(src, si + 8, dst, di + 13);
copy_8b(src, si, dst, di + 21);
}
30 => {
copy_2b(src, si + 28, dst, di);
copy_4b(src, si + 24, dst, di + 2);
copy_8b(src, si + 16, dst, di + 6);
copy_8b(src, si + 8, dst, di + 14);
copy_8b(src, si, dst, di + 22);
}
31 => {
copy_1b(src, si + 30, dst, di);
copy_4b(src, si + 26, dst, di + 1);
copy_8b(src, si + 18, dst, di + 5);
copy_8b(src, si + 10, dst, di + 13);
copy_8b(src, si + 2, dst, di + 21);
copy_2b(src, si, dst, di + 29);
}
_ => unreachable!("copy_n_reordered called with length >= 32"),
}
}
#[inline(always)]
fn copy_1b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
dst[di] = src[si];
}
#[inline(always)]
fn copy_2b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
dst[di] = src[si + 1];
dst[di + 1] = src[si];
}
#[inline(always)]
fn copy_3b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
dst[di] = src[si + 2];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si];
}
#[inline(always)]
fn copy_4b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = src[si + 3];
}
#[inline(always)]
fn copy_8b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
copy_4b(src, si, dst, di);
copy_4b(src, si + 4, dst, di + 4);
}
#[inline(always)]
fn copy_16b(src: &[u8], si: usize, dst: &mut [u8], di: usize) {
copy_8b(src, si + 8, dst, di);
copy_8b(src, si, dst, di + 8);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decompress_empty() {
let source = [0u8; 1];
let mut buffer = [0u8; 16];
decompress_ac21(&source, 0, 0, &mut buffer);
assert!(buffer.iter().all(|&b| b == 0));
}
#[test]
fn test_copy_back_reference() {
let mut buf = [0u8; 16];
buf[0] = 0xAA;
buf[1] = 0xBB;
buf[2] = 0xCC;
copy_back_reference(&mut buf, 3, 3, 3);
assert_eq!(buf[3], 0xAA);
assert_eq!(buf[4], 0xBB);
assert_eq!(buf[5], 0xCC);
}
#[test]
fn test_copy_back_reference_overlapping() {
let mut buf = [0u8; 16];
buf[0] = 0x42;
copy_back_reference(&mut buf, 1, 4, 1);
assert_eq!(&buf[1..5], &[0x42, 0x42, 0x42, 0x42]);
}
}