2026-02-05 15:18:26 +01:00
|
|
|
use std::any::Any;
|
2026-02-06 16:27:13 +01:00
|
|
|
use crate::{AppContext, formats::Format};
|
2026-02-05 15:18:26 +01:00
|
|
|
pub fn format() -> Format {
|
2026-02-05 18:53:35 +01:00
|
|
|
Format { name: "mstar", detector_func: is_mstar_file, extractor_func: extract_mstar }
|
2026-02-05 15:18:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
use std::fs::{self, OpenOptions};
|
2025-09-11 22:04:50 +02:00
|
|
|
use std::path::{Path};
|
2025-11-26 17:29:25 +01:00
|
|
|
use std::io::{Write};
|
2025-09-12 22:18:53 +02:00
|
|
|
|
2025-11-26 17:29:25 +01:00
|
|
|
use crate::utils::common;
|
|
|
|
|
use crate::utils::compression::{decompress_lzma, decompress_lz4};
|
2025-11-23 22:37:03 +01:00
|
|
|
use crate::utils::lzop::{unlzop_to_file};
|
2025-11-23 21:24:56 +01:00
|
|
|
use crate::utils::sparse::{unsparse_to_file};
|
|
|
|
|
|
2026-02-05 18:53:35 +01:00
|
|
|
pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
2026-02-06 16:27:13 +01:00
|
|
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
2025-09-11 20:26:51 +02:00
|
|
|
|
2026-02-06 15:08:38 +01:00
|
|
|
let header = common::read_file(&file, 0, 32768)?;
|
|
|
|
|
let header_string = String::from_utf8_lossy(&header);
|
2025-09-12 22:18:53 +02:00
|
|
|
if header_string.contains("filepartload"){
|
2026-02-05 15:18:26 +01:00
|
|
|
Ok(Some(Box::new(())))
|
2025-09-11 20:26:51 +02:00
|
|
|
} else {
|
2026-02-05 15:18:26 +01:00
|
|
|
Ok(None)
|
2025-09-11 20:26:51 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 16:27:13 +01:00
|
|
|
pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let file = app_ctx.file().ok_or("Extractor expected file")?;
|
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;
|
|
|
|
|
|
2025-11-23 22:37:03 +01:00
|
|
|
for line in &lines {
|
2025-09-11 20:26:51 +02:00
|
|
|
if line.starts_with("filepartload") {
|
|
|
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
2025-11-23 22:37:03 +01:00
|
|
|
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('#') {
|
|
|
|
|
// line[idx + 1..].trim()
|
|
|
|
|
//} else {
|
|
|
|
|
// "unknown"
|
|
|
|
|
//};
|
|
|
|
|
let mut partname = "unknown";
|
|
|
|
|
let mut compression = "none";
|
|
|
|
|
let mut lz4_expect_size = 0;
|
|
|
|
|
let mut j = i + 1;
|
2025-10-08 15:14:53 +02:00
|
|
|
|
2025-11-23 22:37:03 +01:00
|
|
|
// get lines after this filepartload, before the next one
|
|
|
|
|
while j < lines.len() && !lines[j].starts_with("filepartload") {
|
|
|
|
|
//get compression method
|
|
|
|
|
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-11-23 22:37:03 +01:00
|
|
|
}
|
|
|
|
|
if lines[j].starts_with("lz4"){
|
|
|
|
|
compression = "lz4";
|
|
|
|
|
let parts: Vec<&str> = lines[j].split_whitespace().collect();
|
|
|
|
|
lz4_expect_size = parse_number(parts[5]).unwrap_or(0);
|
|
|
|
|
}
|
|
|
|
|
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-12 00:11:55 +02:00
|
|
|
}
|
2025-11-23 22:37:03 +01:00
|
|
|
}
|
|
|
|
|
if lines[j].starts_with("sparse_write"){
|
|
|
|
|
compression = "sparse"; //its not really compression but anyway
|
|
|
|
|
let parts: Vec<&str> = lines[j].split_whitespace().collect();
|
|
|
|
|
// get part name from sparse_write
|
|
|
|
|
if partname == "unknown" {
|
|
|
|
|
partname = parts[3]
|
2025-11-23 21:24:56 +01:00
|
|
|
}
|
2025-11-23 22:37:03 +01:00
|
|
|
}
|
2025-09-11 20:26:51 +02:00
|
|
|
|
2025-11-23 22:37:03 +01:00
|
|
|
// check if its boot partition
|
|
|
|
|
if lines[j].starts_with("mmc write.boot") {
|
|
|
|
|
if partname == "unknown" {
|
|
|
|
|
partname = "_mmc_boot"
|
2025-09-11 20:26:51 +02:00
|
|
|
}
|
2025-11-23 22:37:03 +01:00
|
|
|
}
|
2025-09-11 22:04:50 +02:00
|
|
|
|
2025-11-23 22:37:03 +01: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 22:04:50 +02:00
|
|
|
}
|
2025-09-11 20:26:51 +02:00
|
|
|
}
|
2025-11-23 22:37:03 +01:00
|
|
|
|
|
|
|
|
j += 1;
|
|
|
|
|
}
|
2025-09-11 20:26:51 +02:00
|
|
|
|
2025-11-23 22:37:03 +01:00
|
|
|
println!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname);
|
|
|
|
|
|
|
|
|
|
if partname == "unknown" {
|
|
|
|
|
println!("- Unknown destination, skipping!");
|
|
|
|
|
} else {
|
|
|
|
|
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
|
|
|
|
|
let out_data;
|
2026-02-06 15:08:38 +01:00
|
|
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", partname));
|
2025-11-23 22:37:03 +01:00
|
|
|
|
|
|
|
|
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" {
|
|
|
|
|
println!("- Decompessing LZO..");
|
|
|
|
|
unlzop_to_file(&data, output_path)?;
|
|
|
|
|
println!("-- Saved file!");
|
|
|
|
|
i += 1;
|
|
|
|
|
continue
|
|
|
|
|
} else if compression == "sparse" {
|
|
|
|
|
println!("- Unsparsing...");
|
|
|
|
|
unsparse_to_file(&data, output_path)?;
|
|
|
|
|
println!("-- Saved file!");
|
|
|
|
|
i += 1;
|
|
|
|
|
continue
|
2025-09-11 22:04:50 +02:00
|
|
|
} else {
|
2025-11-23 22:37:03 +01:00
|
|
|
out_data = data;
|
|
|
|
|
}
|
2025-10-07 16:00:32 +02:00
|
|
|
|
2026-02-06 15:08:38 +01:00
|
|
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
2025-11-23 22:37:03 +01:00
|
|
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
|
|
|
|
out_file.write_all(&out_data)?;
|
|
|
|
|
println!("-- Saved file!");
|
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
|
|
|
}
|