compact-pro 0.1.0

Read compressed files created by Compact Pro
Documentation
mod huffman_tree;
mod read_byte;

use huffman_tree::HuffmanTree;
use read_byte::ReadByte;
use std::io;

use crate::Error;

const CIRC_SIZE: usize = 8192;
const BLOCK_SIZE: usize = 0x1fff0;

#[derive(Default)]
struct GetBitContext {
    newbits: u32,
    bitsavail: usize,
}

impl GetBitContext {
    fn get<R: io::Read>(&mut self, reader: &mut R) -> std::io::Result<bool> {
        let b = (self.newbits >> 31) & 1;
        self.bitsavail -= 1;
        if self.bitsavail < 16 {
            self.newbits |= (reader.read_byte()? as u32) << 8;
            self.newbits |= reader.read_byte()? as u32;
            self.bitsavail += 16;
        }
        self.newbits <<= 1;
        Ok(b != 0)
    }
}

pub struct LzhReader<'a, R: io::Read + Sized> {
    inner: &'a mut R,
    uncompressed_size: isize,
    hufftree: HuffmanTree<256>,

    lz_ptr: usize,
    lz_buff: [u8; CIRC_SIZE],
    lz_lengths: HuffmanTree<64>,
    lz_offsets: HuffmanTree<128>,

    outstat: OutStatSeen,

    context: GetBitContext,
    output: Vec<u8>,
    savechar: u8,
}

#[derive(Debug, Copy, Clone, Default)]
enum OutStatSeen {
    #[default]
    None,
    Esc1,
    Esc2,
}

impl<'a, R: io::Read + io::Seek> LzhReader<'a, R> {
    pub fn try_new(reader: &'a mut R, uncompressed_size: usize) -> Result<Self, Error> {
        Ok(Self {
            inner: reader,

            lz_ptr: 0,
            context: GetBitContext::default(),
            uncompressed_size: uncompressed_size as isize,
            hufftree: HuffmanTree::new(),
            lz_lengths: HuffmanTree::new(),
            lz_offsets: HuffmanTree::new(),
            lz_buff: [Default::default(); CIRC_SIZE],
            outstat: Default::default(),
            output: Vec::with_capacity(uncompressed_size),
            savechar: 0,
        })
    }

    fn decode_bytes(&mut self) -> io::Result<()> {
        self.lz_buff[CIRC_SIZE - 3] = 0;
        self.lz_buff[CIRC_SIZE - 2] = 0;
        self.lz_buff[CIRC_SIZE - 1] = 0;
        self.lz_ptr = 0;

        while self.uncompressed_size != 0 {
            self.hufftree.read_from(&mut self.inner)?;
            self.lz_lengths.read_from(&mut self.inner)?;
            self.lz_offsets.read_from(&mut self.inner)?;

            let mut block_count = 0;

            self.context.newbits = (self.inner.read_byte()? as u32) << 8;
            self.context.newbits |= self.inner.read_byte()? as u32;
            self.context.newbits <<= 16;
            self.context.bitsavail = 16;

            while block_count < BLOCK_SIZE && self.uncompressed_size != 0 {
                if self.get_bit()? {
                    let ch = self.get_huff_byte_tree()?;
                    self.outch(ch);
                    block_count += 2;
                } else {
                    let mut lz_length = self.get_huff_byte_length()? as i32;
                    let lz_offset = self.get_huff_byte_offset()? as i32;
                    let lz_offset = (lz_offset << 6) | (self.get6bits()? as i32);
                    let mut bptr = self.lz_ptr as isize - lz_offset as isize;
                    while lz_length > 0 {
                        lz_length -= 1;
                        let byte =
                            self.lz_buff[(bptr & (CIRC_SIZE.cast_signed() - 1)).cast_unsigned()];
                        bptr += 1;

                        self.outch(byte);
                    }
                    block_count += 3;
                }
            }
        }

        Ok(())
    }

    fn outch(&mut self, mut ch: u8) {
        const ESC1: u8 = 0x81;
        const ESC2: u8 = 0x82;

        self.lz_buff[self.lz_ptr & (CIRC_SIZE - 1)] = ch;
        self.lz_ptr += 1;
        match self.outstat {
            OutStatSeen::None => {
                if ch == ESC1 && self.uncompressed_size != 1 {
                    self.outstat = OutStatSeen::Esc1;
                } else {
                    self.savechar = ch;
                    self.output.push(ch);
                    self.uncompressed_size -= 1;
                }
            }
            OutStatSeen::Esc1 => {
                if ch == ESC2 {
                    self.outstat = OutStatSeen::Esc2;
                } else {
                    self.savechar = ESC1;
                    self.output.push(ESC1);
                    self.uncompressed_size -= 1;
                    if self.uncompressed_size == 0 {
                        return;
                    }
                    if ch == ESC1 && self.uncompressed_size != 1 {
                        return;
                    }
                    self.outstat = OutStatSeen::None;
                    self.savechar = ch;
                    self.output.push(ch);
                    self.uncompressed_size -= 1;
                }
            }
            OutStatSeen::Esc2 => {
                self.outstat = OutStatSeen::None;
                if ch != 0 {
                    while ch - 1 != 0 {
                        ch -= 1;
                        self.output.push(self.savechar);
                        self.uncompressed_size -= 1;
                        if self.uncompressed_size == 0 {
                            return;
                        }
                    }
                } else {
                    self.output.push(ESC1);
                    self.uncompressed_size -= 1;
                    if self.uncompressed_size == 0 {
                        return;
                    }
                    self.savechar = ESC2;
                    self.output.push(self.savechar);
                    self.uncompressed_size -= 1;
                }
            }
        }
    }

    fn get6bits(&mut self) -> io::Result<u8> {
        let mut cn: u32;

        let LzhReader { context, inner, .. } = self;

        let b = (context.newbits >> 26) & 0x3f;
        context.bitsavail -= 6;
        context.newbits <<= 6;
        if context.bitsavail < 16 {
            cn = (inner.read_byte()? as u32) << 8;
            cn |= inner.read_byte()? as u32;
            context.newbits |= cn << (16 - context.bitsavail);
            context.bitsavail += 16;
        }

        Ok(b as u8)
    }

    fn get_huff_byte_tree(&mut self) -> io::Result<u8> {
        let LzhReader {
            inner,
            context,
            hufftree,
            ..
        } = self;

        let mut np = 0;
        while hufftree.entries[np].flag == 0 {
            let s = context.get(inner)?;
            np = if s {
                hufftree.entries[np].one as usize
            } else {
                hufftree.entries[np].zero as usize
            }
        }

        Ok(hufftree.entries[np].byte)
    }

    fn get_huff_byte_offset(&mut self) -> io::Result<u8> {
        let LzhReader {
            inner,
            context,
            lz_offsets,
            ..
        } = self;

        let mut np = 0;
        while lz_offsets.entries[np].flag == 0 {
            let s = context.get(inner)?;
            np = if s {
                lz_offsets.entries[np].one as usize
            } else {
                lz_offsets.entries[np].zero as usize
            }
        }

        Ok(lz_offsets.entries[np].byte)
    }

    fn get_huff_byte_length(&mut self) -> io::Result<u8> {
        let LzhReader {
            inner,
            context,
            lz_lengths,
            ..
        } = self;

        let mut np = 0;
        while lz_lengths.entries[np].flag == 0 {
            let s = context.get(inner)?;
            np = if s {
                lz_lengths.entries[np].one as usize
            } else {
                lz_lengths.entries[np].zero as usize
            }
        }

        Ok(lz_lengths.entries[np].byte)
    }

    fn get_bit(&mut self) -> io::Result<bool> {
        let LzhReader { context, inner, .. } = self;
        context.get(inner)
    }
}

impl<R: io::Read + std::io::Seek> std::io::Read for LzhReader<'_, R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        self.decode_bytes()?;
        buf.copy_from_slice(&self.output);

        Ok(buf.len())
    }
}