diff --git a/src/formats.rs b/src/formats.rs index d0d70d1..6370498 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -1,5 +1,14 @@ +use std::any::Any; +use crate::ProgramContext; + +pub struct Format { + pub name: &'static str, + pub detect_func: fn(&ProgramContext) -> Result>, Box>, + pub run_func: fn(&ProgramContext, Option>) -> Result<(), Box>, +} + pub mod mstar; -pub mod samsung_old; +//pub mod samsung_old; not sure what to do with this pub mod nvt_timg; pub mod pfl_upg; pub mod sddl_sec; @@ -31,4 +40,36 @@ pub mod epk3; pub mod mtk_pkg; pub mod mtk_pkg_old; pub mod mtk_pkg_new; -pub mod mtk_bdp; \ No newline at end of file +pub mod mtk_bdp; + +pub fn get_registry() -> Vec { + return vec![ + crate::formats::amlogic::format(), + crate::formats::epk1::format(), + crate::formats::android_ota_payload::format(), + crate::formats::bdl::format(), + crate::formats::epk2::format(), + crate::formats::epk::format(), + crate::formats::epk2b::format(), + crate::formats::funai_upg::format(), + crate::formats::invincible_image::format(), + crate::formats::msd10::format(), + crate::formats::msd11::format(), + crate::formats::mstar::format(), + crate::formats::novatek::format(), + crate::formats::nvt_timg::format(), + crate::formats::pfl_upg::format(), + crate::formats::pup::format(), + crate::formats::roku::format(), + crate::formats::ruf::format(), + crate::formats::rvp::format(), + crate::formats::sddl_sec::format(), + crate::formats::slp::format(), + crate::formats::mtk_pkg_old::format(), + crate::formats::mtk_pkg::format(), + crate::formats::sony_bdp::format(), + crate::formats::mtk_pkg_new::format(), + crate::formats::pana_dvd::format(), + crate::formats::mtk_bdp::format(), + ] +} \ No newline at end of file diff --git a/src/formats/amlogic.rs b/src/formats/amlogic.rs index 0f4180d..08d4793 100644 --- a/src/formats/amlogic.rs +++ b/src/formats/amlogic.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "amlogic", detect_func: is_amlogic_file, run_func: extract_amlogic } +} + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; @@ -44,16 +49,17 @@ impl ItemEntry { } } -pub fn is_amlogic_file(file: &File) -> bool { - let header = common::read_file(&file, 8, 4).expect("Failed to read from file."); +pub fn is_amlogic_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 8, 4)?; if header == b"\x56\x19\xB5\x27" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_amlogic(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; file.seek(SeekFrom::Start(0))?; let header: ImageHeader = file.read_le()?; @@ -85,8 +91,8 @@ pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "android_ota_payload", detect_func: is_android_ota_payload_file, run_func: extract_android_ota_payload } +} + +use std::fs::{self, OpenOptions}; use std::path::{Path}; use std::io::{Write}; use binrw::{BinRead, BinReaderExt}; @@ -16,16 +22,17 @@ struct Header { metadata_signature_size: u32, } -pub fn is_android_ota_payload_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_android_ota_payload_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"CrAU" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_android_ota_payload(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_android_ota_payload(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_be()?; println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size); @@ -75,8 +82,8 @@ pub fn extract_android_ota_payload(mut file: &File, output_folder: &str) -> Resu break } - fs::create_dir_all(&output_folder)?; - let output_path = Path::new(&output_folder).join(format!("{}.bin", partition.partition_name)); + fs::create_dir_all(app_ctx.output_dir)?; + let output_path = Path::new(app_ctx.output_dir).join(format!("{}.bin", partition.partition_name)); let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?; out_file.write_all(&out_data)?; } diff --git a/src/formats/bdl.rs b/src/formats/bdl.rs index bca2206..2b66f41 100644 --- a/src/formats/bdl.rs +++ b/src/formats/bdl.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "bdl", detect_func: is_bdl_file, run_func: extract_bdl } +} + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; @@ -80,16 +85,17 @@ impl PkgEntry { } } -pub fn is_bdl_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_bdl_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"ibdl" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_bdl(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_bdl(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: BdlHeader = file.read_le()?; println!("File info:\nPackage count: {}\nDate: {}\nManufacturer: {}\nModel: {}\nVersion: {}\nInfo: {}", @@ -116,7 +122,7 @@ pub fn extract_bdl(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "epk", detect_func: is_epk_file, run_func: extract_epk } +} use crate::utils::common; use crate::formats; -pub fn is_epk_file(file: &File) -> bool { - let versions = common::read_file(&file, 1712, 36).expect("Failed to read from file."); +pub struct EpkContext { + epk_version: u8, +} - if check_epk_version(&versions).is_some() { - true +pub fn is_epk_file(app_ctx: &ProgramContext) -> Result>, Box> { + let versions = common::read_file(app_ctx.file, 1712, 36)?; + + if let Some(epk_version) = check_epk_version(&versions) { + Ok(Some(Box::new(EpkContext {epk_version}))) } else { - false + Ok(None) } } -fn check_epk_version(versions: &[u8]) -> Option { +fn check_epk_version(versions: &[u8]) -> Option { // _ - 0x00 X - a number . - a dot let epk2_pattern = "____XXXX.XXXX.XXXX__XX.XX.XXX_______"; let epk3_pattern = "____X.X.X___________X.X.X___________"; let epk3_new_pattern = "____XX.X.X__________XX.X.X__________"; if match_with_pattern(&versions, epk2_pattern) { - Some("epk2".to_string()) + Some(2) } else if match_with_pattern(&versions, epk3_pattern) { - Some("epk3".to_string()) + Some(3) } else if match_with_pattern(&versions, epk3_new_pattern) { - Some("epk3".to_string()) + Some(3) }else { None } @@ -42,20 +50,21 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool { true } -pub fn extract_epk(file: &File, output_folder: &str) -> Result<(), Box> { - let versions = common::read_file(&file, 1712, 36)?; - let epk_version = check_epk_version(&versions); +pub fn extract_epk(app_ctx: &ProgramContext, ctx: Option>) -> Result<(), Box> { + let ctx = ctx.and_then(|c| c.downcast::().ok()).ok_or("Context is invalid or missing!")?; + + let versions = common::read_file(app_ctx.file, 1712, 36)?; let platform_version = common::string_from_bytes(&versions[4..20]); let sdk_version = common::string_from_bytes(&versions[20..36]); println!("Platform version: {}\nSDK version: {}", platform_version, sdk_version); - if epk_version == Some("epk2".to_string()) { + if ctx.epk_version == 2 { println!("EPK2 detected!\n"); - formats::epk2::extract_epk2(file, output_folder)?; - } else if epk_version == Some("epk3".to_string()) { + formats::epk2::extract_epk2(app_ctx, None)?; + } else if ctx.epk_version == 3 { println!("EPK3 detected!\n"); - formats::epk3::extract_epk3(file, output_folder)?; + formats::epk3::extract_epk3(app_ctx, None)?; } Ok(()) diff --git a/src/formats/epk1.rs b/src/formats/epk1.rs index 38ec105..9422ad4 100644 --- a/src/formats/epk1.rs +++ b/src/formats/epk1.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "epk1", detect_func: is_epk1_file, run_func: extract_epk1 } +} + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; @@ -35,17 +40,18 @@ struct Pak { size : u32, } -pub fn is_epk1_file(file: &File) -> bool { - let epk2_magic = common::read_file(&file, 12, 4).expect("Failed to read from file."); //for epk2b - let epak_magic = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_epk1_file(app_ctx: &ProgramContext) -> Result>, Box> { + let epk2_magic = common::read_file(app_ctx.file, 12, 4)?; //for epk2b + let epak_magic = common::read_file(app_ctx.file, 0, 4)?; if epak_magic == b"epak" && epk2_magic != b"EPK2" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_epk1(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; //check type of epk1 let epk1_type; let init_pak_count_bytes = common::read_file(&file, 8, 4)?; @@ -119,8 +125,8 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "epk2", detect_func: is_epk2_file, run_func: extract_epk2 } +} + +use std::fs::{self, OpenOptions}; use std::path::{Path}; use std::io::{Write, Seek, SeekFrom, Cursor}; use binrw::{BinRead, BinReaderExt}; @@ -65,16 +71,17 @@ struct Pak { name: String, } -pub fn is_epk2_file(file: &File) -> bool { - let header = common::read_file(&file, 128, 4).expect("Failed to read from file."); +pub fn is_epk2_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 128, 4)?; if header == b"epak" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_epk2(app_ctx: &ProgramContext, _: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?; let stored_header = common::read_exact(&mut file, 1584)?; //max header size @@ -184,8 +191,8 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "epk2b", detect_func: is_epk2b_file, run_func: extract_epk2b } +} + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; @@ -51,17 +56,18 @@ struct Pak { size : u32, } -pub fn is_epk2b_file(file: &File) -> bool { - let epk2_magic = common::read_file(&file, 12, 4).expect("Failed to read from file."); - let epak_magic = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_epk2b_file(app_ctx: &ProgramContext) -> Result>, Box> { + let epk2_magic = common::read_file(app_ctx.file, 12, 4)?; + let epak_magic = common::read_file(app_ctx.file, 0, 4)?; if epak_magic == b"epak" && epk2_magic == b"EPK2" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_epk2b(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: EpkHeader = file.read_le()?; println!("EPK info -\nData size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}", @@ -108,8 +114,8 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box> { +pub fn extract_epk3(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; file.seek(SeekFrom::Start(0))?; let stored_header = common::read_exact(&mut file, 1712)?; let header: Vec; @@ -153,8 +157,8 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "funai_upg", detect_func: is_funai_upg_file, run_func: extract_funai_upg } +} + use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write}; @@ -20,16 +25,17 @@ struct Entry { _unk: u16, } -pub fn is_funai_upg_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); +pub fn is_funai_upg_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 6)?; if header == b"UPG\x00\x00\x00" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_funai_upg(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_funai_upg(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count); @@ -45,9 +51,9 @@ pub fn extract_funai_upg(mut file: &File, output_folder: &str) -> Result<(), Box println!("Descriptor entry info:\n{}", entry_string); } - let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", i + 1, entry.entry_type)); + let output_path = Path::new(app_ctx.output_dir).join(format!("{}_{}.bin", i + 1, entry.entry_type)); - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; out_file.write_all(&data)?; diff --git a/src/formats/invincible_image.rs b/src/formats/invincible_image.rs index 98f39aa..9534962 100644 --- a/src/formats/invincible_image.rs +++ b/src/formats/invincible_image.rs @@ -1,5 +1,11 @@ +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "invincible_image", detect_func: is_invincible_image_file, run_func: extract_invincible_image } +} + use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Seek, SeekFrom, Cursor}; use binrw::{BinRead, BinReaderExt}; @@ -56,16 +62,17 @@ impl Entry { } } -pub fn is_invincible_image_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 16).expect("Failed to read from file."); +pub fn is_invincible_image_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 16)?; if header == b"INVINCIBLE_IMAGE" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_invincible_image(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; println!("File info:\nFile Version: {}.{}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nData start offset: {}\nKeep data size: {}\nSkip data size: {}\n\nPayload Count: {}", @@ -113,9 +120,9 @@ pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result< println!("\n({}/{}) - {}, Size: {}", i, header.payload_count, entry.name(), entry.size); let data = common::read_exact(&mut data_reader, entry.size as usize)?; - let output_path = Path::new(&output_folder).join(entry.name() + ".bin"); + let output_path = Path::new(app_ctx.output_dir).join(entry.name() + ".bin"); - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new() .write(true) .create(true) diff --git a/src/formats/msd10.rs b/src/formats/msd10.rs index 0bd00f0..7c7fefa 100644 --- a/src/formats/msd10.rs +++ b/src/formats/msd10.rs @@ -1,4 +1,10 @@ -use std::fs::{self, File, OpenOptions}; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "msd10", detect_func: is_msd10_file, run_func: extract_msd10 } +} + +use std::fs::{self, OpenOptions}; use std::path::{Path}; use std::io::{Write, Seek, SeekFrom}; use binrw::{BinRead, BinReaderExt}; @@ -41,16 +47,17 @@ struct Section { size: u32, } -pub fn is_msd10_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); +pub fn is_msd10_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 6)?; if header == b"MSDU10" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_msd10(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); @@ -131,8 +138,8 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Format { + Format { name: "msd11", detect_func: is_msd11_file, run_func: extract_msd11 } +} + +use std::fs::{self, OpenOptions}; use std::path::{Path}; use std::io::{Write}; use binrw::{BinRead, BinReaderExt}; @@ -42,16 +48,17 @@ struct Section { size: u64, } -pub fn is_msd11_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); +pub fn is_msd11_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 6)?; if header == b"MSDU11" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_msd11(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); @@ -126,8 +133,8 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "mstar", detect_func: is_mstar_file, run_func: extract_mstar } +} + +use std::fs::{self, OpenOptions}; use std::path::{Path}; use std::io::{Write}; @@ -7,19 +13,14 @@ use crate::utils::compression::{decompress_lzma, decompress_lz4}; use crate::utils::lzop::{unlzop_to_file}; use crate::utils::sparse::{unsparse_to_file}; -//change whether the "userdata" partition is skipped -// this is because the userdata partition is sometimes enourmous sizes like 27gb, and it is empty anyways -// if you want to extract "userdata" you can disable the option -static CONFIG_SKIP_USERDATA: bool = true; - -pub fn is_mstar_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 32768).expect("Failed to read from file."); +pub fn is_mstar_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 32768)?; let header_string = String::from_utf8_lossy(&header); if header_string.contains("filepartload"){ - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } @@ -31,7 +32,8 @@ fn parse_number(s: &str) -> Option { } } -pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_mstar(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let file = app_ctx.file; let mut script = common::read_file(&file, 0, 32768)?; @@ -132,12 +134,10 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Format { + Format { name: "mtk_bdp", detect_func: is_mtk_bdp_file, run_func: extract_mtk_bdp } +} + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Seek, SeekFrom, Read, Write}; @@ -72,7 +77,8 @@ fn find_bytes(data: &[u8], pattern: &[u8]) -> Option { data.windows(pattern.len()).position(|window| window == pattern) } -pub fn is_mtk_bdp_file(mut file: &File) -> Result, Box> { +pub fn is_mtk_bdp_file(app_ctx: &ProgramContext) -> Result>, Box> { + let mut file = app_ctx.file; let file_size = file.metadata()?.len(); let mut data = Vec::new(); @@ -83,14 +89,16 @@ pub fn is_mtk_bdp_file(mut file: &File) -> Result, Box Result<(), Box> { - let offset = context.pitit_offset; +pub fn extract_mtk_bdp(app_ctx: &ProgramContext, ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; + let ctx = ctx.and_then(|c: Box| c.downcast::().ok()).ok_or("Context is invalid or missing!")?; + let offset = ctx.pitit_offset; println!("\nReading PITIT at: {}", offset); file.seek(SeekFrom::Start(offset + 8))?; @@ -173,8 +181,8 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont let data = common::read_file(&file, bit_entry.offset as u64, bit_entry.size as usize)?; - let output_path = Path::new(&output_folder).join(format!("{}.bin", name)); - fs::create_dir_all(&output_folder)?; + let output_path = Path::new(app_ctx.output_dir).join(format!("{}.bin", name)); + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?; out_file.seek(SeekFrom::Start(bit_entry.offset_in_target_part as u64))?; out_file.write_all(&data)?; diff --git a/src/formats/mtk_pkg.rs b/src/formats/mtk_pkg.rs index 3a30877..abd35ca 100644 --- a/src/formats/mtk_pkg.rs +++ b/src/formats/mtk_pkg.rs @@ -1,5 +1,11 @@ +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "mtk_pkg", detect_func: is_mtk_pkg_file, run_func: extract_mtk_pkg } +} + use std::path::Path; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Write, Cursor, Seek, SeekFrom}; use binrw::{BinRead, BinReaderExt}; @@ -75,17 +81,17 @@ static HEADER_KEY: [u8; 16] = [ static HEADER_IV: [u8; 16] = [0x00; 16]; -pub fn is_mtk_pkg_file(file: &File) -> Result, Box> { - let mut encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?; +pub fn is_mtk_pkg_file(app_ctx: &ProgramContext) -> Result>, Box> { + let mut encrypted_header = common::read_file(app_ctx.file, 0, HEADER_SIZE)?; let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?; if &header[4..12] == MTK_HEADER_MAGIC { - Ok(Some(MtkPkgContext { is_philips_variant: false, decrypted_header: header})) + Ok(Some(Box::new(MtkPkgContext { is_philips_variant: false, decrypted_header: header}))) } else { // try for philips which has additional 128 bytes at beginning - encrypted_header = common::read_file(&file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?; + encrypted_header = common::read_file(app_ctx.file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?; header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?; if &header[4..12] == MTK_HEADER_MAGIC { - Ok(Some(MtkPkgContext { is_philips_variant: true, decrypted_header: header })) + Ok(Some(Box::new(MtkPkgContext { is_philips_variant: true, decrypted_header: header }))) } else { Ok(None) @@ -93,16 +99,19 @@ pub fn is_mtk_pkg_file(file: &File) -> Result, Box Result<(), Box> { +pub fn extract_mtk_pkg(app_ctx: &ProgramContext, ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; + let ctx = ctx.and_then(|c: Box| c.downcast::().ok()).ok_or("Context is invalid or missing!")?; + let file_size = file.metadata()?.len(); - let header = context.decrypted_header; + let header = ctx.decrypted_header; let mut hdr_reader = Cursor::new(header); let hdr: Header = hdr_reader.read_le()?; println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" , hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name()); - if context.is_philips_variant { + if ctx.is_philips_variant { file.seek(SeekFrom::Start(HEADER_SIZE as u64 + PHILIPS_EXTRA_HEADER_SIZE as u64))?; } else { file.seek(SeekFrom::Start(HEADER_SIZE as u64))?; @@ -189,13 +198,13 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgCont }; //for compressed part create temp file - let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); - fs::create_dir_all(&output_folder)?; + let output_path = Path::new(app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?; out_file.write_all(&out_data[CRYPTED_HEADER_SIZE + extra_header_len as usize..])?; if part_entry.is_compressed() { - let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); + 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!"); diff --git a/src/formats/mtk_pkg_new.rs b/src/formats/mtk_pkg_new.rs index 3ba5d5f..0cce9e1 100644 --- a/src/formats/mtk_pkg_new.rs +++ b/src/formats/mtk_pkg_new.rs @@ -1,5 +1,11 @@ +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "mtk_pkg_new", detect_func: is_mtk_pkg_new_file, run_func: extract_mtk_pkg_new } +} + use std::path::Path; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Write, Cursor, Seek, SeekFrom}; use binrw::{BinRead, BinReaderExt}; @@ -64,34 +70,36 @@ impl PartEntry { static HEADER_SIZE: usize = 0x170; -pub fn is_mtk_pkg_new_file(file: &File) -> Result, Box> { - let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?; +pub fn is_mtk_pkg_new_file(app_ctx: &ProgramContext) -> Result>, Box> { + let encrypted_header = common::read_file(app_ctx.file, 0, HEADER_SIZE)?; for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST { let key_array: [u8; 16] = hex::decode(key_hex)?.as_slice().try_into()?; let iv_array: [u8; 16] = hex::decode(iv_hex)?.as_slice().try_into()?; let try_decrypt = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array)?; if &try_decrypt[4..12] == MTK_HEADER_MAGIC { - return Ok(Some(MtkPkgNewContext { + return Ok(Some(Box::new(MtkPkgNewContext { matching_key_name: name.to_string(), matching_key_key: key_array, matching_key_iv: iv_array, decrypted_header: try_decrypt - })); + }))); } } Ok(None) } -pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkgNewContext) -> Result<(), Box> { +pub fn extract_mtk_pkg_new(app_ctx: &ProgramContext, ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; + let ctx = ctx.and_then(|c: Box| c.downcast::().ok()).ok_or("Context is invalid or missing!")?; let file_size = file.metadata()?.len(); //the key was founf, and header was decrypted at detection stage so we can reuse - println!("Using key {}", context.matching_key_name); - let key_array = context.matching_key_key; - let iv_array = context.matching_key_iv; - let header = context.decrypted_header; + println!("Using key {}", ctx.matching_key_name); + let key_array = ctx.matching_key_key; + let iv_array = ctx.matching_key_iv; + let header = ctx.decrypted_header; let mut hdr_reader = Cursor::new(header); let hdr: Header = hdr_reader.read_le()?; @@ -147,13 +155,13 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkg }; //for compressed part create temp file - let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); - fs::create_dir_all(&output_folder)?; + let output_path = Path::new(app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?; out_file.write_all(&out_data[48 + extra_header_len as usize..])?; if part_entry.is_compressed() { - let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); + 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!"); diff --git a/src/formats/mtk_pkg_old.rs b/src/formats/mtk_pkg_old.rs index a03231f..9af4b11 100644 --- a/src/formats/mtk_pkg_old.rs +++ b/src/formats/mtk_pkg_old.rs @@ -1,5 +1,11 @@ +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "mtk_pkg_old", detect_func: is_mtk_pkg_old_file, run_func: extract_mtk_pkg_old } +} + use std::path::Path; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Write, Cursor, Seek}; use binrw::{BinRead, BinReaderExt}; @@ -57,25 +63,27 @@ impl PartEntry { static HEADER_SIZE: usize = 0x98; -pub fn is_mtk_pkg_old_file(mut file: &File) -> bool { - let encrypted_header = common::read_file(&file, 0, HEADER_SIZE).expect("Failed to read from file."); +pub fn is_mtk_pkg_old_file(app_ctx: &ProgramContext) -> Result>, Box> { + let mut file = app_ctx.file; + let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?; let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK)); if &header[4..12] == MTK_HEADER_MAGIC { - true + Ok(Some(Box::new(()))) } else if &header[68..76] == MTK_HEADER_MAGIC { //check for 64 byte additional header used in some Sony and Philips firmwares and skip it - file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek"); - true + file.seek(std::io::SeekFrom::Start(64))?; + Ok(Some(Box::new(()))) } else if &header[132..140] == MTK_HEADER_MAGIC { //check for 128 byte additional header used in some Philips firmwares and skip it - file.seek(std::io::SeekFrom::Start(128)).expect("Failed to seek"); - true + file.seek(std::io::SeekFrom::Start(128))?; + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_mtk_pkg_old(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let file_size = file.metadata()?.len(); let encrypted_header = common::read_exact(&mut file, HEADER_SIZE)?; let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK)); @@ -118,13 +126,13 @@ pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), B }; //for compressed part create temp file - let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); - fs::create_dir_all(&output_folder)?; + let output_path = Path::new(app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"}); + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?; out_file.write_all(&out_data[extra_header_len as usize..])?; if part_entry.is_compressed() { - let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin"); + let lzhs_out_path = Path::new(app_ctx.output_dir).join(part_entry.name() + ".bin"); match decompress_lzhs_fs_file2file_old(&out_file, lzhs_out_path) { Ok(()) => { println!("- Decompressed Successfully!"); diff --git a/src/formats/novatek.rs b/src/formats/novatek.rs index d99c029..d1c721c 100644 --- a/src/formats/novatek.rs +++ b/src/formats/novatek.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "novatek", detect_func: is_novatek_file, run_func: extract_novatek } +} + use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write}; @@ -35,16 +40,17 @@ struct PartEntry { #[br(count = 16)] _md5_checksum: Vec, } -pub fn is_novatek_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_novatek_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"NFWB" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_novatek(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; println!("File info:\nFirmware name: {}\nVersion: {}.{}\nData size: {}\nPart count: {}", @@ -63,9 +69,9 @@ pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "nvt_timg", detect_func: is_nvt_timg_file, run_func: extract_nvt_timg } +} + use std::path::{Path}; use std::io::{Seek, Write}; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use binrw::{BinRead, BinReaderExt}; use crate::utils::common; @@ -44,16 +49,17 @@ impl PIMG { } } -pub fn is_nvt_timg_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_nvt_timg_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"TIMG" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_nvt_timg(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_nvt_timg(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let file_size = file.metadata()?.len(); let timg: TIMG = file.read_le()?; println!("File info:\nData size: {}", timg.data_size); @@ -72,7 +78,7 @@ pub fn extract_nvt_timg(mut file: &File, output_folder: &str) -> Result<(), Box< println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}", pimg_i, pimg.name(), pimg.size, pimg.dest_dev(), pimg.comp_type()); let out_data; - let output_path = Path::new(&output_folder).join(pimg.name() + ".bin"); + let output_path = Path::new(app_ctx.output_dir).join(pimg.name() + ".bin"); if pimg.comp_type() == "gzip" && data.starts_with(b"\x1F\x8B") { //additionally check for gzip header, because sometimes its deceptive println!("- Decompressing gzip..."); @@ -89,7 +95,7 @@ pub fn extract_nvt_timg(mut file: &File, output_folder: &str) -> Result<(), Box< out_data = data; } - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new() .write(true) .create(true) diff --git a/src/formats/pana_dvd.rs b/src/formats/pana_dvd.rs index 8985ba4..3f23230 100644 --- a/src/formats/pana_dvd.rs +++ b/src/formats/pana_dvd.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "pana_dvd", detect_func: is_pana_dvd_file, run_func: extract_pana_dvd } +} + use std::path::{Path, PathBuf}; use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Cursor, Seek, SeekFrom}; @@ -144,38 +149,41 @@ pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: Ok(None) } -pub fn is_pana_dvd_file(file: &File) -> Result, Box> { - let header = common::read_file(&file, 0, 64)?; +pub fn is_pana_dvd_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 64)?; if let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0)? { - Ok(Some(PanaDvdContext { + Ok(Some(Box::new(PanaDvdContext { matching_key: matching_key, base_hdr_size: 0, is_aes: false, aes_key: None, aes_iv: None, - })) + }))) } else if header.starts_with(b"PANASONIC\x00\x00\x00") && let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 48)? { - Ok(Some(PanaDvdContext { + Ok(Some(Box::new(PanaDvdContext { matching_key: matching_key, base_hdr_size: 48, is_aes: false, aes_key: None, aes_iv: None, - })) + }))) } else if let Some((aes_key, aes_iv, matching_key)) = find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32)? { - Ok(Some(PanaDvdContext { + Ok(Some(Box::new(PanaDvdContext { matching_key: matching_key, base_hdr_size: 48, is_aes: true, aes_key: Some(aes_key), aes_iv: Some(aes_iv), - })) + }))) } else { Ok(None) } } -pub fn extract_pana_dvd(mut file: &File, output_folder: &str, context: PanaDvdContext) -> Result<(), Box> { +pub fn extract_pana_dvd(app_ctx: &ProgramContext, ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; + let context = ctx.and_then(|c: Box| c.downcast::().ok()).ok_or("Context is invalid or missing!")?; + let mut data = Vec::new(); // we need to load the entire file into memory so we can swap it with AES decrypted data if its AES encrypted file.read_to_end(&mut data)?; let mut file_reader = Cursor::new(data); @@ -209,12 +217,12 @@ pub fn extract_pana_dvd(mut file: &File, output_folder: &str, context: PanaDvdCo if file_entries.len() == 1 { //only one file, standard extraction println!("File contains no extra sub-files...\n"); - extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, output_folder)?; + extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, app_ctx.output_dir)?; } else { println!("File contains {} sub-files...", file_entries.len()); for (i, file_entry ) in file_entries.iter().enumerate() { println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset); - extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &format!("{}/file_{}", output_folder, i + 1))?; + extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &format!("{}/file_{}", app_ctx.output_dir, i + 1))?; } } diff --git a/src/formats/pfl_upg.rs b/src/formats/pfl_upg.rs index 2b5b886..ad1f85e 100644 --- a/src/formats/pfl_upg.rs +++ b/src/formats/pfl_upg.rs @@ -1,4 +1,9 @@ -use std::fs::{File}; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "pfl_upg", detect_func: is_pfl_upg_file, run_func: extract_pfl_upg } +} + use rsa::{RsaPublicKey, BigUint}; use hex::decode; use std::path::Path; @@ -43,12 +48,12 @@ impl FileHeader { } } -pub fn is_pfl_upg_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 8).expect("Failed to read from file."); +pub fn is_pfl_upg_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 8)?; if header == b"2SWU3TXV" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } @@ -84,7 +89,8 @@ fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result, Box Result<(), Box> { +pub fn extract_pfl_upg(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; let signature = common::read_exact(&mut file, 128)?; let _ = common::read_exact(&mut file, 32)?; //unknown @@ -157,7 +163,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Format { + Format { name: "pup", detect_func: is_pup_file, run_func: extract_pup } +} + use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use binrw::{BinRead, BinReaderExt}; use std::io::{Write, Seek, SeekFrom}; @@ -50,16 +56,17 @@ struct BlockEntry { size: u32, } -pub fn is_pup_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_pup_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"\x4F\x15\x3D\x1D" || header == b"\x54\x14\xF5\xEE" { //ps4, ps5 - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_pup(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; println!("File info:\nFile size: {}\nEntry count: {}", @@ -132,9 +139,9 @@ pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Format { + Format { name: "roku", detect_func: is_roku_file, run_func: extract_roku } +} + +use std::fs::{self, OpenOptions}; use std::path::Path; use std::io::{Write, Seek, Read, Cursor}; use tar::Archive; @@ -55,18 +61,19 @@ impl ImageHeader { } } -pub fn is_roku_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 32).expect("Failed to read from file."); - let try_decrypt_header = decrypt_aes128_cbc_nopad(&header, &FILE_KEY, &FILE_IV).expect("Decryption error!"); +pub fn is_roku_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 32)?; + let try_decrypt_header = decrypt_aes128_cbc_nopad(&header, &FILE_KEY, &FILE_IV)?; if try_decrypt_header.starts_with(b"manifest\x00\x00\x00\x00\x00\x00\x00\x00") { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_roku(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let mut encrypted_data = Vec::new(); file.read_to_end(&mut encrypted_data)?; @@ -109,7 +116,7 @@ pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Format { + Format { name: "ruf", detect_func: is_ruf_file, run_func: extract_ruf } +} + use std::path::{Path}; use std::fs::{self, File, OpenOptions}; use binrw::{BinRead, BinReaderExt}; @@ -69,24 +75,25 @@ impl RufEntry { } } -pub fn is_ruf_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 3).expect("Failed to read from file."); - if header == b"RUF" { - true +pub fn is_ruf_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 6)?; + if header == b"RUF\x00\x00\x00" { + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_ruf(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_ruf(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: RufHeader = file.read_be()?; if header.is_dual_ruf() { println!("\nDual RUF detected! Extracting 1st RUF...\n"); - actually_extract_ruf(&file, &format!("{}/RUF_1", output_folder), 0)?; + actually_extract_ruf(file, &format!("{}/RUF_1", app_ctx.output_dir), 0)?; println!("\nExtracting 2nd RUF...\n"); - actually_extract_ruf(&file, &format!("{}/RUF_2", output_folder), 41943088)?; + actually_extract_ruf(file, &format!("{}/RUF_2", app_ctx.output_dir), 41943088)?; } else { - actually_extract_ruf(&file, &output_folder, 0)?; + actually_extract_ruf(file, app_ctx.output_dir, 0)?; } println!("\nExtraction finished!"); diff --git a/src/formats/rvp.rs b/src/formats/rvp.rs index 9400fb7..f91abe0 100644 --- a/src/formats/rvp.rs +++ b/src/formats/rvp.rs @@ -1,4 +1,9 @@ -use std::fs::File; +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "rvp", detect_func: is_rvp_file, run_func: extract_rvp } +} + use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Cursor, Seek}; @@ -13,26 +18,29 @@ fn decrypt_xor(data: &[u8]) -> Vec { .collect() } -pub fn is_rvp_file(mut file: &File) -> bool { +pub fn is_rvp_file(app_ctx: &ProgramContext) -> Result>, Box> { + let mut file = app_ctx.file; //MVP - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); + let header = common::read_file(file, 0, 4)?; if header == b"UPDT" { - file.seek(std::io::SeekFrom::Start(36)).expect("Failed to seek"); //skip rest of header - return true; + file.seek(std::io::SeekFrom::Start(36))?; //skip rest of header // NOT GOOD PRACTICE SHOULD BE REMOVED + return Ok(Some(Box::new(()))) } //RVP - let bytes = common::read_file(&file, 16, 18).expect("Failed to read from file."); + let bytes = common::read_file(file, 16, 18)?; for (_i, &b) in bytes.iter().enumerate().step_by(2) { if b != 0xA3 { - return false; + return Ok(None); } } - file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek"); //skip rest of header - true + + file.seek(std::io::SeekFrom::Start(64))?; //skip rest of header // NOT GOOD PRACTICE SHOULD BE REMOVED + Ok(Some(Box::new(()))) } -pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_rvp(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let mut obf_data = Vec::new(); //we sadly cannot deXOR on the fly because of its 32 byte pattern file.read_to_end(&mut obf_data)?; println!("DeXORing data.."); @@ -114,9 +122,9 @@ pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "sddl_sec", detect_func: is_sddl_sec_file, run_func: extract_sddl_sec } +} use std::path::{Path, PathBuf}; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Cursor, Seek, SeekFrom, Write}; use binrw::{BinRead, BinReaderExt}; @@ -98,13 +103,13 @@ static DEC_IV: [u8; 16] = [ 0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66, ]; -pub fn is_sddl_sec_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 32).expect("Failed to read from file."); +pub fn is_sddl_sec_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 32)?; let deciph_header = decipher(&header); if deciph_header.starts_with(b"\x11\x22\x33\x44") { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } @@ -150,7 +155,8 @@ fn decipher(s: &[u8]) -> Vec { out } -pub fn extract_sddl_sec(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_sddl_sec(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let mut hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?)); let hdr: SddlSecHeader = hdr_reader.read_be()?; @@ -168,10 +174,10 @@ pub fn extract_sddl_sec(mut file: &File, output_folder: &str) -> Result<(), Box< let data = common::read_exact(&mut file, entry_header.size() as usize)?; let dec_data = decrypt_aes128_cbc_pcks7(&data, &DEC_KEY, &DEC_IV)?; - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(app_ctx.output_dir)?; //detect the file type based on the counts of each file if i == 0 { //SDIT.FDI file - let output_path = Path::new(&output_folder).join(entry_header.name()); + let output_path = Path::new(app_ctx.output_dir).join(entry_header.name()); let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; out_file.write_all(&dec_data)?; println!("-- Saved file!"); @@ -210,11 +216,11 @@ pub fn extract_sddl_sec(mut file: &File, output_folder: &str) -> Result<(), Box< let file_name = common::string_from_bytes(&file_name_bytes); println!("--- File name: {}", file_name); - let out_folder_path = Path::new(&output_folder).join(source_name); + let out_folder_path = Path::new(app_ctx.output_dir).join(source_name); fs::create_dir_all(&out_folder_path)?; output_path = Path::new(&out_folder_path).join(file_name); } else { - output_path = Path::new(&output_folder).join(format!("{}.bin", source_name)); + output_path = Path::new(app_ctx.output_dir).join(format!("{}.bin", source_name)); } let data = common::read_exact(&mut content_reader, content_header.size as usize)?; diff --git a/src/formats/slp.rs b/src/formats/slp.rs index 9308f10..de1aff5 100644 --- a/src/formats/slp.rs +++ b/src/formats/slp.rs @@ -1,5 +1,11 @@ +use std::any::Any; +use crate::{ProgramContext, formats::Format}; +pub fn format() -> Format { + Format { name: "slp", detect_func: is_slp_file, run_func: extract_slp } +} + use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; +use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; use binrw::{BinRead, BinReaderExt}; @@ -46,16 +52,17 @@ struct EntryNew { #[br(count = 12)] _unk2: Vec, } -pub fn is_slp_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_slp_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"SLP\x00" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_slp(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_slp(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let header: Header = file.read_le()?; println!("File info:\nModel: {}\nVersion: {}\nFirmware: {}\nNew type: {}\n", @@ -92,9 +99,9 @@ pub fn extract_slp(mut file: &File, output_folder: &str) -> Result<(), Box Format { + Format { name: "sony_bdp", detect_func: is_sony_bdp_file, run_func: extract_sony_bdp } +} + use std::fs::File; use std::path::{Path, PathBuf}; use std::fs::{self, OpenOptions}; @@ -57,16 +63,17 @@ struct Entry { size: u32, } -pub fn is_sony_bdp_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); +pub fn is_sony_bdp_file(app_ctx: &ProgramContext) -> Result>, Box> { + let header = common::read_file(app_ctx.file, 0, 4)?; if header == b"\x01\x73\xEC\xC9" || header == b"\x01\x73\xEC\x1F" || header == b"\xEC\x7D\xB0\xB0" { //MSB1x, MSB0x, BDPPxx - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_sony_bdp(app_ctx: &ProgramContext, _ctx: Option>) -> Result<(), Box> { + let mut file = app_ctx.file; let obf_header = common::read_exact(&mut file, 300)?; let header = hex_substitute(&obf_header); let mut hdr_reader = Cursor::new(header); @@ -96,10 +103,10 @@ pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box< let obf_data = common::read_file(&file, entry.offset as u64, entry.size as usize)?; let data = hex_substitute(&obf_data); - let output_path = Path::new(&output_folder).join(format!("{}.bin", i)); + let output_path = Path::new(app_ctx.output_dir).join(format!("{}.bin", i)); last_file_path = Some(output_path.clone()); - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(app_ctx.output_dir)?; let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; out_file.write_all(&data)?; @@ -110,11 +117,15 @@ pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box< //The last file is often a Mtk BDP file so we can extract that here. if last_file_path.is_some() { println!("\nChecking if it's also MTK BDP..."); + let last_file = File::open(last_file_path.unwrap())?; - if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&last_file)? { + let mtk_extraction_path = format!("{}/{}", app_ctx.output_dir, i - 1); + let ctx: ProgramContext = ProgramContext { file: &last_file, output_dir: &mtk_extraction_path }; + + if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&ctx)? { println!("- MTK BDP file detected!\n"); - let mtk_extraction_path = format!("{}/{}", &output_folder, i - 1); - formats::mtk_bdp::extract_mtk_bdp(&last_file, &mtk_extraction_path, result)?; + + formats::mtk_bdp::extract_mtk_bdp(&ctx, Some(result))?; } else { println!("- Not an MTK BDP file."); } diff --git a/src/main.rs b/src/main.rs index 5bf66f2..b7cb468 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::Parser; use std::path::{PathBuf}; use std::io::{self}; use std::fs::{self, File}; +use crate::formats::{Format, get_registry}; #[derive(Parser, Debug)] struct Args { @@ -13,6 +14,11 @@ struct Args { output_folder: Option, } +pub struct ProgramContext<'a> { + pub file: &'a std::fs::File, + pub output_dir: &'a str, +} + fn main() -> Result<(), Box> { println!("unixtract Firmware extractor"); let args = Args::parse(); @@ -39,130 +45,19 @@ fn main() -> Result<(), Box> { } } } - - if path.is_dir() { - if formats::samsung_old::is_samsung_old_dir(&path) { - println!("Samsung old firmware dir detected!\n"); - formats::samsung_old::extract_samsung_old(&path, &output_path)? - } else { - println!("Input format not recognized!"); - } - } else { - let file = File::open(path)?; - if formats::sddl_sec::is_sddl_sec_file(&file) { - println!("SDDL.SEC file detected!"); - formats::sddl_sec::extract_sddl_sec(&file, &output_path)?; - } - else if formats::invincible_image::is_invincible_image_file(&file) { - println!("INVINCIBLE_IMAGE file detected!"); - formats::invincible_image::extract_invincible_image(&file, &output_path)?; - } - else if formats::msd10::is_msd10_file(&file) { - println!("MSD10 file detected!"); - formats::msd10::extract_msd10(&file, &output_path)?; - } - else if formats::msd11::is_msd11_file(&file) { - println!("MSD11 file detected!"); - formats::msd11::extract_msd11(&file, &output_path)?; - } - else if formats::nvt_timg::is_nvt_timg_file(&file) { - println!("Novatek TIMG file detected!"); - formats::nvt_timg::extract_nvt_timg(&file, &output_path)?; - } - else if formats::bdl::is_bdl_file(&file) { - println!("BDL file detected!"); - formats::bdl::extract_bdl(&file, &output_path)?; - } - else if formats::android_ota_payload::is_android_ota_payload_file(&file) { - println!("Android OTA payload file detected!"); - formats::android_ota_payload::extract_android_ota_payload(&file, &output_path)?; - } - else if formats::novatek::is_novatek_file(&file) { - println!("Novatek file detected!"); - formats::novatek::extract_novatek(&file, &output_path)?; - } - else if formats::slp::is_slp_file(&file) { - println!("SLP file detected!"); - formats::slp::extract_slp(&file, &output_path)?; - } - else if formats::epk1::is_epk1_file(&file) { - println!("EPK1 file detected!"); - formats::epk1::extract_epk1(&file, &output_path)?; - } - else if formats::epk2b::is_epk2b_file(&file) { - println!("EPK2B file detected!"); - formats::epk2b::extract_epk2b(&file, &output_path)?; - } - //epk with encrypted header - it can be epk2 or epk3 so we need to check - else if formats::epk::is_epk_file(&file) { - println!("EPK file detected!"); - formats::epk::extract_epk(&file, &output_path)?; - } - //epk2 with unencrypted header - else if formats::epk2::is_epk2_file(&file) { - println!("EPK2 file detected!"); - formats::epk2::extract_epk2(&file, &output_path)?; - } - else if formats::ruf::is_ruf_file(&file) { - println!("RUF file detected!"); - formats::ruf::extract_ruf(&file, &output_path)?; - } - else if formats::funai_upg::is_funai_upg_file(&file) { - println!("Funai UPG file detected!"); - formats::funai_upg::extract_funai_upg(&file, &output_path)?; - } - else if formats::pfl_upg::is_pfl_upg_file(&file) { - println!("PFL UPG file detected!"); - formats::pfl_upg::extract_pfl_upg(&file, &output_path)?; - } - else if formats::amlogic::is_amlogic_file(&file) { - println!("Amlogic image file detected!"); - formats::amlogic::extract_amlogic(&file, &output_path)?; - } - else if let Some(result) = formats::pana_dvd::is_pana_dvd_file(&file)? { - println!("PANA_DVD file detected!"); - formats::pana_dvd::extract_pana_dvd(&file, &output_path, result)?; - } - else if formats::pup::is_pup_file(&file) { - println!("PUP file detected!"); - formats::pup::extract_pup(&file, &output_path)?; - } - else if formats::sony_bdp::is_sony_bdp_file(&file) { - println!("Sony BDP file detected!"); - formats::sony_bdp::extract_sony_bdp(&file, &output_path)?; - } - else if formats::rvp::is_rvp_file(&file) { - println!("RVP/MVP file detected!"); - formats::rvp::extract_rvp(&file, &output_path)?; - } - else if formats::mstar::is_mstar_file(&file) { - println!("Mstar upgrade file detected!"); - formats::mstar::extract_mstar(&file, &output_path)?; - } - else if formats::roku::is_roku_file(&file) { - println!("Roku file detected!"); - formats::roku::extract_roku(&file, &output_path)?; - } - else if let Some(result) = formats::mtk_pkg::is_mtk_pkg_file(&file)? { - println!("MTK PKG file detected!"); - formats::mtk_pkg::extract_mtk_pkg(&file, &output_path, result)?; - } - else if formats::mtk_pkg_old::is_mtk_pkg_old_file(&file) { - println!("MTK PKG (Old) file detected!"); - formats::mtk_pkg_old::extract_mtk_pkg_old(&file, &output_path)?; - } - else if let Some(result) = formats::mtk_pkg_new::is_mtk_pkg_new_file(&file)? { - println!("MTK PKG (New) file detected!"); - formats::mtk_pkg_new::extract_mtk_pkg_new(&file, &output_path, result)?; - } - else if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&file)? { - println!("MTK BDP file detected!"); - formats::mtk_bdp::extract_mtk_bdp(&file, &output_path, result)?; - } - else { - println!("Input format not recognized!"); + + let file = File::open(path)?; + let program_context: ProgramContext = ProgramContext { file: &file, output_dir: &output_path }; + let formats: Vec = get_registry(); + + for format in formats { + if let Some(ctx) = (format.detect_func)(&program_context)? { + println!("{} detected!", format.name); + (format.run_func)(&program_context, Some(ctx))?; + return Ok(()); } } + println!("\nInput format not recognized!"); Ok(()) }