use std::fs::{File, OpenOptions}; use std::io::{Write, Cursor, Seek, SeekFrom, Read}; use binrw::{BinRead, BinReaderExt}; use std::path::{PathBuf}; use crate::utils::huffman_tables::{CHARLEN, POS}; use crate::utils::compression::{decompress_lz4}; #[derive(BinRead)] struct LzhsHeader { uncompressed_size: u32, compressed_size: u32, checksum_or_seg_idx: u16, //as checksum in normal lzhs header, as index in lzhs_fs header #[br(count = 6)] padding: Vec, } pub fn decompress_lzhs_fs_file2file(mut file: &File, output_file: PathBuf) -> Result<(), Box> { let file_size = file.metadata()?.len(); let mut out_file = OpenOptions::new().append(true).create(true).open(&output_file)?; file.seek(SeekFrom::Start(0))?; let mut uncompressed_heading = vec![0u8; 0x100000]; //first 1mb is uncompressed file.read_exact(&mut uncompressed_heading)?; out_file.write_all(&uncompressed_heading)?; while file.stream_position().unwrap() < file_size { let segment_header: LzhsHeader = file.read_le()?; let lzhs_header: LzhsHeader = file.read_le()?; //lz4 type uses a 4 byte checksum instead of 1(2) byte , so the padding is not 0 anymore //maybe this method will be changed let is_lz4 = if lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false}; println!("[{}] Segment {} - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}", if is_lz4 {"LZ4"} else {"LZHS"}, segment_header.checksum_or_seg_idx, lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx); let mut compressed_data = vec![0u8; lzhs_header.compressed_size as usize]; file.read_exact(&mut compressed_data)?; let mut out_data; if lzhs_header.compressed_size == 0 { //odd variant no.1: if the compressed size is 0 , the output is just zeros with the uncompressed size. out_data = vec![0; lzhs_header.uncompressed_size as usize]; } else if (lzhs_header.uncompressed_size > segment_header.uncompressed_size) && (lzhs_header.compressed_size == lzhs_header.uncompressed_size) && lzhs_header.checksum_or_seg_idx == 0x00 { //odd variant no.2: the lzhs header compressed size is for some reason bigger by 8 bytes (and those 8 bytes are zeros), the data is stored UNCOMPRESSED in these //the compressed and uncompressed size are the same and the checksum is 0. out_data = compressed_data[..segment_header.uncompressed_size as usize].to_vec(); } else { //normal variant println!("- Decompressing..."); if is_lz4 { out_data = decompress_lz4(&compressed_data, lzhs_header.uncompressed_size as i32)?; } else { //lzhs let out_huff = unhuff(&compressed_data); out_data = unlzss(&out_huff, lzhs_header.uncompressed_size as usize); arm_thumb_convert(&mut out_data, 0, false); let checksum = calc_checksum(&out_data); println!("-- Calculated checksum: 0x{:02x?}", checksum); if u16::from(checksum) != lzhs_header.checksum_or_seg_idx { println!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", lzhs_header.checksum_or_seg_idx, checksum); return Err("Checksum mismatch!".into()); } else { println!("--- Checksum OK!") } } } out_file.write_all(&out_data)?; //padded to 16 bytes let pad_pos = (file.stream_position().unwrap() + 15) & !15; file.seek(SeekFrom::Start(pad_pos))?; } Ok(()) } #[derive(Debug)] struct HuffmanCtx { c: u8, code: u32, len: u32, bitno: u8, code_buf: [u8; 32], code_buf_ptr: usize, mask: u8, } fn get_byte(ctx: &mut HuffmanCtx, input: &mut Cursor) -> bool { if ctx.bitno > 7 { ctx.bitno = 0; let mut buf = [0u8; 1]; if input.read_exact(&mut buf).is_err() { return false; } ctx.c = buf[0]; } let bit = ((ctx.c >> (7 - ctx.bitno)) & 1) as u32; ctx.code = (ctx.code << 1) | bit; ctx.len += 1; ctx.bitno += 1; true } fn unhuff(data: &[u8]) -> Vec { let charlen = &CHARLEN; let pos = &POS; let mut ctx = HuffmanCtx { c: 0, code: 0, len: 0, bitno: 8, code_buf: [0u8; 32], code_buf_ptr: 1, mask: 1, }; let mut lookup_charlen = vec![-1i16; 131072]; let mut lookup_charpos = vec![-1i16; 512]; let mut in_cur = Cursor::new(data); let mut out: Vec = Vec::with_capacity(data.len()); loop { if !get_byte(&mut ctx, &mut in_cur) { /*println!("break1");*/break; } if ctx.len < 4 { continue; } let key = (((ctx.len & 0xF) as usize) << 13) | ((ctx.code & 0x1FFF) as usize); let mut idx = lookup_charlen[key]; if idx == -2 { continue; } if idx == -1 { let mut found = false; for (i, &(code, len)) in charlen.iter().enumerate() { if len == ctx.len && code == ctx.code { lookup_charlen[key] = i as i16; idx = i as i16; found = true; break; } } if !found { lookup_charlen[key] = -2; continue; } } let i = idx as i32; if i > 255 { let val = (i - 256) as u8; if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = val; ctx.code_buf_ptr += 1; } ctx.code = 0; ctx.len = 0; let found_j: i32; loop { if !get_byte(&mut ctx, &mut in_cur) { //println!("retA"); //flush if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } }; return out; } if ctx.len < 2 { continue; } let keyp = (((ctx.len & 0x7) as usize) << 6) | ((ctx.code & 0x3F) as usize); let mut jdx = lookup_charpos[keyp]; if jdx == -2 { continue; } if jdx == -1 { let mut found = false; for (j, &(code, len)) in pos.iter().enumerate() { if len == ctx.len && code == ctx.code { lookup_charpos[keyp] = j as i16; jdx = j as i16; found = true; break; } } if !found { lookup_charpos[keyp] = -2; continue; } } found_j = jdx as i32; let b = ((jdx as i32) >> 1) as u8; if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = b; ctx.code_buf_ptr += 1; } break; } ctx.code = 0; for _ in 0..7 { if !get_byte(&mut ctx, &mut in_cur) { //println!("retB"); //flush if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } }; return out; } } let combined = (ctx.code | ((found_j as u32) << 7)) as u32; if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = (combined & 0xFF) as u8; ctx.code_buf_ptr += 1; } ctx.code = 0; ctx.len = 0; } else { ctx.code_buf[0] |= ctx.mask; if ctx.code_buf_ptr < ctx.code_buf.len() { ctx.code_buf[ctx.code_buf_ptr] = i as u8; ctx.code_buf_ptr += 1; } ctx.code = 0; ctx.len = 0; } ctx.mask = ctx.mask.wrapping_shl(1); if ctx.mask == 0 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } ctx.code_buf[0] = 0; ctx.code_buf_ptr = 1; ctx.mask = 1; } } if ctx.code_buf_ptr > 1 { for j in 0..ctx.code_buf_ptr { out.push(ctx.code_buf[j]); } } out } fn unlzss(data: &[u8], expected_size: usize) -> Vec { let mut window = [0u8; 0x1000]; let mut dst = Vec::with_capacity(expected_size); let mut src_i = 0; let mut win_pos = 0usize; let mut flags = 0u32; while dst.len() < expected_size { flags >>= 1; if (flags & 0x100) == 0 { if src_i >= data.len() { break; } flags = data[src_i] as u32 | 0xFF00; src_i += 1; } if (flags & 1) != 0 { // literal if src_i >= data.len() { break; } let c = data[src_i]; src_i += 1; dst.push(c); window[win_pos] = c; win_pos = (win_pos + 1) & 0xFFF; } else { // backreference if src_i + 2 >= data.len() { break; } let j = data[src_i] as usize; let off = ((data[src_i + 1] as usize) << 8) | data[src_i + 2] as usize; src_i += 3; let count = j + 3; for _ in 0..count { if dst.len() >= expected_size { break; } let c = window[(win_pos.wrapping_sub(off)) & 0xFFF]; dst.push(c); window[win_pos] = c; win_pos = (win_pos + 1) & 0xFFF; } } } dst } fn arm_thumb_convert(data: &mut [u8], now_pos: u32, encoding: bool) { let size = data.len() as u32; let mut i: u32 = 0; while i + 4 <= size { let idx = i as usize; if (data[idx + 1] & 0xF8) == 0xF0 && (data[idx + 3] & 0xF8) == 0xF8 { let mut src: u32 = ((data[idx + 1] as u32 & 0x7) << 19) | ((data[idx + 0] as u32) << 11) | ((data[idx + 3] as u32 & 0x7) << 8) | (data[idx + 2] as u32); src <<= 1; let dest = if encoding { now_pos + i + 4 + src } else { src.wrapping_sub(now_pos + i + 4) } >> 1; data[idx + 1] = 0xF0 | ((dest >> 19) & 0x7) as u8; data[idx + 0] = ((dest >> 11) & 0xFF) as u8; data[idx + 3] = 0xF8 | ((dest >> 8) & 0x7) as u8; data[idx + 2] = (dest & 0xFF) as u8; i += 2; } i += 2; } } fn calc_checksum(data: &[u8]) -> u8 { let mut checksum: u8 = 0; for &bt in data { checksum = checksum.wrapping_add(bt); } checksum }