mstar: further code optimizations + lzop optimization. fix memory issue by writing directly to file

This commit is contained in:
theubusu
2025-11-23 22:37:03 +01:00
parent faad86f5a7
commit 7637cdfe68
2 changed files with 106 additions and 113 deletions
+99 -108
View File
@@ -5,12 +5,12 @@ use lz4::block::decompress;
use lzma_rs::lzma_decompress; use lzma_rs::lzma_decompress;
use crate::common; use crate::common;
use crate::utils::lzop::{decompress_lzop}; use crate::utils::lzop::{unlzop_to_file};
use crate::utils::sparse::{unsparse_to_file}; use crate::utils::sparse::{unsparse_to_file};
//change whether the "userdata" partition is skipped //change whether the "userdata" partition is skipped
// this is because the userdata partition is sometimes enourmous sizes like 27gb, and it will fail to allocate memory on most computers // this is because the userdata partition is sometimes enourmous sizes like 27gb, and it is empty anyways
// if you want to attempt to extract "userdata" you can enable the option // if you want to extract "userdata" you can disable the option
static CONFIG_SKIP_USERDATA: bool = true; static CONFIG_SKIP_USERDATA: bool = true;
pub fn is_mstar_file(file: &File) -> bool { pub fn is_mstar_file(file: &File) -> bool {
@@ -76,125 +76,116 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
let lines: Vec<&str> = script_string.lines().map(|l| l.trim()).collect(); let lines: Vec<&str> = script_string.lines().map(|l| l.trim()).collect();
let mut i = 0; let mut i = 0;
while i < lines.len() { for line in &lines {
let line = lines[i];
if line.starts_with("filepartload") { if line.starts_with("filepartload") {
let parts: Vec<&str> = line.split_whitespace().collect(); let parts: Vec<&str> = line.split_whitespace().collect();
let offset = parse_number(parts[3]).unwrap_or(0);
let size = parse_number(parts[4]).unwrap_or(0);
if parts.len() >= 5 { //try to get partname from comment
let offset = parse_number(parts[3]).unwrap_or(0); //let mut partname = if let Some(idx) = line.find('#') {
let size = parse_number(parts[4]).unwrap_or(0); // line[idx + 1..].trim()
//} else {
//try to get partname from comment // "unknown"
//let mut partname = if let Some(idx) = line.find('#') { //};
// line[idx + 1..].trim() let mut partname = "unknown";
//} else { let mut compression = "none";
// "unknown" let mut lz4_expect_size = 0;
//}; let mut j = i + 1;
let mut partname = "unknown";
let mut compression = "none";
let mut lz4_expect_size = 0;
let mut j = i + 1;
// get lines after this filepartload, before the next one // get lines after this filepartload, before the next one
while j < lines.len() && !lines[j].starts_with("filepartload") { while j < lines.len() && !lines[j].starts_with("filepartload") {
//get compression method //get compression method
if lines[j].starts_with("mscompress7"){ if lines[j].starts_with("mscompress7"){
if compression == "none" { if compression == "none" {
compression = "lzma"; compression = "lzma";
} else if compression == "lzma" { } else if compression == "lzma" {
//thank the turks //thank the turks
compression = "double_lzma"; compression = "double_lzma";
}
} }
if lines[j].starts_with("lz4"){ }
compression = "lz4"; if lines[j].starts_with("lz4"){
let parts: Vec<&str> = lines[j].split_whitespace().collect(); compression = "lz4";
lz4_expect_size = parse_number(parts[5]).unwrap_or(0); 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]
} }
if lines[j].starts_with("mmc unlzo"){ }
compression = "lzo"; if lines[j].starts_with("sparse_write"){
let parts: Vec<&str> = lines[j].split_whitespace().collect(); compression = "sparse"; //its not really compression but anyway
// get part name from mmc unlzo let parts: Vec<&str> = lines[j].split_whitespace().collect();
if partname == "unknown" { // get part name from sparse_write
partname = parts[4] if partname == "unknown" {
} partname = parts[3]
} }
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]
}
}
// check if its boot partition
if lines[j].starts_with("mmc write.boot") {
if partname == "unknown" {
partname = "_mmc_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!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname); // check if its boot partition
if lines[j].starts_with("mmc write.boot") {
if partname == "unknown" { if partname == "unknown" {
println!("- Unknown destination, skipping!"); partname = "_mmc_boot"
} else if partname == "userdata" && CONFIG_SKIP_USERDATA {
println!("- Skipping userdata according to config!")
} else {
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let out_data;
let output_path = Path::new(&output_folder).join(format!("{}.bin", partname));
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..");
out_data = decompress_lzop(&data)?;
} else if compression == "sparse" {
println!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
i += 1;
continue
//out_data = unsparse(&data)?;
} else {
out_data = data;
} }
}
fs::create_dir_all(&output_folder)?; // try to get partname from nand/mmc/ubi writes
let mut out_file = OpenOptions::new() if lines[j].starts_with("mmc write") | lines[j].starts_with("nand write") | lines[j].starts_with("ubi write"){
.append(true) let parts: Vec<&str> = lines[j].split_whitespace().collect();
.create(true) if partname == "unknown" {
.open(output_path)?; partname = parts[3]
}
}
j += 1;
}
out_file.write_all(&out_data)?; println!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname);
if partname == "unknown" {
println!("- Unknown destination, skipping!");
} else if partname == "userdata" && CONFIG_SKIP_USERDATA {
println!("- Skipping userdata according to config!")
} else {
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let out_data;
let output_path = Path::new(&output_folder).join(format!("{}.bin", partname));
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!"); println!("-- Saved file!");
} i += 1;
continue
} else if compression == "sparse" {
println!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
i += 1;
continue
} else {
out_data = data;
}
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!");
} }
} }
+7 -5
View File
@@ -1,5 +1,7 @@
use binrw::{BinRead, BinReaderExt}; use binrw::{BinRead, BinReaderExt};
use std::io::{Cursor}; use std::fs::OpenOptions;
use std::path::{PathBuf};
use std::io::{Cursor, Write};
use simd_adler32::adler32; use simd_adler32::adler32;
use crate::common; use crate::common;
@@ -28,7 +30,7 @@ struct SegmentHeader {
checksum: u32, checksum: u32,
} }
pub fn decompress_lzop(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> { pub fn unlzop_to_file(data: &[u8], file_path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let mut data_reader = Cursor::new(data); let mut data_reader = Cursor::new(data);
let header: LzopHeader = data_reader.read_be()?; let header: LzopHeader = data_reader.read_be()?;
if header.magic_bytes != b"\x89LZO\x00\x0D\x0A\x1A\x0A" { if header.magic_bytes != b"\x89LZO\x00\x0D\x0A\x1A\x0A" {
@@ -39,7 +41,7 @@ pub fn decompress_lzop(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error
} }
let lzo = minilzo_rs::LZO::init()?; let lzo = minilzo_rs::LZO::init()?;
let mut decompressed_output = Vec::new(); let mut out_file = OpenOptions::new().create(true).append(true).open(file_path)?;
loop { loop {
if (data.len() as u64 - data_reader.position()) < 12 { //check if there are enough bytes to read a segment header if (data.len() as u64 - data_reader.position()) < 12 { //check if there are enough bytes to read a segment header
break; break;
@@ -66,8 +68,8 @@ pub fn decompress_lzop(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error
return Err("Invalid segment checksum! Data corrupted?".into()); return Err("Invalid segment checksum! Data corrupted?".into());
}; };
decompressed_output.extend_from_slice(&out_data); out_file.write_all(&out_data)?;
} }
Ok(decompressed_output) Ok(())
} }