2026-05-07 18:42:47 +02:00
use std ::fs ::OpenOptions ;
2025-12-23 14:51:15 +01:00
use std ::io ::{ Write , Cursor , Seek , SeekFrom , Read };
use binrw ::{ BinRead , BinReaderExt };
use std ::path ::{ PathBuf };
2026-02-17 17:28:59 +01:00
use super ::huffman_tables ::{ CHARLEN , POS };
2026-05-07 18:42:47 +02:00
use crate ::utils ::common ;
use crate ::utils ::compression ::{ decompress_lz4 , decompress_zstd };
#[derive(PartialEq, Debug)]
enum CompressionType {
LZHS ,
LZ4 ,
ZSTD ,
}
2025-12-23 14:51:15 +01:00
#[derive(BinRead)]
struct LzhsHeader {
uncompressed_size : u32 ,
compressed_size : u32 ,
2026-05-07 18:42:47 +02:00
checksum_or_seg_idx : u32 , //as checksum in normal lzhs header, as index in lzhs_fs header
_padding : [ u8 ; 4 ],
2025-12-23 14:51:15 +01:00
}
2026-01-12 15:40:05 +01:00
#[derive(BinRead)]
struct LzhsOldSegmentHdr {
_uncompressed_size : u32 ,
_compressed_size : u32 ,
}
2026-05-07 18:42:47 +02:00
pub fn decompress_mtk_to_file ( data : & [ u8 ], output_file : & PathBuf ) -> Result < (), Box < dyn std ::error ::Error >> {
2025-12-27 17:09:08 +01:00
let mut out_file = OpenOptions ::new (). append ( true ). create ( true ). open ( & output_file ) ? ;
2026-05-07 18:42:47 +02:00
let mut data_reader = Cursor ::new ( data );
2025-12-23 14:51:15 +01:00
2026-05-07 18:42:47 +02:00
let uncompressed_heading = common ::read_exact ( & mut data_reader , 0x100000 ) ? ; //first 1mb is uncompressed
2025-12-23 14:51:15 +01:00
out_file . write_all ( & uncompressed_heading ) ? ;
2026-05-07 18:42:47 +02:00
while data_reader . stream_position (). unwrap () < data_reader . get_ref (). len () as u64 {
let segment_header : LzhsHeader = data_reader . read_le () ? ;
let comp_header : LzhsHeader = data_reader . read_le () ? ;
2025-12-27 17:09:08 +01:00
2026-05-07 18:42:47 +02:00
println! ( "[cmp] Segment {} - Compressed size: {} , Decompressed size: {} " ,
segment_header . checksum_or_seg_idx , comp_header . compressed_size , comp_header . uncompressed_size );
2025-12-27 17:09:08 +01:00
2026-05-07 18:42:47 +02:00
let compressed_data = common ::read_exact ( & mut data_reader , comp_header . compressed_size as usize ) ? ;
//set comp method
let compression_type : CompressionType ;
if comp_header . checksum_or_seg_idx & 0xFFFFFF00 != 0 { //LZHS uses only a 8 bit checksum, and other use 32 bit(although the type is unknown).
if compressed_data . starts_with ( b " \x28\xB5\x2F\xFD " ) { //ZSTD magic
compression_type = CompressionType ::ZSTD ;
} else {
compression_type = CompressionType ::LZ4 ;
}
} else {
compression_type = CompressionType ::LZHS ;
}
2025-12-23 14:51:15 +01:00
2025-12-27 17:09:08 +01:00
let mut out_data ;
2026-05-07 18:42:47 +02:00
if comp_header . compressed_size == 0 {
2025-12-27 17:09:08 +01:00
//odd variant no.1: if the compressed size is 0 , the output is just zeros with the uncompressed size.
2026-05-07 18:42:47 +02:00
out_data = vec! [ 0 ; comp_header . uncompressed_size as usize ];
2025-12-23 14:51:15 +01:00
2026-05-07 18:42:47 +02:00
} else if ( comp_header . uncompressed_size > segment_header . uncompressed_size ) &&
( comp_header . compressed_size == comp_header . uncompressed_size ) &&
comp_header . checksum_or_seg_idx == 0x00
2025-12-27 17:09:08 +01:00
{
//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 ();
2025-12-23 14:51:15 +01:00
} else {
2025-12-27 17:09:08 +01:00
//normal variant
2026-05-07 18:42:47 +02:00
println! ( "- Decompressing {:?} ..." , compression_type );
if compression_type == CompressionType ::LZ4 {
out_data = decompress_lz4 ( & compressed_data , comp_header . uncompressed_size as i32 ) ? ;
}
else if compression_type == CompressionType ::ZSTD {
out_data = decompress_zstd ( & compressed_data ) ? ;
}
else if compression_type == CompressionType ::LZHS {
2025-12-27 17:09:08 +01:00
let out_huff = unhuff ( & compressed_data );
2026-05-07 18:42:47 +02:00
out_data = unlzss ( & out_huff , comp_header . uncompressed_size as usize );
2025-12-27 17:09:08 +01:00
arm_thumb_convert ( & mut out_data , 0 , false );
let checksum = calc_checksum ( & out_data );
println! ( "-- Calculated checksum: 0x {:02x?} " , checksum );
2026-05-07 18:42:47 +02:00
if u32 ::from ( checksum ) != comp_header . checksum_or_seg_idx {
println! ( "--- Checksum mismatch! Expected: 0x {:02x?} , Got: 0x {:02x?} !" , comp_header . checksum_or_seg_idx , checksum );
return Err ( "LZHS checksum mismatch" . into ());
2025-12-27 17:09:08 +01:00
} else {
println! ( "--- Checksum OK!" )
}
}
2026-05-07 18:42:47 +02:00
else {
return Err ( "undefined compression type" . into ());
}
2025-12-23 14:51:15 +01:00
}
out_file . write_all ( & out_data ) ? ;
//padded to 16 bytes
2026-05-07 18:42:47 +02:00
let pad_pos = ( data_reader . stream_position (). unwrap () + 15 ) & ! 15 ;
data_reader . seek ( SeekFrom ::Start ( pad_pos )) ? ;
2025-12-23 14:51:15 +01:00
}
Ok (())
}
2026-01-12 15:40:05 +01:00
// OLD VARIANT use in old mtk pkg
2026-05-07 18:42:47 +02:00
pub fn decompress_mtk_to_file_old ( data : & [ u8 ], output_file : & PathBuf ) -> Result < (), Box < dyn std ::error ::Error >> {
2026-01-12 15:40:05 +01:00
let mut out_file = OpenOptions ::new (). append ( true ). create ( true ). open ( & output_file ) ? ;
2026-05-07 18:42:47 +02:00
let mut data_reader = Cursor ::new ( data );
2026-01-12 15:40:05 +01:00
2026-05-07 18:42:47 +02:00
let uncompressed_heading = common ::read_exact ( & mut data_reader , 0x200 ) ? ; //first 1mb is uncompressed
2026-01-12 15:40:05 +01:00
out_file . write_all ( & uncompressed_heading ) ? ;
2026-05-07 18:42:47 +02:00
while data_reader . stream_position (). unwrap () < data_reader . get_ref (). len () as u64 {
let _segment_header : LzhsOldSegmentHdr = data_reader . read_le () ? ;
let lzhs_header : LzhsHeader = data_reader . read_le () ? ;
2026-01-12 15:40:05 +01:00
println! ( "[LZHS] Segment - Compressed size: {} , Decompressed size: {} , Expected Checksum: 0x {:02x?} " ,
lzhs_header . compressed_size , lzhs_header . uncompressed_size , lzhs_header . checksum_or_seg_idx );
2026-05-07 18:42:47 +02:00
let compressed_data = common ::read_exact ( & mut data_reader , lzhs_header . compressed_size as usize ) ? ;
2026-01-12 15:40:05 +01:00
let mut out_data ;
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 );
2026-05-07 18:42:47 +02:00
if u32 ::from ( checksum ) != lzhs_header . checksum_or_seg_idx {
2026-01-12 15:40:05 +01:00
println! ( "--- Checksum mismatch! Expected: 0x {:02x?} , Got: 0x {:02x?} !" , lzhs_header . checksum_or_seg_idx , checksum );
2026-05-07 18:42:47 +02:00
return Err ( "LZHS checksum mismatch" . into ());
2026-01-12 15:40:05 +01:00
} else {
println! ( "--- Checksum OK!" )
}
out_file . write_all ( & out_data ) ? ;
}
Ok (())
}
2025-12-23 14:51:15 +01:00
#[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 < Cursor : Read > ( ctx : & mut HuffmanCtx , input : & mut Cursor ) -> bool {
if ctx . bitno > 7 {
ctx . bitno = 0 ;
let mut buf = [ 0 u8 ; 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 < u8 > {
let charlen = & CHARLEN ;
let pos = & POS ;
let mut ctx = HuffmanCtx {
c : 0 ,
code : 0 ,
len : 0 ,
bitno : 8 ,
code_buf : [ 0 u8 ; 32 ],
code_buf_ptr : 1 ,
mask : 1 ,
};
let mut lookup_charlen = vec! [ - 1 i16 ; 131072 ];
let mut lookup_charpos = vec! [ - 1 i16 ; 512 ];
let mut in_cur = Cursor ::new ( data );
let mut out : Vec < u8 > = 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 < u8 > {
let mut window = [ 0 u8 ; 0x1000 ];
let mut dst = Vec ::with_capacity ( expected_size );
let mut src_i = 0 ;
let mut win_pos = 0 usize ;
let mut flags = 0 u32 ;
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
}