Files
unixtract/src/formats/mstar.rs
T

182 lines
6.6 KiB
Rust
Raw Normal View History

2025-09-11 22:04:50 +02:00
use std::fs::{self, File, OpenOptions};
use std::path::{Path};
2025-09-12 00:11:55 +02:00
use std::io::{Write, Cursor};
2025-09-11 22:04:50 +02:00
use lz4::block::decompress;
2025-09-12 00:11:55 +02:00
use lzma_rs::lzma_decompress;
2025-09-12 22:18:53 +02:00
2025-09-11 20:26:51 +02:00
use crate::common;
pub fn is_mstar_file(file: &File) -> bool {
2025-09-11 22:04:50 +02:00
let header = common::read_file(&file, 0, 32768).expect("Failed to read from file.");
2025-09-11 20:26:51 +02:00
let header_string = String::from_utf8_lossy(&header);
2025-09-12 22:18:53 +02:00
if header_string.contains("filepartload"){
2025-09-11 20:26:51 +02:00
true
} else {
false
}
}
2025-09-11 22:04:50 +02:00
fn parse_number(s: &str) -> Option<u64> {
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()
}
}
2025-09-12 00:11:55 +02:00
fn decompress_lzma(compressed_data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut input = Cursor::new(compressed_data);
let mut output = Vec::new();
lzma_decompress(&mut input, &mut output)?;
Ok(output)
}
2025-09-11 22:04:50 +02:00
fn decompress_lz4(compressed_data: &[u8], original_size: i32) -> Result<Vec<u8>, 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)),
}
}
2025-09-11 20:26:51 +02:00
2025-09-11 22:04:50 +02:00
pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
2025-09-12 00:11:55 +02:00
2025-09-11 22:04:50 +02:00
let mut script = common::read_file(&file, 0, 32768)?;
2025-09-11 20:26:51 +02:00
if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) {
script.truncate(pos);
}
2025-09-11 22:04:50 +02:00
let mut script_string = String::from_utf8_lossy(&script);
2025-09-11 20:26:51 +02:00
//println!("{}", script_string);
2025-09-11 22:04:50 +02:00
if script_string == "" {
2025-09-12 00:11:55 +02:00
//try for hisense
2025-09-11 22:04:50 +02:00
println!("Failed to get script at 0x0, trying 0x1000...");
script = common::read_file(&file, 4096, 32768)?;
2025-09-12 00:11:55 +02:00
if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) {
script.truncate(pos);
}
2025-09-11 22:04:50 +02:00
script_string = String::from_utf8_lossy(&script);
if script_string == "" {
println!("Failed to get script.");
}
}
2025-09-11 20:26:51 +02:00
let lines: Vec<&str> = script_string.lines().map(|l| l.trim()).collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
if line.starts_with("filepartload") {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 5 {
2025-09-11 22:04:50 +02:00
let offset = parse_number(parts[3]).unwrap_or(0);
let size = parse_number(parts[4]).unwrap_or(0);
2025-09-11 20:26:51 +02:00
//try to get partname from comment
let mut partname = if let Some(idx) = line.find('#') {
line[idx + 1..].trim()
} else {
"unknown"
};
let mut compression = "none";
2025-09-11 22:04:50 +02:00
let mut lz4_expect_size = 0;
2025-09-11 20:26:51 +02:00
let mut j = i + 1;
2025-09-12 22:18:53 +02:00
// get lines after this filepartload, before the next one
2025-09-11 20:26:51 +02:00
while j < lines.len() && !lines[j].starts_with("filepartload") {
//get compression method
2025-09-12 00:11:55 +02:00
if lines[j].starts_with("mscompress7"){
if compression == "none" {
compression = "lzma";
} else if compression == "lzma" {
//thank the turks
compression = "double_lzma";
}
2025-09-11 20:26:51 +02:00
}
2025-09-11 22:04:50 +02:00
if lines[j].starts_with("lz4"){
2025-09-12 00:11:55 +02:00
compression = "lz4";
2025-09-11 20:26:51 +02:00
let parts: Vec<&str> = lines[j].split_whitespace().collect();
2025-09-11 22:04:50 +02:00
lz4_expect_size = parse_number(parts[5]).unwrap_or(0);
2025-09-11 20:26:51 +02:00
}
2025-09-12 00:11:55 +02:00
if lines[j].starts_with("mmc unlzo"){
compression = "lzo";
let parts: Vec<&str> = lines[j].split_whitespace().collect();
// get part name from mmc unlzo
if partname == "unknown" {
partname = parts[4]
}
}
2025-09-11 20:26:51 +02:00
// check if its boot partition
if lines[j].starts_with("mmc write.boot") {
if partname == "unknown" {
2025-09-12 22:18:53 +02:00
partname = "_mmc_boot"
2025-09-11 20:26:51 +02:00
}
}
2025-09-11 22:04:50 +02:00
// 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]
}
}
2025-09-11 20:26:51 +02:00
j += 1;
}
2025-09-12 22:18:53 +02:00
println!("Part - Offset: {}, Size: {} --> {}", offset, size, partname);
2025-09-11 22:04:50 +02:00
2025-09-12 00:11:55 +02:00
if partname == "unknown"{
println!("- Unknown destination, skipping!");
2025-09-11 22:04:50 +02:00
} else {
2025-09-12 00:11:55 +02:00
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let out_data;
if compression == "lzma" {
println!("- Decompressing LZMA...");
out_data = decompress_lzma(&data)?;
} else if compression == "double_lzma" {
println!("- Decompressing LZMA (Pass 1)...");
let pass_1 = decompress_lzma(&data)?;
println!("- Decompressing LZMA (Pass 2)...");
out_data = decompress_lzma(&pass_1)?;
} else if compression == "lz4" {
println!("- Decompressing lz4, expected size: {}", lz4_expect_size);
out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?;
} else if compression == "lzo" {
2025-09-12 22:18:53 +02:00
// nothing in rust to parse lzo file with header
println!("- lzo compression is not supported yet!!...");
2025-09-12 00:11:55 +02:00
out_data = data;
}else {
out_data = data;
}
2025-09-11 22:04:50 +02:00
2025-09-12 00:11:55 +02:00
let output_path = Path::new(&output_folder).join(partname.to_owned() + ".bin");
2025-09-11 22:04:50 +02:00
2025-09-12 00:11:55 +02:00
fs::create_dir_all(&output_folder)?;
let mut out_file = OpenOptions::new()
.append(true)
.create(true)
.open(output_path)?;
2025-09-11 22:04:50 +02:00
2025-09-12 00:11:55 +02:00
out_file.write_all(&out_data)?;
}
2025-09-11 20:26:51 +02:00
}
}
i += 1;
}
2025-09-12 00:11:55 +02:00
println!();
println!("Extraction finished!");
2025-09-11 22:04:50 +02:00
Ok(())
2025-09-11 20:26:51 +02:00
}