//! LZX decompressor for Xbox 360 XEX2 "normal compression". //! Ported from libmspack lzxd.c (C) 2003-2013 Stuart Caie, LGPL 2.1. use std::fmt; // ── LZX constants ─────────────────────────────────────────────────────────── const LZX_MIN_MATCH: usize = 2; const LZX_NUM_CHARS: usize = 256; const LZX_BLOCKTYPE_VERBATIM: u8 = 1; const LZX_BLOCKTYPE_ALIGNED: u8 = 2; const LZX_BLOCKTYPE_UNCOMPRESSED: u8 = 3; const LZX_NUM_PRIMARY_LENGTHS: usize = 7; const LZX_NUM_SECONDARY_LENGTHS: usize = 249; const LZX_FRAME_SIZE: usize = 32768; const HUFF_MAXBITS: usize = 16; const PRETREE_MAXSYMS: usize = 20; const PRETREE_TABLEBITS: usize = 6; const MAINTREE_MAXSYMS: usize = LZX_NUM_CHARS + 290 * 8; // 2576 const MAINTREE_TABLEBITS: usize = 12; const LENGTH_MAXSYMS: usize = LZX_NUM_SECONDARY_LENGTHS + 1; // 250 const LENGTH_TABLEBITS: usize = 12; const ALIGNED_MAXSYMS: usize = 8; const ALIGNED_TABLEBITS: usize = 7; const LENTABLE_SAFETY: usize = 64; const BITBUF_WIDTH: u32 = 32; // ── Static tables ─────────────────────────────────────────────────────────── static POSITION_SLOTS: [u32; 11] = [30, 32, 34, 36, 38, 42, 50, 66, 98, 162, 290]; static EXTRA_BITS: [u8; 36] = [ 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, 14, 14, 15, 15, 16, 16, ]; #[rustfmt::skip] static POSITION_BASE: [u32; 290] = [ 0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, 131072, 196608, 262144, 393216, 524288, 655360, 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936, 1835008, 1966080, 2097152, 2228224, 2359296, 2490368, 2621440, 2752512, 2883584, 3014656, 3145728, 3276800, 3407872, 3538944, 3670016, 3801088, 3932160, 4063232, 4194304, 4325376, 4456448, 4587520, 4718592, 4849664, 4980736, 5111808, 5242880, 5373952, 5505024, 5636096, 5767168, 5898240, 6029312, 6160384, 6291456, 6422528, 6553600, 6684672, 6815744, 6946816, 7077888, 7208960, 7340032, 7471104, 7602176, 7733248, 7864320, 7995392, 8126464, 8257536, 8388608, 8519680, 8650752, 8781824, 8912896, 9043968, 9175040, 9306112, 9437184, 9568256, 9699328, 9830400, 9961472, 10092544, 10223616, 10354688, 10485760, 10616832, 10747904, 10878976, 11010048, 11141120, 11272192, 11403264, 11534336, 11665408, 11796480, 11927552, 12058624, 12189696, 12320768, 12451840, 12582912, 12713984, 12845056, 12976128, 13107200, 13238272, 13369344, 13500416, 13631488, 13762560, 13893632, 14024704, 14155776, 14286848, 14417920, 14548992, 14680064, 14811136, 14942208, 15073280, 15204352, 15335424, 15466496, 15597568, 15728640, 15859712, 15990784, 16121856, 16252928, 16384000, 16515072, 16646144, 16777216, 16908288, 17039360, 17170432, 17301504, 17432576, 17563648, 17694720, 17825792, 17956864, 18087936, 18219008, 18350080, 18481152, 18612224, 18743296, 18874368, 19005440, 19136512, 19267584, 19398656, 19529728, 19660800, 19791872, 19922944, 20054016, 20185088, 20316160, 20447232, 20578304, 20709376, 20840448, 20971520, 21102592, 21233664, 21364736, 21495808, 21626880, 21757952, 21889024, 22020096, 22151168, 22282240, 22413312, 22544384, 22675456, 22806528, 22937600, 23068672, 23199744, 23330816, 23461888, 23592960, 23724032, 23855104, 23986176, 24117248, 24248320, 24379392, 24510464, 24641536, 24772608, 24903680, 25034752, 25165824, 25296896, 25427968, 25559040, 25690112, 25821184, 25952256, 26083328, 26214400, 26345472, 26476544, 26607616, 26738688, 26869760, 27000832, 27131904, 27262976, 27394048, 27525120, 27656192, 27787264, 27918336, 28049408, 28180480, 28311552, 28442624, 28573696, 28704768, 28835840, 28966912, 29097984, 29229056, 29360128, 29491200, 29622272, 29753344, 29884416, 30015488, 30146560, 30277632, 30408704, 30539776, 30670848, 30801920, 30932992, 31064064, 31195136, 31326208, 31457280, 31588352, 31719424, 31850496, 31981568, 32112640, 32243712, 32374784, 32505856, 32636928, 32768000, 32899072, 33030144, 33161216, 33292288, 33423360, ]; // ── Error type ────────────────────────────────────────────────────────────── #[derive(Debug)] pub enum LzxError { BadHuffmanTable, Decrunch(String), } impl fmt::Display for LzxError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::BadHuffmanTable => write!(f, "failed to build Huffman table"), Self::Decrunch(msg) => write!(f, "LZX decrunch error: {msg}"), } } } impl std::error::Error for LzxError {} // ── Bit reader (MSB order, 16-bit LE pairs) ──────────────────────────────── struct BitReader<'a> { data: &'a [u8], pos: usize, buf: u32, left: i32, } impl<'a> BitReader<'a> { fn new(data: &'a [u8]) -> Self { Self { data, pos: 0, buf: 0, left: 0 } } /// Inject one 16-bit little-endian pair into MSB bit buffer. fn fill(&mut self) { let b0 = if self.pos < self.data.len() { let b = self.data[self.pos]; self.pos += 1; b as u32 } else { 0 }; let b1 = if self.pos < self.data.len() { let b = self.data[self.pos]; self.pos += 1; b as u32 } else { 0 }; let word = (b1 << 8) | b0; self.buf |= word << (16 - self.left as u32); self.left += 16; } #[inline] fn ensure(&mut self, n: i32) { while self.left < n { self.fill(); } } #[inline] fn peek(&self, n: u32) -> u32 { self.buf >> (BITBUF_WIDTH - n) } #[inline] fn remove(&mut self, n: u32) { self.buf <<= n; self.left -= n as i32; } #[inline] fn read(&mut self, n: u32) -> u32 { self.ensure(n as i32); let v = self.peek(n); self.remove(n); v } /// Read a raw byte directly (for UNCOMPRESSED blocks). fn raw_byte(&mut self) -> u8 { if self.pos < self.data.len() { let b = self.data[self.pos]; self.pos += 1; b } else { 0 } } /// Re-align the bitstream at a frame boundary. fn align_frame(&mut self) { if self.left > 0 { self.ensure(16); } let r = self.left & 15; if r != 0 { self.remove(r as u32); } } } // ── Huffman table builder (MSB order) ─────────────────────────────────────── fn make_decode_table( nsyms: usize, nbits: usize, length: &[u8], table: &mut [u16], ) -> bool { let mut pos: usize = 0; let table_mask = 1usize << nbits; let mut bit_mask = table_mask >> 1; // Short codes: direct mapping for bit_num in 1..=nbits { for sym in 0..nsyms { if length[sym] as usize != bit_num { continue; } let leaf = pos; pos += bit_mask; if pos > table_mask { return true; } for i in leaf..leaf + bit_mask { table[i] = sym as u16; } } bit_mask >>= 1; } if pos == table_mask { return false; } // Mark remaining entries as unused for i in pos..table_mask { table[i] = 0xFFFF; } let mut next_symbol = if (table_mask >> 1) < nsyms { nsyms } else { table_mask >> 1 }; let mut pos32 = (pos as u32) << 16; let table_mask32 = (table_mask as u32) << 16; let mut bit_mask32: u32 = 1 << 15; // Long codes: tree traversal for bit_num in (nbits + 1)..=HUFF_MAXBITS { for sym in 0..nsyms { if length[sym] as usize != bit_num { continue; } if pos32 >= table_mask32 { return true; } let mut leaf = (pos32 >> 16) as usize; for fill in 0..(bit_num - nbits) { if table[leaf] == 0xFFFF { table[next_symbol << 1] = 0xFFFF; table[(next_symbol << 1) + 1] = 0xFFFF; table[leaf] = next_symbol as u16; next_symbol += 1; } leaf = (table[leaf] as usize) << 1; if (pos32 >> (15 - fill as u32)) & 1 != 0 { leaf += 1; } } table[leaf] = sym as u16; pos32 += bit_mask32; } bit_mask32 >>= 1; } pos32 != table_mask32 } // ── Huffman symbol decoder ────────────────────────────────────────────────── fn read_huffsym( br: &mut BitReader, table: &[u16], lens: &[u8], tablebits: usize, maxsyms: usize, ) -> Result { br.ensure(HUFF_MAXBITS as i32); let mut sym = table[br.peek(tablebits as u32) as usize] as usize; if sym >= maxsyms { let mut i: u32 = 1 << (BITBUF_WIDTH - tablebits as u32); loop { i >>= 1; if i == 0 { return Err(LzxError::BadHuffmanTable); } sym = table[(sym << 1) | if br.buf & i != 0 { 1 } else { 0 }] as usize; if sym < maxsyms { break; } } } br.remove(lens[sym] as u32); Ok(sym) } // ── LZX decoder state ─────────────────────────────────────────────────────── pub struct LzxDecoder { window: Vec, window_size: usize, window_posn: usize, frame_posn: usize, frame: usize, num_offsets: usize, r0: u32, r1: u32, r2: u32, block_type: u8, block_length: usize, block_remaining: usize, header_read: bool, intel_filesize: i32, intel_curpos: i32, intel_started: bool, // Huffman code lengths pretree_len: Vec, maintree_len: Vec, length_len: Vec, aligned_len: Vec, // Huffman decode tables pretree_table: Vec, maintree_table: Vec, length_table: Vec, aligned_table: Vec, length_empty: bool, } impl LzxDecoder { pub fn new(window_bits: u32) -> Self { assert!((15..=21).contains(&window_bits)); let window_size = 1usize << window_bits; let num_offsets = (POSITION_SLOTS[(window_bits - 15) as usize] as usize) << 3; Self { window: vec![0u8; window_size], window_size, window_posn: 0, frame_posn: 0, frame: 0, num_offsets, r0: 1, r1: 1, r2: 1, block_type: 0, block_length: 0, block_remaining: 0, header_read: false, intel_filesize: 0, intel_curpos: 0, intel_started: false, pretree_len: vec![0u8; PRETREE_MAXSYMS + LENTABLE_SAFETY], maintree_len: vec![0u8; MAINTREE_MAXSYMS + LENTABLE_SAFETY], length_len: vec![0u8; LENGTH_MAXSYMS + LENTABLE_SAFETY], aligned_len: vec![0u8; ALIGNED_MAXSYMS + LENTABLE_SAFETY], pretree_table: vec![0u16; (1 << PRETREE_TABLEBITS) + PRETREE_MAXSYMS * 2], maintree_table: vec![0u16; (1 << MAINTREE_TABLEBITS) + MAINTREE_MAXSYMS * 2], length_table: vec![0u16; (1 << LENGTH_TABLEBITS) + LENGTH_MAXSYMS * 2], aligned_table: vec![0u16; (1 << ALIGNED_TABLEBITS) + ALIGNED_MAXSYMS * 2], length_empty: false, } } fn build_table( lens: &[u8], table: &mut [u16], maxsyms: usize, tablebits: usize, ) -> Result<(), LzxError> { if make_decode_table(maxsyms, tablebits, lens, table) { Err(LzxError::BadHuffmanTable) } else { Ok(()) } } fn build_table_maybe_empty( lens: &[u8], table: &mut [u16], maxsyms: usize, tablebits: usize, ) -> Result { if make_decode_table(maxsyms, tablebits, lens, table) { // Check if table is simply empty (all lengths zero) for i in 0..maxsyms { if lens[i] > 0 { return Err(LzxError::BadHuffmanTable); } } Ok(true) // empty } else { Ok(false) // not empty } } /// Read Huffman code lengths using the pretree (lzxd_read_lens). fn read_lens( br: &mut BitReader, lens: &mut [u8], pretree_len: &mut [u8], pretree_table: &mut [u16], first: usize, last: usize, ) -> Result<(), LzxError> { // Build pretree: 20 symbols, 4 bits each for i in 0..20 { pretree_len[i] = br.read(4) as u8; } Self::build_table(pretree_len, pretree_table, PRETREE_MAXSYMS, PRETREE_TABLEBITS)?; let mut x = first; while x < last { let z = read_huffsym(br, pretree_table, pretree_len, PRETREE_TABLEBITS, PRETREE_MAXSYMS)?; if z == 17 { // Run of zeros: [read 4 bits] + 4 let mut y = br.read(4) as usize + 4; while y > 0 && x < last { lens[x] = 0; x += 1; y -= 1; } } else if z == 18 { // Run of zeros: [read 5 bits] + 20 let mut y = br.read(5) as usize + 20; while y > 0 && x < last { lens[x] = 0; x += 1; y -= 1; } } else if z == 19 { // Run of same: [read 1 bit] + 4, then read symbol let mut y = br.read(1) as usize + 4; let z2 = read_huffsym(br, pretree_table, pretree_len, PRETREE_TABLEBITS, PRETREE_MAXSYMS)?; let mut val = lens[x] as i32 - z2 as i32; if val < 0 { val += 17; } while y > 0 && x < last { lens[x] = val as u8; x += 1; y -= 1; } } else { // Delta: code 0..16 let mut val = lens[x] as i32 - z as i32; if val < 0 { val += 17; } lens[x] = val as u8; x += 1; } } Ok(()) } /// Decompress the full LZX stream into the output buffer. pub fn decompress(&mut self, input: &[u8], output_len: usize) -> Result, LzxError> { let mut br = BitReader::new(input); let mut output = Vec::with_capacity(output_len); let mut offset: usize = 0; let end_frame = (output_len / LZX_FRAME_SIZE) + 1; while self.frame < end_frame { // Read header once if !self.header_read { let i_bit = br.read(1); let (hi, lo) = if i_bit != 0 { (br.read(16), br.read(16)) } else { (0, 0) }; self.intel_filesize = ((hi << 16) | lo) as i32; self.header_read = true; } // Frame size let frame_size = if output_len > 0 && (output_len - offset) < LZX_FRAME_SIZE { output_len - offset } else { LZX_FRAME_SIZE }; let mut bytes_todo = (self.frame_posn + frame_size).wrapping_sub(self.window_posn) as i32; while bytes_todo > 0 { // New block? if self.block_remaining == 0 { // Realign after odd UNCOMPRESSED block if self.block_type == LZX_BLOCKTYPE_UNCOMPRESSED && (self.block_length & 1) != 0 { br.raw_byte(); } // Read block type (3 bits) and length (24 bits) self.block_type = br.read(3) as u8; let hi = br.read(16) as usize; let lo = br.read(8) as usize; self.block_length = (hi << 8) | lo; self.block_remaining = self.block_length; match self.block_type { LZX_BLOCKTYPE_ALIGNED => { for i in 0..8 { self.aligned_len[i] = br.read(3) as u8; } Self::build_table(&self.aligned_len, &mut self.aligned_table, ALIGNED_MAXSYMS, ALIGNED_TABLEBITS)?; // Fall through to verbatim tree reading Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 0, 256)?; Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 256, LZX_NUM_CHARS + self.num_offsets)?; Self::build_table(&self.maintree_len, &mut self.maintree_table, MAINTREE_MAXSYMS, MAINTREE_TABLEBITS)?; if self.maintree_len[0xE8] != 0 { self.intel_started = true; } Self::read_lens(&mut br, &mut self.length_len, &mut self.pretree_len, &mut self.pretree_table, 0, LZX_NUM_SECONDARY_LENGTHS)?; self.length_empty = Self::build_table_maybe_empty(&self.length_len, &mut self.length_table, LENGTH_MAXSYMS, LENGTH_TABLEBITS)?; } LZX_BLOCKTYPE_VERBATIM => { Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 0, 256)?; Self::read_lens(&mut br, &mut self.maintree_len, &mut self.pretree_len, &mut self.pretree_table, 256, LZX_NUM_CHARS + self.num_offsets)?; Self::build_table(&self.maintree_len, &mut self.maintree_table, MAINTREE_MAXSYMS, MAINTREE_TABLEBITS)?; if self.maintree_len[0xE8] != 0 { self.intel_started = true; } Self::read_lens(&mut br, &mut self.length_len, &mut self.pretree_len, &mut self.pretree_table, 0, LZX_NUM_SECONDARY_LENGTHS)?; self.length_empty = Self::build_table_maybe_empty(&self.length_len, &mut self.length_table, LENGTH_MAXSYMS, LENGTH_TABLEBITS)?; } LZX_BLOCKTYPE_UNCOMPRESSED => { self.intel_started = true; // Align to byte boundary if br.left == 0 { br.ensure(16); } br.left = 0; br.buf = 0; // Read R0, R1, R2 (12 bytes, little-endian u32s) let mut buf = [0u8; 12]; for b in &mut buf { *b = br.raw_byte(); } self.r0 = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]); self.r1 = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]); self.r2 = u32::from_le_bytes([buf[8], buf[9], buf[10], buf[11]]); } _ => return Err(LzxError::Decrunch("bad block type".into())), } } let mut this_run = self.block_remaining as i32; if this_run > bytes_todo { this_run = bytes_todo; } bytes_todo -= this_run; self.block_remaining -= this_run as usize; let window_size = self.window_size; match self.block_type { LZX_BLOCKTYPE_VERBATIM => { while this_run > 0 { let main_element = read_huffsym(&mut br, &self.maintree_table, &self.maintree_len, MAINTREE_TABLEBITS, MAINTREE_MAXSYMS)?; if main_element < LZX_NUM_CHARS { self.window[self.window_posn] = main_element as u8; self.window_posn += 1; this_run -= 1; } else { let me = main_element - LZX_NUM_CHARS; let mut match_length = me & LZX_NUM_PRIMARY_LENGTHS; if match_length == LZX_NUM_PRIMARY_LENGTHS { if self.length_empty { return Err(LzxError::Decrunch("LENGTH tree empty".into())); } let footer = read_huffsym(&mut br, &self.length_table, &self.length_len, LENGTH_TABLEBITS, LENGTH_MAXSYMS)?; match_length += footer; } match_length += LZX_MIN_MATCH; let mut match_offset = (me >> 3) as u32; match match_offset { 0 => match_offset = self.r0, 1 => { match_offset = self.r1; self.r1 = self.r0; self.r0 = match_offset; } 2 => { match_offset = self.r2; self.r2 = self.r0; self.r0 = match_offset; } 3 => { match_offset = 1; self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; } _ => { let extra = if match_offset >= 36 { 17 } else { EXTRA_BITS[match_offset as usize] as u32 }; let verbatim_bits = br.read(extra); match_offset = POSITION_BASE[match_offset as usize] - 2 + verbatim_bits; self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; } } if self.window_posn + match_length > window_size { return Err(LzxError::Decrunch("match overrun".into())); } self.copy_match(match_offset as usize, match_length); this_run -= match_length as i32; } } } LZX_BLOCKTYPE_ALIGNED => { while this_run > 0 { let main_element = read_huffsym(&mut br, &self.maintree_table, &self.maintree_len, MAINTREE_TABLEBITS, MAINTREE_MAXSYMS)?; if main_element < LZX_NUM_CHARS { self.window[self.window_posn] = main_element as u8; self.window_posn += 1; this_run -= 1; } else { let me = main_element - LZX_NUM_CHARS; let mut match_length = me & LZX_NUM_PRIMARY_LENGTHS; if match_length == LZX_NUM_PRIMARY_LENGTHS { if self.length_empty { return Err(LzxError::Decrunch("LENGTH tree empty".into())); } let footer = read_huffsym(&mut br, &self.length_table, &self.length_len, LENGTH_TABLEBITS, LENGTH_MAXSYMS)?; match_length += footer; } match_length += LZX_MIN_MATCH; let mut match_offset = (me >> 3) as u32; match match_offset { 0 => match_offset = self.r0, 1 => { match_offset = self.r1; self.r1 = self.r0; self.r0 = match_offset; } 2 => { match_offset = self.r2; self.r2 = self.r0; self.r0 = match_offset; } _ => { let extra = if match_offset >= 36 { 17 } else { EXTRA_BITS[match_offset as usize] as u32 }; match_offset = POSITION_BASE[match_offset as usize] - 2; if extra > 3 { let verbatim_bits = br.read(extra - 3); match_offset += verbatim_bits << 3; let aligned = read_huffsym(&mut br, &self.aligned_table, &self.aligned_len, ALIGNED_TABLEBITS, ALIGNED_MAXSYMS)?; match_offset += aligned as u32; } else if extra == 3 { let aligned = read_huffsym(&mut br, &self.aligned_table, &self.aligned_len, ALIGNED_TABLEBITS, ALIGNED_MAXSYMS)?; match_offset += aligned as u32; } else if extra > 0 { let verbatim_bits = br.read(extra); match_offset += verbatim_bits; } else { match_offset = 1; } self.r2 = self.r1; self.r1 = self.r0; self.r0 = match_offset; } } if self.window_posn + match_length > window_size { return Err(LzxError::Decrunch("match overrun".into())); } self.copy_match(match_offset as usize, match_length); this_run -= match_length as i32; } } } LZX_BLOCKTYPE_UNCOMPRESSED => { let run = this_run as usize; for _ in 0..run { self.window[self.window_posn] = br.raw_byte(); self.window_posn += 1; } } _ => return Err(LzxError::Decrunch("bad block type in decode".into())), } // Overrun accounting if this_run < 0 { let overrun = (-this_run) as usize; if overrun > self.block_remaining { return Err(LzxError::Decrunch("overrun past block end".into())); } self.block_remaining -= overrun; } } // Frame boundary check if (self.window_posn.wrapping_sub(self.frame_posn)) != frame_size { return Err(LzxError::Decrunch(format!( "decode beyond frame: {} != {}", self.window_posn - self.frame_posn, frame_size ))); } // Re-align bitstream br.align_frame(); // Intel E8 postprocessing if self.intel_started && self.intel_filesize != 0 && self.frame <= 32768 && frame_size > 10 { let mut e8_buf = vec![0u8; frame_size]; e8_buf.copy_from_slice(&self.window[self.frame_posn..self.frame_posn + frame_size]); let mut i = 0usize; let limit = frame_size - 10; let mut curpos = self.intel_curpos; let filesize = self.intel_filesize; while i < limit { if e8_buf[i] != 0xE8 { i += 1; curpos += 1; continue; } let abs_off = e8_buf[i+1] as i32 | (e8_buf[i+2] as i32) << 8 | (e8_buf[i+3] as i32) << 16 | (e8_buf[i+4] as i32) << 24; if abs_off >= -curpos && abs_off < filesize { let rel_off = if abs_off >= 0 { abs_off - curpos } else { abs_off + filesize }; e8_buf[i+1] = rel_off as u8; e8_buf[i+2] = (rel_off >> 8) as u8; e8_buf[i+3] = (rel_off >> 16) as u8; e8_buf[i+4] = (rel_off >> 24) as u8; } i += 5; curpos += 5; } self.intel_curpos += frame_size as i32; let to_write = frame_size.min(output_len - offset); output.extend_from_slice(&e8_buf[..to_write]); offset += to_write; } else { if self.intel_filesize != 0 { self.intel_curpos += frame_size as i32; } let to_write = frame_size.min(output_len - offset); output.extend_from_slice(&self.window[self.frame_posn..self.frame_posn + to_write]); offset += to_write; } // Advance frame self.frame_posn += frame_size; self.frame += 1; if self.window_posn == self.window_size { self.window_posn = 0; } if self.frame_posn == self.window_size { self.frame_posn = 0; } } Ok(output) } /// Copy a match from the window (handles wrap-around). fn copy_match(&mut self, match_offset: usize, match_length: usize) { let window_size = self.window_size; let mut remaining = match_length; if match_offset > self.window_posn { // Source wraps around window end let j = match_offset - self.window_posn; let mut src = window_size - j; if j < remaining { remaining -= j; for _ in 0..j { self.window[self.window_posn] = self.window[src]; self.window_posn += 1; src += 1; } src = 0; // wrap to start } for _ in 0..remaining { self.window[self.window_posn] = self.window[src]; self.window_posn += 1; src += 1; } } else { let mut src = self.window_posn - match_offset; for _ in 0..remaining { self.window[self.window_posn] = self.window[src]; self.window_posn += 1; src += 1; } } } }