Files
xenia-rs/crates/xenia-xex/src/lzx.rs
MechaCat02 c694bb3f43 Initial commit: xenia-rs workspace for Xbox 360 RE
Rust reimplementation of the xenia Xbox 360 emulator targeting reverse-
engineering and preservation, initially scoped to Project Sylpheed.

Includes:
- XEX2 loader (LZX decompression, AES decryption, PE parsing)
- XISO / XGD2 disc image VFS
- PPC interpreter with 200+ opcodes and VMX128 decoding
- Static analyzer: functions, cross-references, labels, asm + SQLite output
- HLE kernel covering the xboxkrnl/xam subset used by Sylpheed init
- Debugger with in-memory and SQLite-backed execution tracing
- `xenia-rs` CLI with extract/dis/exec commands that produce cumulative,
  superset SQLite databases and opt-in instruction/import/branch traces

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 23:14:56 +02:00

693 lines
30 KiB
Rust

//! 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<usize, LzxError> {
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<u8>,
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<u8>,
maintree_len: Vec<u8>,
length_len: Vec<u8>,
aligned_len: Vec<u8>,
// Huffman decode tables
pretree_table: Vec<u16>,
maintree_table: Vec<u16>,
length_table: Vec<u16>,
aligned_table: Vec<u16>,
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<bool, LzxError> {
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<Vec<u8>, 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;
}
}
}
}