diff --git a/Cargo.lock b/Cargo.lock index b943780..c05c760 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "cc" +version = "1.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "clap" version = "4.5.47" @@ -98,6 +108,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + [[package]] name = "heck" version = "0.5.0" @@ -110,6 +126,31 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "lz4" +version = "1.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20b523e860d03443e98350ceaac5e71c6ba89aea7d960769ec3ce37f4de5af4" +dependencies = [ + "lz4-sys", +] + +[[package]] +name = "lz4-sys" +version = "1.11.1+lz4-1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "once_cell_polyfill" version = "1.70.1" @@ -134,6 +175,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "strsim" version = "0.11.1" @@ -162,6 +209,7 @@ name = "unixtract" version = "0.1.0" dependencies = [ "clap", + "lz4", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 65950ac..991eb16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] -clap = { version = "4.5", features = ["derive"] } \ No newline at end of file +clap = { version = "4.5", features = ["derive"] } +lz4 = "1.23.1" \ No newline at end of file diff --git a/src/formats/mstar.rs b/src/formats/mstar.rs index 1b894dd..e3abfeb 100644 --- a/src/formats/mstar.rs +++ b/src/formats/mstar.rs @@ -1,8 +1,11 @@ -use std::fs::{File}; +use std::fs::{self, File, OpenOptions}; +use std::path::{Path}; +use std::io::{Write}; +use lz4::block::decompress; use crate::common; pub fn is_mstar_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 32768).expect("REASON"); + let header = common::read_file(&file, 0, 32768).expect("Failed to read from file."); let header_string = String::from_utf8_lossy(&header); if header_string.contains("filepartload") & header_string.contains("MstarUpgrade") | header_string.contains("CtvUpgrade") { @@ -12,18 +15,43 @@ pub fn is_mstar_file(file: &File) -> bool { } } -pub fn extract_mstar(file: &File) { - println!("extract mstar file"); +fn parse_number(s: &str) -> Option { + if let Some(hex_str) = s.strip_prefix("0x") { + u64::from_str_radix(hex_str, 16).ok() + } else { + u64::from_str_radix(s, 16).ok() + } +} - let mut script = common::read_file(&file, 0, 32768).expect("REASON"); +fn decompress_lz4(compressed_data: &[u8], original_size: i32) -> Result, std::io::Error> { + match decompress(compressed_data, Some(original_size)) { + Ok(decompressed) => Ok(decompressed), + Err(e) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, e)), + } +} + +pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box> { + println!(); + let mut script = common::read_file(&file, 0, 32768)?; if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) { script.truncate(pos); } - let script_string = String::from_utf8_lossy(&script); + let mut script_string = String::from_utf8_lossy(&script); //println!("{}", script_string); + if script_string == "" { + //try ts hisense + println!("Failed to get script at 0x0, trying 0x1000..."); + script = common::read_file(&file, 4096, 32768)?; + script_string = String::from_utf8_lossy(&script); + + if script_string == "" { + println!("Failed to get script."); + } + } + let lines: Vec<&str> = script_string.lines().map(|l| l.trim()).collect(); let mut i = 0; @@ -34,8 +62,8 @@ pub fn extract_mstar(file: &File) { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 5 { - let offset = parts[3]; - let size = parts[4]; + let offset = parse_number(parts[3]).unwrap_or(0); + let size = parse_number(parts[4]).unwrap_or(0); //try to get partname from comment let mut partname = if let Some(idx) = line.find('#') { @@ -45,6 +73,7 @@ pub fn extract_mstar(file: &File) { }; let mut compression = "none"; + let mut lz4_expect_size = 0; let mut j = i + 1; while j < lines.len() && !lines[j].starts_with("filepartload") { //get compression method @@ -54,12 +83,9 @@ pub fn extract_mstar(file: &File) { compression = "lz4"; } - // try to get partname from nand/mmc/ubi writes - if lines[j].starts_with("mmc write") | lines[j].starts_with("nand write") | lines[j].starts_with("ubi write"){ + if lines[j].starts_with("lz4"){ let parts: Vec<&str> = lines[j].split_whitespace().collect(); - if partname == "unknown" { - partname = parts[3] - } + lz4_expect_size = parse_number(parts[5]).unwrap_or(0); } // check if its boot partition @@ -68,14 +94,52 @@ pub fn extract_mstar(file: &File) { partname = "boot" } } + + // try to get partname from nand/mmc/ubi writes + if lines[j].starts_with("mmc write") | lines[j].starts_with("nand write") | lines[j].starts_with("ubi write"){ + let parts: Vec<&str> = lines[j].split_whitespace().collect(); + if partname == "unknown" { + partname = parts[3] + } + } + j += 1; } println!("offset: {} size: {} --> {} (compression: {})", offset, size, partname, compression); + + let data = common::read_file(&file, offset, size.try_into().unwrap())?; + + //println!("{:02x?}", &data[0]); + //println!("{}", data.len()); + + let out_data; + + if compression == "lzma" { + // TODO: do lzma decompression + out_data = data; + } else if compression == "lz4" { + println!("- Decompressing lz4, expected size: {}", lz4_expect_size); + out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?; + //out_data = data; + } else { + out_data = data; + } + + let output_path = Path::new(&output_folder).join(partname.to_owned() + ".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)?; } } i += 1; } + Ok(()) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 714c26a..2414a89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,15 +27,15 @@ fn main() -> Result<(), Box> { //println!("file"); let file = File::open(path)?; + println!(); + if formats::mstar::is_mstar_file(&file) { println!("Mstar upgrade file detected!"); - formats::mstar::extract_mstar(&file); + formats::mstar::extract_mstar(&file, &output_path)?; } else { println!("File format not recognized!"); } } - - Ok(()) }