From 81fd148cb86e63a8d4d474638e5dabdbb994027d Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:49:02 +0200 Subject: [PATCH] TPV TIMG extractor added --- Cargo.lock | 35 +++++++++++ Cargo.toml | 3 +- src/common.rs | 2 + src/formats/mod.rs | 3 +- src/formats/samsung_old.rs | 2 + src/formats/tpv_timg.rs | 115 +++++++++++++++++++++++++++++++++++++ src/main.rs | 6 +- 7 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 src/formats/tpv_timg.rs diff --git a/Cargo.lock b/Cargo.lock index 04b4668..fdf16c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aes" version = "0.8.4" @@ -192,6 +198,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -218,6 +233,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -291,6 +316,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "once_cell_polyfill" version = "1.70.1" @@ -368,6 +402,7 @@ dependencies = [ "aes", "cbc", "clap", + "flate2", "lz4", "lzma-rs", "md5", diff --git a/Cargo.toml b/Cargo.toml index 70418a8..51bd7a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,5 @@ lzma-rs = "0.3" aes = "0.8.4" cbc = "0.1.2" md5 = "0.7" -sha1 = "0.10" \ No newline at end of file +sha1 = "0.10" +flate2 = "1.0" \ No newline at end of file diff --git a/src/common.rs b/src/common.rs index 81edbf5..2ff5fc7 100644 --- a/src/common.rs +++ b/src/common.rs @@ -6,5 +6,7 @@ pub fn read_file(mut file: &File, offset: u64, size: usize) -> Result, B let mut buffer = vec![0u8; size]; let _bytes_read = file.read(&mut buffer)?; + // reset seek (! + file.seek(SeekFrom::Start(offset))?; Ok(buffer) } \ No newline at end of file diff --git a/src/formats/mod.rs b/src/formats/mod.rs index 54855b4..990ad56 100644 --- a/src/formats/mod.rs +++ b/src/formats/mod.rs @@ -1,2 +1,3 @@ pub mod mstar; -pub mod samsung_old; \ No newline at end of file +pub mod samsung_old; +pub mod tpv_timg; \ No newline at end of file diff --git a/src/formats/samsung_old.rs b/src/formats/samsung_old.rs index 24cb8d6..c5331f9 100644 --- a/src/formats/samsung_old.rs +++ b/src/formats/samsung_old.rs @@ -147,6 +147,8 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo } } + println!(); + println!("Extraction finished!"); Ok(()) } \ No newline at end of file diff --git a/src/formats/tpv_timg.rs b/src/formats/tpv_timg.rs new file mode 100644 index 0000000..4b8b54d --- /dev/null +++ b/src/formats/tpv_timg.rs @@ -0,0 +1,115 @@ +use std::str; +use std::path::{Path}; +use std::io::{self, Read, Write}; +use std::fs::{self, File, OpenOptions}; + +use flate2::read::GzDecoder; + +use crate::common; + +pub fn is_tpv_timg_file(file: &File) -> bool { + let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); + let header_string = String::from_utf8_lossy(&header); + + if header_string == "TIMG"{ + true + } else { + false + } +} + +fn decompress_gzip(compressed_data: &[u8]) -> Result, Box> { + let mut decoder = GzDecoder::new(compressed_data); + let mut decompressed = Vec::new(); + decoder.read_to_end(&mut decompressed)?; + Ok(decompressed) +} + +fn read_exact(reader: &mut R, size: usize) -> io::Result> { + let mut buf = vec![0u8; size]; + reader.read_exact(&mut buf)?; + Ok(buf) +} + +fn string_from_bytes(buf: &[u8]) -> String { + let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len()); + String::from_utf8_lossy(&buf[..end]).to_string() +} + +pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box> { + //TIMG header + let _timg = read_exact(&mut file, 288)?; + + loop { + //PIMG + let mut pimg = [0u8; 4]; + if file.read_exact(&mut pimg).is_err() { + break; //EOF + } else { + assert!(&pimg == b"PIMG", "Invalid PIMG section!"); + } + + //4 bytes 00 + let _ = read_exact(&mut file, 4)?; + + //4 bytes size + let size_bytes = read_exact(&mut file, 4)?; + let size = u32::from_le_bytes(size_bytes.try_into().unwrap()); + + //4 bytes nothing + let _ = read_exact(&mut file, 4)?; + + //16 bytes checksum? or maybe signature + let _checksum = read_exact(&mut file, 16)?; + + //16 bytes name + let name_bytes = read_exact(&mut file, 16)?; + let name = string_from_bytes(&name_bytes); + + //64 bytes destination device + let dev_bytes = read_exact(&mut file, 64)?; + let dev = string_from_bytes(&dev_bytes); + + //16 bytes compression type + let comp_bytes = read_exact(&mut file, 16)?; + let comp_type = string_from_bytes(&comp_bytes); + + //1032 bytes maybe comment? skip this + let _ = read_exact(&mut file, 1032)?; + + //actual data + let data = read_exact(&mut file, size as usize)?; + + println!("PIMG - Name: {} Size: {} Dest: {} Compression: {}", name, size, dev, comp_type); + + let out_data; + + if comp_type == "gzip" { + println!("- Decompressing gzip..."); + out_data = decompress_gzip(&data)?; + } else if comp_type == "none" { + out_data = data; + } else { + println!("- Warning: unsupported compression type!"); + out_data = data; + } + + let output_path = Path::new(&output_folder).join(name + ".bin"); + + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new() + .append(true) + .create(true) + .open(output_path)?; + + out_file.write_all(&out_data)?; + + println!("- Saved file!"); + } + + println!(); + println!("Extraction finished!"); + + Ok(()) +} + diff --git a/src/main.rs b/src/main.rs index 81a2293..c77ab43 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,11 @@ fn main() -> Result<(), Box> { println!(); - if formats::mstar::is_mstar_file(&file) { + if formats::tpv_timg::is_tpv_timg_file(&file) { + println!("TPV TIMG file detected!"); + println!(); + formats::tpv_timg::extract_tpv_timg(&file, &output_path)?; + } else if formats::mstar::is_mstar_file(&file) { println!("Mstar upgrade file detected!"); println!(); formats::mstar::extract_mstar(&file, &output_path)?;