use crush_core::error::{CrushError, PluginError, Result};
const NUM_STREAMS: usize = 32;
const MAX_CODE_LEN: usize = 15;
const NUM_LITLEN_CODES: usize = 286;
const NUM_DIST_CODES: usize = 30;
const MAX_MATCH_DISTANCE: usize = 32768;
const MIN_MATCH_LEN: usize = 3;
const MAX_MATCH_LEN: usize = 258;
const LENGTH_BASE: [u16; 29] = [
3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131,
163, 195, 227, 258,
];
const LENGTH_EXTRA: [u8; 29] = [
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0,
];
const DIST_BASE: [u16; 30] = [
1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537,
2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577,
];
const DIST_EXTRA: [u8; 30] = [
0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13,
13,
];
struct BitReader<'a> {
data: &'a [u8],
byte_pos: usize,
bit_pos: u8,
}
impl<'a> BitReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
byte_pos: 0,
bit_pos: 0,
}
}
fn read_bits(&mut self, n: u8) -> Result<u16> {
let mut result: u16 = 0;
for i in 0..n {
if self.byte_pos >= self.data.len() {
return Err(CrushError::InvalidFormat(
"GDeflate: unexpected end of bitstream".to_owned(),
));
}
let bit = (self.data[self.byte_pos] >> self.bit_pos) & 1;
result |= u16::from(bit) << i;
self.bit_pos += 1;
if self.bit_pos >= 8 {
self.bit_pos = 0;
self.byte_pos += 1;
}
}
Ok(result)
}
}
struct BitWriter {
data: Vec<u8>,
current_byte: u8,
bit_pos: u8,
}
impl BitWriter {
fn new() -> Self {
Self {
data: Vec::new(),
current_byte: 0,
bit_pos: 0,
}
}
fn write_bits(&mut self, value: u16, n: u8) {
for i in 0..n {
if (value >> i) & 1 != 0 {
self.current_byte |= 1 << self.bit_pos;
}
self.bit_pos += 1;
if self.bit_pos >= 8 {
self.data.push(self.current_byte);
self.current_byte = 0;
self.bit_pos = 0;
}
}
}
fn write_bits_msb(&mut self, value: u16, n: u8) {
for i in (0..n).rev() {
let bit = (value >> i) & 1;
if bit != 0 {
self.current_byte |= 1 << self.bit_pos;
}
self.bit_pos += 1;
if self.bit_pos >= 8 {
self.data.push(self.current_byte);
self.current_byte = 0;
self.bit_pos = 0;
}
}
}
fn flush(&mut self) {
if self.bit_pos > 0 {
self.data.push(self.current_byte);
self.current_byte = 0;
self.bit_pos = 0;
}
}
fn pad_to_u32(&mut self) {
self.flush();
while !self.data.len().is_multiple_of(4) {
self.data.push(0);
}
}
}
struct HuffmanTable {
min_code: [u16; MAX_CODE_LEN + 1],
max_code: [i32; MAX_CODE_LEN + 1],
offsets: [usize; MAX_CODE_LEN + 1],
symbols: Vec<u16>,
}
impl HuffmanTable {
fn from_code_lengths(code_lengths: &[u8]) -> Result<Self> {
let mut bl_count = [0u16; MAX_CODE_LEN + 1];
for &cl in code_lengths {
if usize::from(cl) > MAX_CODE_LEN {
return Err(CrushError::InvalidFormat(format!(
"GDeflate: Huffman code length {cl} exceeds max {MAX_CODE_LEN}"
)));
}
bl_count[usize::from(cl)] += 1;
}
bl_count[0] = 0;
let mut next_code = [0u16; MAX_CODE_LEN + 1];
let mut code: u16 = 0;
for bits in 1..=MAX_CODE_LEN {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
let total_symbols: usize = bl_count.iter().map(|&c| usize::from(c)).sum();
let mut symbols = vec![0u16; total_symbols];
let mut min_code = [0u16; MAX_CODE_LEN + 1];
let mut max_code = [-1i32; MAX_CODE_LEN + 1];
let mut offsets = [0usize; MAX_CODE_LEN + 1];
let mut offset = 0;
for bits in 1..=MAX_CODE_LEN {
offsets[bits] = offset;
min_code[bits] = next_code[bits];
if bl_count[bits] > 0 {
max_code[bits] = i32::from(next_code[bits] + bl_count[bits] - 1);
}
offset += usize::from(bl_count[bits]);
}
let mut code_idx = [0usize; MAX_CODE_LEN + 1];
for (sym, &cl) in code_lengths.iter().enumerate() {
let len = usize::from(cl);
if len > 0 {
let idx = offsets[len] + code_idx[len];
if idx < symbols.len() {
#[allow(clippy::cast_possible_truncation)]
{
symbols[idx] = sym as u16; }
}
code_idx[len] += 1;
}
}
Ok(Self {
min_code,
max_code,
offsets,
symbols,
})
}
fn decode(&self, reader: &mut BitReader<'_>) -> Result<u16> {
let mut code: u16 = 0;
for bits in 1..=MAX_CODE_LEN {
let bit = reader.read_bits(1)?;
code = (code << 1) | bit;
if i32::from(code) <= self.max_code[bits] {
let idx = self.offsets[bits] + usize::from(code - self.min_code[bits]);
if idx < self.symbols.len() {
return Ok(self.symbols[idx]);
}
}
}
Err(CrushError::InvalidFormat(
"GDeflate: invalid Huffman code".to_owned(),
))
}
fn fixed_litlen() -> Result<Self> {
let mut code_lengths = [0u8; NUM_LITLEN_CODES];
for cl in &mut code_lengths[0..=143] {
*cl = 8;
}
for cl in &mut code_lengths[144..=255] {
*cl = 9;
}
for cl in &mut code_lengths[256..=279] {
*cl = 7;
}
for cl in &mut code_lengths[280..=285] {
*cl = 8;
}
Self::from_code_lengths(&code_lengths)
}
fn fixed_dist() -> Result<Self> {
let code_lengths = [5u8; NUM_DIST_CODES];
Self::from_code_lengths(&code_lengths)
}
}
#[derive(Debug, Clone)]
enum LzSymbol {
Literal(u8),
Match { length: u16, distance: u16 },
EndOfBlock,
}
fn hash3(data: &[u8], pos: usize) -> usize {
let b0 = u32::from(data[pos]);
let b1 = u32::from(data[pos + 1]);
let b2 = u32::from(data[pos + 2]);
((b0 << 10) ^ (b1 << 5) ^ b2) as usize & 0xFFFF
}
fn lz77_find_matches(data: &[u8]) -> Vec<LzSymbol> {
let mut symbols = Vec::with_capacity(data.len());
let mut pos = 0;
let mut hash_table = vec![u32::MAX; 65536];
while pos < data.len() {
let mut best_len = 0usize;
let mut best_dist = 0usize;
if pos + 2 < data.len() {
let h = hash3(data, pos);
let prev = hash_table[h] as usize;
#[allow(clippy::cast_possible_truncation)]
{
hash_table[h] = pos as u32; }
if prev != u32::MAX as usize {
let dist = pos - prev;
if dist > 0 && dist <= MAX_MATCH_DISTANCE {
let max_len = std::cmp::min(MAX_MATCH_LEN, data.len() - pos);
let mut len = 0;
while len < max_len && data[prev + len] == data[pos + len] {
len += 1;
}
if len >= MIN_MATCH_LEN {
best_len = len;
best_dist = dist;
}
}
}
}
if best_len >= MIN_MATCH_LEN {
#[allow(clippy::cast_possible_truncation)]
symbols.push(LzSymbol::Match {
length: best_len as u16, distance: best_dist as u16, });
for j in 1..best_len {
if pos + j + 2 < data.len() {
let h = hash3(data, pos + j);
#[allow(clippy::cast_possible_truncation)]
{
hash_table[h] = (pos + j) as u32; }
}
}
pos += best_len;
} else {
symbols.push(LzSymbol::Literal(data[pos]));
pos += 1;
}
}
symbols.push(LzSymbol::EndOfBlock);
symbols
}
fn build_fixed_litlen_codes() -> ([u16; NUM_LITLEN_CODES], [u8; NUM_LITLEN_CODES]) {
let mut codes = [0u16; NUM_LITLEN_CODES];
let mut lengths = [0u8; NUM_LITLEN_CODES];
for item in &mut lengths[0..=143] {
*item = 8;
}
for item in &mut lengths[144..=255] {
*item = 9;
}
for item in &mut lengths[256..=279] {
*item = 7;
}
for item in &mut lengths[280..=285] {
*item = 8;
}
let mut bl_count = [0u16; MAX_CODE_LEN + 1];
for &cl in &lengths {
bl_count[usize::from(cl)] += 1;
}
let mut next_code = [0u16; MAX_CODE_LEN + 1];
let mut code: u16 = 0;
for bits in 1..=MAX_CODE_LEN {
code = (code + bl_count[bits - 1]) << 1;
next_code[bits] = code;
}
for (sym, &cl) in lengths.iter().enumerate() {
if cl > 0 {
codes[sym] = next_code[usize::from(cl)];
next_code[usize::from(cl)] += 1;
}
}
(codes, lengths)
}
fn build_fixed_dist_codes() -> ([u16; NUM_DIST_CODES], [u8; NUM_DIST_CODES]) {
let mut codes = [0u16; NUM_DIST_CODES];
let lengths = [5u8; NUM_DIST_CODES];
for (i, code) in codes.iter_mut().enumerate() {
#[allow(clippy::cast_possible_truncation)]
{
*code = i as u16; }
}
(codes, lengths)
}
fn encode_length(length: u16) -> Result<(u16, u16, u8)> {
for (i, (&base, &extra)) in LENGTH_BASE.iter().zip(LENGTH_EXTRA.iter()).enumerate() {
let max_len = base + (1 << extra) - 1;
if length >= base && length <= max_len {
#[allow(clippy::cast_possible_truncation)]
let code = 257 + i as u16; let extra_val = length - base;
return Ok((code, extra_val, extra));
}
}
Err(CrushError::InvalidFormat(format!(
"GDeflate: cannot encode length {length}"
)))
}
fn encode_distance(distance: u16) -> Result<(u16, u16, u8)> {
for (i, (&base, &extra)) in DIST_BASE.iter().zip(DIST_EXTRA.iter()).enumerate() {
let max_dist = base + (1 << extra) - 1;
if distance >= base && distance <= max_dist {
let extra_val = distance - base;
#[allow(clippy::cast_possible_truncation)]
{
return Ok((i as u16, extra_val, extra)); }
}
}
Err(CrushError::InvalidFormat(format!(
"GDeflate: cannot encode distance {distance}"
)))
}
fn serialize_gdeflate(symbols: &[LzSymbol]) -> Result<Vec<u8>> {
let (litlen_codes, litlen_lengths) = build_fixed_litlen_codes();
let (dist_codes, dist_lengths) = build_fixed_dist_codes();
let mut streams: Vec<BitWriter> = (0..NUM_STREAMS).map(|_| BitWriter::new()).collect();
streams[0].write_bits(1, 1); streams[0].write_bits(1, 2);
let mut stream_idx = 0;
for symbol in symbols {
match symbol {
LzSymbol::Literal(byte) => {
let code_idx = usize::from(*byte);
let code = litlen_codes[code_idx];
let len = litlen_lengths[code_idx];
streams[stream_idx].write_bits_msb(code, len);
stream_idx = (stream_idx + 1) % NUM_STREAMS;
}
LzSymbol::Match { length, distance } => {
let (len_code, len_extra, len_extra_bits) = encode_length(*length)?;
let code = litlen_codes[usize::from(len_code)];
let code_len = litlen_lengths[usize::from(len_code)];
streams[stream_idx].write_bits_msb(code, code_len);
if len_extra_bits > 0 {
streams[stream_idx].write_bits(len_extra, len_extra_bits);
}
let (dist_code, dist_extra, dist_extra_bits) = encode_distance(*distance)?;
let dc = dist_codes[usize::from(dist_code)];
let dl = dist_lengths[usize::from(dist_code)];
streams[stream_idx].write_bits_msb(dc, dl);
if dist_extra_bits > 0 {
streams[stream_idx].write_bits(dist_extra, dist_extra_bits);
}
stream_idx = (stream_idx + 1) % NUM_STREAMS;
}
LzSymbol::EndOfBlock => {
let code = litlen_codes[256];
let len = litlen_lengths[256];
streams[stream_idx].write_bits_msb(code, len);
}
}
}
for s in &mut streams {
s.pad_to_u32();
}
let mut output = Vec::new();
for s in &streams {
if s.data.len() >= 4 {
output.extend_from_slice(&s.data[..4]);
} else {
let mut buf = [0u8; 4];
buf[..s.data.len()].copy_from_slice(&s.data);
output.extend_from_slice(&buf);
}
}
let max_words = streams.iter().map(|s| s.data.len() / 4).max().unwrap_or(0);
for word_idx in 1..max_words {
for s in &streams {
let byte_off = word_idx * 4;
if byte_off + 4 <= s.data.len() {
output.extend_from_slice(&s.data[byte_off..byte_off + 4]);
} else {
output.extend_from_slice(&[0u8; 4]);
}
}
}
Ok(output)
}
fn deserialize_gdeflate(payload: &[u8], uncompressed_size: usize) -> Result<Vec<u8>> {
if payload.len() < 128 {
return Err(CrushError::InvalidFormat(
"GDeflate: payload too small for initial state (need 128 bytes)".to_owned(),
));
}
let mut stream_data: Vec<Vec<u8>> = (0..NUM_STREAMS).map(|_| Vec::new()).collect();
for (i, sd) in stream_data.iter_mut().enumerate() {
let off = i * 4;
sd.extend_from_slice(&payload[off..off + 4]);
}
let remaining = &payload[128..];
let word_count = remaining.len() / 4;
let mut word_idx = 0;
while word_idx < word_count {
for sd in &mut stream_data {
if word_idx < word_count {
let off = word_idx * 4;
sd.extend_from_slice(&remaining[off..off + 4]);
word_idx += 1;
}
}
}
let litlen_table = HuffmanTable::fixed_litlen()?;
let dist_table = HuffmanTable::fixed_dist()?;
let mut readers: Vec<BitReader<'_>> = stream_data.iter().map(|d| BitReader::new(d)).collect();
let _bfinal = readers[0].read_bits(1)?;
let btype = readers[0].read_bits(2)?;
if btype != 1 {
return Err(CrushError::InvalidFormat(format!(
"GDeflate: expected fixed Huffman (BTYPE=1), got BTYPE={btype}"
)));
}
let mut output = Vec::with_capacity(uncompressed_size);
let mut stream_idx = 0;
loop {
if output.len() >= uncompressed_size {
break;
}
let sym = litlen_table.decode(&mut readers[stream_idx])?;
match sym.cmp(&256) {
std::cmp::Ordering::Less => {
#[allow(clippy::cast_possible_truncation)]
output.push(sym as u8); stream_idx = (stream_idx + 1) % NUM_STREAMS;
}
std::cmp::Ordering::Equal => {
break;
}
std::cmp::Ordering::Greater => {
let len_idx = usize::from(sym - 257);
if len_idx >= LENGTH_BASE.len() {
return Err(CrushError::InvalidFormat(format!(
"GDeflate: invalid length code {sym}"
)));
}
let base_len = LENGTH_BASE[len_idx];
let extra = LENGTH_EXTRA[len_idx];
let extra_bits = if extra > 0 {
readers[stream_idx].read_bits(extra)?
} else {
0
};
let length = usize::from(base_len + extra_bits);
let dist_sym = dist_table.decode(&mut readers[stream_idx])?;
let dist_idx = usize::from(dist_sym);
if dist_idx >= DIST_BASE.len() {
return Err(CrushError::InvalidFormat(format!(
"GDeflate: invalid distance code {dist_sym}"
)));
}
let base_dist = DIST_BASE[dist_idx];
let dist_extra = DIST_EXTRA[dist_idx];
let dist_extra_bits = if dist_extra > 0 {
readers[stream_idx].read_bits(dist_extra)?
} else {
0
};
let distance = usize::from(base_dist + dist_extra_bits);
stream_idx = (stream_idx + 1) % NUM_STREAMS;
if distance == 0 || distance > output.len() {
return Err(CrushError::InvalidFormat(format!(
"GDeflate: invalid match distance {distance} (output len {})",
output.len()
)));
}
let start = output.len() - distance;
for j in 0..length {
let byte = output[start + j];
output.push(byte);
}
}
}
}
output.truncate(uncompressed_size);
Ok(output)
}
pub fn gdeflate_compress_tile(tile_data: &[u8]) -> Result<Vec<u8>> {
if tile_data.is_empty() {
return Ok(Vec::new());
}
if tile_data.len() > 65536 {
return Err(CrushError::from(PluginError::OperationFailed(
"GDeflate: tile exceeds 64KB".to_owned(),
)));
}
let symbols = lz77_find_matches(tile_data);
serialize_gdeflate(&symbols)
}
pub fn gdeflate_decompress_tile(payload: &[u8], uncompressed_size: usize) -> Result<Vec<u8>> {
if payload.is_empty() {
return Ok(Vec::new());
}
deserialize_gdeflate(payload, uncompressed_size)
}