From f4a146a389a1655ca49b0a84fea7fc8f4a616254 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:54:03 +0100 Subject: [PATCH] msd: add option to print ouith tree instead of static, some improvements to pfl_upg, mtk_pkg: add option to not delete decompressed lzhs part --- README.md | 15 ++++- src/formats/msd/msd_ouith_parser_old.rs | 57 +++++++++---------- src/formats/msd/msd_ouith_parser_tizen_1_8.rs | 53 ++++++++--------- src/formats/msd/msd_ouith_parser_tizen_1_9.rs | 57 +++++++++---------- src/formats/msd10/mod.rs | 5 +- src/formats/msd11/mod.rs | 3 +- src/formats/mtk_pkg/mod.rs | 9 ++- src/formats/mtk_pkg_new/mod.rs | 7 ++- src/formats/mtk_pkg_old/mod.rs | 7 ++- src/formats/pfl_upg/include.rs | 14 ++++- src/formats/pfl_upg/mod.rs | 13 +---- 11 files changed, 128 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 22c2a71..f4faef3 100644 --- a/README.md +++ b/README.md @@ -59,12 +59,15 @@ Options: **Notes:** **Depends on keys** - see keys.rs **Thanks to:** https://github.com/bugficks/msddecrypt **Options:** -`msd10:save_cmac` - Save CMAC data for files that is skipped by default. +`msd10:save_cmac` - Save CMAC data for files that is skipped by default. +`msd:print_ouith` - Prints the entire parsed OUITH header. ## MSD 1.1 **Used in:** Samsung TVs 2016+ **Notes:** **Depends on keys** - see keys.rs (keys 2015-2018, 2020 included) **Thanks to:** https://github.com/bugficks/msddecrypt +**Options:** +`msd:print_ouith` - Prints the entire parsed OUITH header. ## MStar upgrade bin **Used in:** Many MStar-based TVs (Hisense, Toshiba...) @@ -77,15 +80,21 @@ Options: ## MediaTek PKG (New) **Used in:** Newer MediaTek-based TVs (TCL, Hisense, Sony, Philips, CVT...) **Notes:** **Depends on keys** - see keys.rs (Keys for Philips and Sony included) +**Options:** +`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing. ## MediaTek PKG (Old) **Used in:** Older MediaTek-based TVs (Philips, Sony, Hisense...) -**Notes:** All files should be supported, decryption + decompression +**Notes:** All files should be supported, decryption + decompression +**Options:** +`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing. ## MediaTek PKG **Used in:** MediaTek-based TVs (Sony, Philips, Panasonic, Sharp...) **Notes:** All files should be supported, decryption + decompression, however some Philips files use custom keys - most are included some could be missing -**Thanks to:** https://github.com/openlgtv/epk2extract +**Thanks to:** https://github.com/openlgtv/epk2extract +**Options:** +`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing. ## Novatek PKG (NFWB) **Used in:** Some older Novatek-based TVs (LG, Philips) diff --git a/src/formats/msd/msd_ouith_parser_old.rs b/src/formats/msd/msd_ouith_parser_old.rs index a2375d2..69b8b0f 100644 --- a/src/formats/msd/msd_ouith_parser_old.rs +++ b/src/formats/msd/msd_ouith_parser_old.rs @@ -5,9 +5,6 @@ use binrw::{BinRead, BinReaderExt}; use crate::utils::common; -// whether to print the tree -static CONFIG_PRINT_TREE: bool = false; - #[derive(BinRead)] struct ChunkHeader { size: u32, @@ -92,7 +89,7 @@ pub struct MSDItem { //pub secure_hash: Option>, } -pub fn parse_ouith_blob(blob: &[u8]) -> Result<(Vec, Option), Box> { +pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec, Option), Box> { let mut reader = Cursor::new(blob); let mut items: Vec = Vec::new(); let mut info: Option = None; @@ -103,31 +100,31 @@ pub fn parse_ouith_blob(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option = None; @@ -152,9 +149,9 @@ pub fn parse_ouith_blob(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option>, } -pub fn parse_blob_1_8(blob: &[u8]) -> Result<(Vec, Option), Box> { +pub fn parse_blob_1_8(blob: &[u8], print_tree: bool) -> Result<(Vec, Option), Box> { let mut reader = Cursor::new(blob); let mut items: Vec = Vec::new(); let mut info: Option = None; @@ -119,15 +116,15 @@ pub fn parse_blob_1_8(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option = None; //let mut crc32_hash: Option = None; @@ -146,9 +143,9 @@ pub fn parse_blob_1_8(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option>, } -pub fn parse_blob_1_9(blob: &[u8]) -> Result<(Vec, Option), Box> { +pub fn parse_blob_1_9(blob: &[u8], print_tree: bool) -> Result<(Vec, Option), Box> { let mut reader = Cursor::new(blob); let mut items: Vec = Vec::new(); let mut info: Option = None; @@ -124,15 +121,15 @@ pub fn parse_blob_1_9(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option = None; //let mut crc32_hash: Option = None; @@ -151,9 +148,9 @@ pub fn parse_blob_1_9(blob: &[u8]) -> Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result<(Vec, Option Result>, Box) -> Result<(), Box> { let mut file = app_ctx.file().ok_or("Extractor expected file")?; let save_cmac = app_ctx.options.iter().any(|e| e == "msd10:save_cmac"); + let print_ouith_tree = app_ctx.options.iter().any(|e| e == "msd:print_ouith"); let header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); @@ -81,7 +82,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box //parse TOC if firmware_type == "tizen" { let toc = decrypt_aes_salted_tizen(&toc_data, &passphrase_bytes)?; - let (items, info) = parse_blob_1_8(&toc)?; + let (items, info) = parse_blob_1_8(&toc, print_ouith_tree)?; if let Some(info) = info { println!("\nImage info:\n{} {}.{} {}/{}/{}", @@ -121,7 +122,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box } else if firmware_type == "old" { let toc = decrypt_aes_salted_old(&toc_data, &passphrase_bytes)?; - let (items, info) = parse_ouith_blob(&toc)?; + let (items, info) = parse_ouith_blob(&toc, print_ouith_tree)?; if let Some(info) = info { println!("\nImage info:\n{} {}.{} {}/{}/20{}", diff --git a/src/formats/msd11/mod.rs b/src/formats/msd11/mod.rs index 09bbb69..8325965 100644 --- a/src/formats/msd11/mod.rs +++ b/src/formats/msd11/mod.rs @@ -26,6 +26,7 @@ pub fn is_msd11_file(app_ctx: &AppContext) -> Result>, Box) -> Result<(), Box> { let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let print_ouith_tree = app_ctx.options.iter().any(|e| e == "msd:print_ouith"); let header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); @@ -73,7 +74,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box let toc_data = common::read_file(&file, toc_offset as u64, toc_size as usize)?; let toc = decrypt_aes_salted_tizen(&toc_data, &passphrase_bytes)?; - let (items, info) = parse_blob_1_9(&toc)?; + let (items, info) = parse_blob_1_9(&toc, print_ouith_tree)?; if let Some(info) = info { println!("\nImage info:\n{} {}.{}", diff --git a/src/formats/mtk_pkg/mod.rs b/src/formats/mtk_pkg/mod.rs index f728ed6..6743cf9 100644 --- a/src/formats/mtk_pkg/mod.rs +++ b/src/formats/mtk_pkg/mod.rs @@ -43,6 +43,7 @@ pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result>, Box pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box) -> Result<(), Box> { let mut file = app_ctx.file().ok_or("Extractor expected file")?; let ctx = ctx.downcast::().expect("Missing context"); + let no_del_comp = app_ctx.options.iter().any(|e| e == "mtk_pkg:no_del_comp"); let file_size = file.metadata()?.len(); let header = ctx.decrypted_header; @@ -148,9 +149,11 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box) -> Result<(), Bo let lzhs_out_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + ".bin"); match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) { Ok(()) => { - println!("- Decompressed Successfully!"); - //after successfull decompression remove the temporary .lzhs file - fs::remove_file(&output_path)?; + println!("- Decompressed Successfully!"); + if !no_del_comp { + //after successfull decompression remove the temporary .lzhs file + fs::remove_file(&output_path)?; + } }, Err(e) => { eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e); diff --git a/src/formats/mtk_pkg_new/mod.rs b/src/formats/mtk_pkg_new/mod.rs index 1203a8e..7b926ce 100644 --- a/src/formats/mtk_pkg_new/mod.rs +++ b/src/formats/mtk_pkg_new/mod.rs @@ -46,6 +46,7 @@ pub fn is_mtk_pkg_new_file(app_ctx: &AppContext) -> Result>, pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box) -> Result<(), Box> { let mut file = app_ctx.file().ok_or("Extractor expected file")?; let ctx = ctx.downcast::().expect("Missing context"); + let no_del_comp = app_ctx.options.iter().any(|e| e == "mtk_pkg:no_del_comp"); let file_size = file.metadata()?.len(); @@ -119,8 +120,10 @@ pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box) -> Result<() match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) { Ok(()) => { println!("-- Decompressed Successfully!"); - //after successfull decompression remove the temporary .lzhs file - fs::remove_file(&output_path)?; + if !no_del_comp { + //after successfull decompression remove the temporary .lzhs file + fs::remove_file(&output_path)?; + } }, Err(e) => { eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e); diff --git a/src/formats/mtk_pkg_old/mod.rs b/src/formats/mtk_pkg_old/mod.rs index 0d91749..9e7ca9f 100644 --- a/src/formats/mtk_pkg_old/mod.rs +++ b/src/formats/mtk_pkg_old/mod.rs @@ -36,6 +36,7 @@ pub fn is_mtk_pkg_old_file(app_ctx: &AppContext) -> Result>, pub fn extract_mtk_pkg_old(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let no_del_comp = app_ctx.options.iter().any(|e| e == "mtk_pkg:no_del_comp"); let file_size = file.metadata()?.len(); let encrypted_header = common::read_exact(&mut file, HEADER_SIZE)?; @@ -89,8 +90,10 @@ pub fn extract_mtk_pkg_old(app_ctx: &AppContext, _ctx: Box) -> Result<( match decompress_lzhs_fs_file2file_old(&out_file, lzhs_out_path) { Ok(()) => { println!("- Decompressed Successfully!"); - //after successfull decompression remove the temporary .lzhs file - fs::remove_file(&output_path)?; + if !no_del_comp { + //after successfull decompression remove the temporary .lzhs file + fs::remove_file(&output_path)?; + } }, Err(e) => { eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e); diff --git a/src/formats/pfl_upg/include.rs b/src/formats/pfl_upg/include.rs index 233e540..b79bf5a 100644 --- a/src/formats/pfl_upg/include.rs +++ b/src/formats/pfl_upg/include.rs @@ -48,7 +48,10 @@ pub struct Header { } impl Header { pub fn description(&self) -> String { - common::string_from_bytes(&self.description_bytes) + common::string_from_bytes(&self.description_bytes).replace('\r', "\n") + } + pub fn is_encrypted(&self) -> bool { + (self.mask & 0x2000_0000) != 0 } } @@ -64,4 +67,13 @@ impl FileHeader { pub fn file_name(&self) -> String { common::string_from_bytes(&self.file_name_bytes) } + pub fn is_folder(&self) -> bool { + (self.attributes[3] & (1 << 1)) != 0 + } + pub fn has_extended_name(&self) -> bool { + (self.attributes[2] & (1 << 7)) != 0 + } + pub fn _is_package(&self) -> bool { + (self.attributes[3] & (1 << 1)) != 0 + } } \ No newline at end of file diff --git a/src/formats/pfl_upg/mod.rs b/src/formats/pfl_upg/mod.rs index df189ea..45889f8 100644 --- a/src/formats/pfl_upg/mod.rs +++ b/src/formats/pfl_upg/mod.rs @@ -39,7 +39,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box) -> Result<(), B println!("Data size: {}", header.data_size); let mut decrypted_data; - if (header.mask & 0x2000_0000) != 0 { + if header.is_encrypted() { println!("File is encrypted."); let mut key = None; let mut n_hex = None; @@ -95,16 +95,14 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box) -> Result<(), B while (data_reader.position() as usize) < data_reader.get_ref().len() { let file_header: FileHeader = data_reader.read_le()?; - //its a folder not a file - if (file_header.attributes[3] & (1 << 1)) != 0 { + if file_header.is_folder() { println!("\nFolder - {}", file_header.file_name()); let output_path = Path::new(&app_ctx.output_dir).join(file_header.file_name().trim_start_matches('/')); fs::create_dir_all(output_path)?; continue } - //extended name is used - let file_name = if (file_header.attributes[2] & (1 << 7)) != 0 { + let file_name = if file_header.has_extended_name() { let ex_name_size = file_header.header_size - 76; //76 is base file header size //println!("extended name {}, org name: {}", ex_name_size, file_header.file_name()); let ex_name_bytes = common::read_exact(&mut data_reader, ex_name_size as usize)?; @@ -134,11 +132,6 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box) -> Result<(), B out_file.write_all(&data[..file_header.real_size as usize])?; - //if it contains a PFL upg in itself to extract - //if (file_header.attributes[3] & (1 << 2)) != 0 { - // println!("Container file"); - //} - println!("- Saved file!"); }