diff --git a/src/formats.rs b/src/formats.rs index d0d70d1..5d88d44 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -1,3 +1,12 @@ +use std::any::Any; +use crate::AppContext; + +pub struct Format { + pub name: &'static str, + pub detector_func: fn(&AppContext) -> Result>, Box>, + pub extractor_func: fn(&AppContext, Box) -> Result<(), Box>, +} + pub mod mstar; pub mod samsung_old; pub mod nvt_timg; @@ -31,4 +40,156 @@ 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; + +//define all formats here +pub fn get_registry() -> Vec { + return vec![ + Format { + name: "mstar", + detector_func: crate::formats::mstar::is_mstar_file, + extractor_func: crate::formats::mstar::extract_mstar, + }, + Format { + name: "samsung_old", + detector_func: crate::formats::samsung_old::is_samsung_old_dir, + extractor_func: crate::formats::samsung_old::extract_samsung_old, + }, + Format { + name: "nvt_timg", + detector_func: crate::formats::nvt_timg::is_nvt_timg_file, + extractor_func: crate::formats::nvt_timg::extract_nvt_timg, + }, + Format { + name: "pfl_upg", + detector_func: crate::formats::pfl_upg::is_pfl_upg_file, + extractor_func: crate::formats::pfl_upg::extract_pfl_upg, + }, + Format { + name: "sddl_sec", + detector_func: crate::formats::sddl_sec::is_sddl_sec_file, + extractor_func: crate::formats::sddl_sec::extract_sddl_sec, + }, + Format { + name: "novatek", + detector_func: crate::formats::novatek::is_novatek_file, + extractor_func: crate::formats::novatek::extract_novatek, + }, + Format { + name: "ruf", + detector_func: crate::formats::ruf::is_ruf_file, + extractor_func: crate::formats::ruf::extract_ruf, + }, + Format { + name: "invincible_image", + detector_func: crate::formats::invincible_image::is_invincible_image_file, + extractor_func: crate::formats::invincible_image::extract_invincible_image, + }, + Format { + name: "slp", + detector_func: crate::formats::slp::is_slp_file, + extractor_func: crate::formats::slp::extract_slp, + }, + Format { + name: "roku", + detector_func: crate::formats::roku::is_roku_file, + extractor_func: crate::formats::roku::extract_roku, + }, + Format { + name: "sony_bdp", + detector_func: crate::formats::sony_bdp::is_sony_bdp_file, + extractor_func: crate::formats::sony_bdp::extract_sony_bdp, + }, + Format { + name: "rvp", + detector_func: crate::formats::rvp::is_rvp_file, + extractor_func: crate::formats::rvp::extract_rvp, + }, + Format { + name: "funai_upg", + detector_func: crate::formats::funai_upg::is_funai_upg_file, + extractor_func: crate::formats::funai_upg::extract_funai_upg, + }, + Format { + name: "pana_dvd", + detector_func: crate::formats::pana_dvd::is_pana_dvd_file, + extractor_func: crate::formats::pana_dvd::extract_pana_dvd, + }, + Format { + name: "android_ota_payload", + detector_func: crate::formats::android_ota_payload::is_android_ota_payload_file, + extractor_func: crate::formats::android_ota_payload::extract_android_ota_payload, + }, + Format { + name: "bdl", + detector_func: crate::formats::bdl::is_bdl_file, + extractor_func: crate::formats::bdl::extract_bdl, + }, + Format { + name: "amlogic", + detector_func: crate::formats::amlogic::is_amlogic_file, + extractor_func: crate::formats::amlogic::extract_amlogic, + }, + Format { + name: "pup", + detector_func: crate::formats::pup::is_pup_file, + extractor_func: crate::formats::pup::extract_pup, + }, + Format { + name: "msd10", + detector_func: crate::formats::msd10::is_msd10_file, + extractor_func: crate::formats::msd10::extract_msd10, + }, + Format { + name: "msd11", + detector_func: crate::formats::msd11::is_msd11_file, + extractor_func: crate::formats::msd11::extract_msd11, + }, + Format { + name: "epk", + detector_func: crate::formats::epk::is_epk_file, + extractor_func: crate::formats::epk::extract_epk, + }, + Format { + name: "epk1", + detector_func: crate::formats::epk1::is_epk1_file, + extractor_func: crate::formats::epk1::extract_epk1, + }, + Format { + name: "epk2", + detector_func: crate::formats::epk2::is_epk2_file, + extractor_func: crate::formats::epk2::extract_epk2, + }, + Format { + name: "epk2b", + detector_func: crate::formats::epk2b::is_epk2b_file, + extractor_func: crate::formats::epk2b::extract_epk2b, + }, + Format { + name: "epk3", + detector_func: crate::formats::epk3::is_epk3_file, + extractor_func: crate::formats::epk3::extract_epk3, + }, + Format { + name: "mtk_pkg", + detector_func: crate::formats::mtk_pkg::is_mtk_pkg_file, + extractor_func: crate::formats::mtk_pkg::extract_mtk_pkg, + }, + Format { + name: "mtk_pkg_old", + detector_func: crate::formats::mtk_pkg_old::is_mtk_pkg_old_file, + extractor_func: crate::formats::mtk_pkg_old::extract_mtk_pkg_old, + }, + Format { + name: "mtk_pkg_new", + detector_func: crate::formats::mtk_pkg_new::is_mtk_pkg_new_file, + extractor_func: crate::formats::mtk_pkg_new::extract_mtk_pkg_new, + }, + Format { + name: "mtk_bdp", + detector_func: crate::formats::mtk_bdp::is_mtk_bdp_file, + extractor_func: crate::formats::mtk_bdp::extract_mtk_bdp, + }, + + ] +} \ No newline at end of file diff --git a/src/formats/amlogic/include.rs b/src/formats/amlogic/include.rs new file mode 100644 index 0000000..428115e --- /dev/null +++ b/src/formats/amlogic/include.rs @@ -0,0 +1,39 @@ +use crate::utils::common; +use binrw::{BinRead}; + +#[derive(BinRead)] +pub struct ImageHeader { + _crc32: u32, + pub version: u32, + _magic_bytes: [u8; 4], //56 19 B5 27 + pub image_size: u64, + pub item_align_size: u32, + pub item_count: u32, + _reserved: [u8; 36], +} + +#[derive(BinRead)] +pub struct ItemEntry { + _item_id: u32, + pub file_type: u32, + _current_offset_in_item: u64, + pub offset_in_image: u64, + pub item_size: u64, + item_type_bytes: [u8; 256], + name_bytes: [u8; 256], + _verify: u32, + _is_backup_item: u16, + _backup_item_id: u16, + _reserved: [u8; 24], +} +impl ItemEntry { + pub fn item_type(&self) -> String { + common::string_from_bytes(&self.item_type_bytes) + } + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } + pub fn is_sparse(&self) -> bool { + self.file_type == 254 + } +} \ No newline at end of file diff --git a/src/formats/amlogic.rs b/src/formats/amlogic/mod.rs similarity index 53% rename from src/formats/amlogic.rs rename to src/formats/amlogic/mod.rs index 0f4180d..a704c70 100644 --- a/src/formats/amlogic.rs +++ b/src/formats/amlogic/mod.rs @@ -1,68 +1,36 @@ -use std::fs::File; -use std::path::{Path}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::Path; use std::fs::{self, OpenOptions}; -use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; +use std::io::Write; +use binrw::BinReaderExt; use crate::utils::common; -use crate::utils::sparse::{unsparse_to_file}; +use crate::utils::sparse::unsparse_to_file; +use include::*; -#[derive(BinRead)] -struct ImageHeader { - _crc32: u32, - version: u32, - #[br(count = 4)] _magic_bytes: Vec, //56 19 B5 27 - image_size: u64, - item_align_size: u32, - item_count: u32, - #[br(count = 36)] _reserved: Vec, -} - -#[derive(BinRead)] -struct ItemEntry { - _item_id: u32, - file_type: u32, - _current_offset_in_item: u64, - offset_in_image: u64, - item_size: u64, - #[br(count = 256)] item_type_bytes: Vec, - #[br(count = 256)] name_bytes: Vec, - _verify: u32, - _is_backup_item: u16, - _backup_item_id: u16, - #[br(count = 24)] _reserved: Vec, -} -impl ItemEntry { - fn item_type(&self) -> String { - common::string_from_bytes(&self.item_type_bytes) - } - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn is_sparse(&self) -> bool { - self.file_type == 254 - } -} - -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: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&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> { - file.seek(SeekFrom::Start(0))?; - let header: ImageHeader = file.read_le()?; +pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: ImageHeader = file.read_le()?; println!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}", header.image_size, header.item_align_size, header.item_count, header.version); if header.version != 2 { - println!("\nSorry, this format version is not yet supported!"); - return Ok(()); + return Err("Unsupported format version! (Only 2 is supported right now)".into()); } let mut items: Vec = Vec::new(); @@ -85,8 +53,8 @@ pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box, //CrAU - file_format_version: u64, - manifest_size: u64, - metadata_signature_size: u32, -} +pub fn is_android_ota_payload_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -pub fn is_android_ota_payload_file(file: &File) -> bool { - 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"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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: Header = file.read_be()?; println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size); if header.file_format_version != 2 { - println!("\nSorry, this version of the file is not supported!"); - return Ok(()) + return Err("Unsupported format version! (Only 2 is supported right now)".into()); } let base_offset = 24 /* size of header */ + header.manifest_size + header.metadata_signature_size as u64; @@ -75,15 +76,13 @@ 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)?; } println!("\n-- Saved!"); } - println!("\nExtraction finished!"); - Ok(()) } \ No newline at end of file diff --git a/src/formats/bdl.rs b/src/formats/bdl.rs deleted file mode 100644 index bca2206..0000000 --- a/src/formats/bdl.rs +++ /dev/null @@ -1,142 +0,0 @@ -use std::fs::File; -use std::path::{Path}; -use std::fs::{self, OpenOptions}; -use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; - -#[derive(BinRead)] -struct BdlHeader { - #[br(count = 4)] _magic_bytes: Vec, //ibdl - #[br(count = 8)] _file_version: Vec, - _unk1: u32, - pkg_count: u32, - #[br(count = 12)] _unk2: Vec, - #[br(count = 256)] date_bytes: Vec, - #[br(count = 256)] manufacturer_bytes: Vec, - #[br(count = 256)] model_bytes: Vec, - #[br(count = 9)] _unk3: Vec, - #[br(count = 256)] version_bytes: Vec, - #[br(count = 1280)] info_bytes: Vec, -} -impl BdlHeader { - fn date(&self) -> String { - common::string_from_bytes(&self.date_bytes) - } - fn manufacturer(&self) -> String { - common::string_from_bytes(&self.manufacturer_bytes) - } - fn model(&self) -> String { - common::string_from_bytes(&self.model_bytes) - } - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn info(&self) -> String { - common::string_from_bytes(&self.info_bytes) - } -} - -#[derive(BinRead)] -struct PkgListEntry { - offset: u64, - size: u64, -} - -#[derive(BinRead)] -struct PkgHeader { - #[br(count = 4)] _magic_bytes: Vec, //ipkg - #[br(count = 12)] _unk1: Vec, - entry_count: u32, - #[br(count = 12)] _unk2: Vec, - #[br(count = 256)] version_bytes: Vec, - #[br(count = 256)] manufacturer_bytes: Vec, - #[br(count = 256)] name_bytes: Vec, - #[br(count = 285)] _unk3: Vec, -} -impl PkgHeader { - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn manufacturer(&self) -> String { - common::string_from_bytes(&self.manufacturer_bytes) - } - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -#[derive(BinRead)] -struct PkgEntry { - #[br(count = 256)] name_bytes: Vec, - offset: u64, - size: u64, - _crc32: u32, -} -impl PkgEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -pub fn is_bdl_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); - if header == b"ibdl" { - true - } else { - false - } -} - -pub fn extract_bdl(mut file: &File, output_folder: &str) -> Result<(), Box> { - let header: BdlHeader = file.read_le()?; - - println!("File info:\nPackage count: {}\nDate: {}\nManufacturer: {}\nModel: {}\nVersion: {}\nInfo: {}", - header.pkg_count, header.date(), header.manufacturer(), header.model(), header.version(), header.info()); - - let mut pkgs: Vec = Vec::new(); - - for _i in 0..header.pkg_count { - let pkg_entry: PkgListEntry = file.read_le()?; - //println!("Package {} - Offset: {}, Size: {}", i + 1, pkg_entry.offset, pkg_entry.size); - pkgs.push(pkg_entry); - } - - for (i, pkg) in pkgs.iter().enumerate() { - file.seek(SeekFrom::Start(pkg.offset))?; - let pkg_header: PkgHeader = file.read_le()?; - println!("\nPackage ({}/{}) - Name: {}, Version: {}, Entry Count: {}, Manufacturer: {}, Offset: {}, Size: {}", - i + 1, header.pkg_count, pkg_header.name(), pkg_header.version(), pkg_header.entry_count, pkg_header.manufacturer(), pkg.offset, pkg.size); - - let mut pkg_entries: Vec = Vec::new(); - - for _i in 0..pkg_header.entry_count { - let pkg_entry: PkgEntry = file.read_le()?; - pkg_entries.push(pkg_entry); - } - - let pkg_folder = Path::new(&output_folder).join(pkg_header.name()); - fs::create_dir_all(&pkg_folder)?; - - for (i, pkg_entry) in pkg_entries.iter().enumerate() { - println!("- Entry {}/{} - Name: {}, Offset: {}, Size: {}", - i + 1, pkg_header.entry_count, pkg_entry.name(), pkg_entry.offset, pkg_entry.size); - - let calc_offset = pkg.offset + pkg_entry.offset; - let data = common::read_file(&file, calc_offset, pkg_entry.size as usize)?; - - let output_path = Path::new(&pkg_folder).join(pkg_entry.name()); - let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; - - out_file.write_all(&data)?; - - println!("-- Saved file!"); - - } - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/bdl/include.rs b/src/formats/bdl/include.rs new file mode 100644 index 0000000..3f94337 --- /dev/null +++ b/src/formats/bdl/include.rs @@ -0,0 +1,76 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(BinRead)] +pub struct BdlHeader { + _magic_bytes: [u8; 4], //ibdl + _file_version: [u8; 8], + _unk1: u32, + pub pkg_count: u32, + _unk2: [u8; 12], + date_bytes: [u8; 256], + manufacturer_bytes: [u8; 256], + model_bytes: [u8; 256], + _unk3: [u8; 9], + version_bytes: [u8; 256], + info_bytes: [u8; 1280], +} +impl BdlHeader { + pub fn date(&self) -> String { + common::string_from_bytes(&self.date_bytes) + } + pub fn manufacturer(&self) -> String { + common::string_from_bytes(&self.manufacturer_bytes) + } + pub fn model(&self) -> String { + common::string_from_bytes(&self.model_bytes) + } + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn info(&self) -> String { + common::string_from_bytes(&self.info_bytes) + } +} + +#[derive(BinRead)] +pub struct PkgListEntry { + pub offset: u64, + pub size: u64, +} + +#[derive(BinRead)] +pub struct PkgHeader { + _magic_bytes: [u8; 4], //ipkg + _unk1: [u8; 12], + pub entry_count: u32, + _unk2: [u8; 12], + version_bytes: [u8; 256], + manufacturer_bytes: [u8; 256], + name_bytes: [u8; 256], + _unk3: [u8; 285], +} +impl PkgHeader { + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn manufacturer(&self) -> String { + common::string_from_bytes(&self.manufacturer_bytes) + } + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +#[derive(BinRead)] +pub struct PkgEntry { + name_bytes: [u8; 256], + pub offset: u64, + pub size: u64, + _crc32: u32, +} +impl PkgEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} \ No newline at end of file diff --git a/src/formats/bdl/mod.rs b/src/formats/bdl/mod.rs new file mode 100644 index 0000000..4839106 --- /dev/null +++ b/src/formats/bdl/mod.rs @@ -0,0 +1,74 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::Path; +use std::fs::{self, OpenOptions}; +use std::io::{Write, Seek, SeekFrom}; +use binrw::BinReaderExt; + +use crate::utils::common; +use include::*; + +pub fn is_bdl_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 4)?; + if header == b"ibdl" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let header: BdlHeader = file.read_le()?; + + println!("File info:\nPackage count: {}\nDate: {}\nManufacturer: {}\nModel: {}\nVersion: {}\nInfo: {}", + header.pkg_count, header.date(), header.manufacturer(), header.model(), header.version(), header.info()); + + let mut pkgs: Vec = Vec::new(); + + for _i in 0..header.pkg_count { + let pkg_entry: PkgListEntry = file.read_le()?; + //println!("Package {} - Offset: {}, Size: {}", i + 1, pkg_entry.offset, pkg_entry.size); + pkgs.push(pkg_entry); + } + + for (i, pkg) in pkgs.iter().enumerate() { + file.seek(SeekFrom::Start(pkg.offset))?; + let pkg_header: PkgHeader = file.read_le()?; + println!("\nPackage ({}/{}) - Name: {}, Version: {}, Entry Count: {}, Manufacturer: {}, Offset: {}, Size: {}", + i + 1, header.pkg_count, pkg_header.name(), pkg_header.version(), pkg_header.entry_count, pkg_header.manufacturer(), pkg.offset, pkg.size); + + let mut pkg_entries: Vec = Vec::new(); + + for _i in 0..pkg_header.entry_count { + let pkg_entry: PkgEntry = file.read_le()?; + pkg_entries.push(pkg_entry); + } + + let pkg_folder = Path::new(&app_ctx.output_dir).join(pkg_header.name()); + fs::create_dir_all(&pkg_folder)?; + + for (i, pkg_entry) in pkg_entries.iter().enumerate() { + println!("- Entry {}/{} - Name: {}, Offset: {}, Size: {}", + i + 1, pkg_header.entry_count, pkg_entry.name(), pkg_entry.offset, pkg_entry.size); + + let calc_offset = pkg.offset + pkg_entry.offset; + let data = common::read_file(&file, calc_offset, pkg_entry.size as usize)?; + + let output_path = Path::new(&pkg_folder).join(pkg_entry.name()); + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; + + out_file.write_all(&data)?; + + println!("-- Saved file!"); + + } + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/epk.rs b/src/formats/epk/mod.rs similarity index 73% rename from src/formats/epk.rs rename to src/formats/epk/mod.rs index 45283c1..5643887 100644 --- a/src/formats/epk.rs +++ b/src/formats/epk/mod.rs @@ -1,30 +1,37 @@ -use std::fs::File; +use std::any::Any; +use std::io::Seek; +use crate::AppContext; 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: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let versions = common::read_file(&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 +49,24 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool { true } -pub fn extract_epk(file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_epk(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 versions = common::read_file(&file, 1712, 36)?; - let epk_version = check_epk_version(&versions); 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()) { + file.seek(std::io::SeekFrom::Start(0))?; + + 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, Box::new(()))?; + } else if ctx.epk_version == 3 { println!("EPK3 detected!\n"); - formats::epk3::extract_epk3(file, output_folder)?; + formats::epk3::extract_epk3(app_ctx, Box::new(()))?; } Ok(()) diff --git a/src/formats/epk1/include.rs b/src/formats/epk1/include.rs new file mode 100644 index 0000000..43cad85 --- /dev/null +++ b/src/formats/epk1/include.rs @@ -0,0 +1,31 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(BinRead)] +pub struct CommonHeader { + _magic_bytes: [u8; 4], + pub file_size: u32, + pub pak_count: u32, +} + +#[derive(BinRead)] +pub struct PakHeader { + pak_name_bytes: [u8; 4], + pub image_size: u32, + platform_id_bytes: [u8; 64], + _reserved: [u8; 56], +} +impl PakHeader { + pub fn pak_name(&self) -> String { + common::string_from_bytes(&self.pak_name_bytes) + } + pub fn platform_id(&self) -> String { + common::string_from_bytes(&self.platform_id_bytes) + } +} + +#[derive(BinRead)] +pub struct Pak { + pub offset : u32, + pub size : u32, +} \ No newline at end of file diff --git a/src/formats/epk1.rs b/src/formats/epk1/mod.rs similarity index 64% rename from src/formats/epk1.rs rename to src/formats/epk1/mod.rs index 38ec105..dad0ef9 100644 --- a/src/formats/epk1.rs +++ b/src/formats/epk1/mod.rs @@ -1,51 +1,29 @@ -use std::fs::File; -use std::path::{Path}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; +use include::*; -#[derive(BinRead)] -struct CommonHeader { - #[br(count = 4)] _magic_bytes: Vec, - file_size: u32, - pak_count: u32, -} +pub fn is_epk1_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct PakHeader { - #[br(count = 4)] pak_name_bytes: Vec, - image_size: u32, - #[br(count = 64)] platform_id_bytes: Vec, - #[br(count = 56)] _reserved: Vec, -} -impl PakHeader { - fn pak_name(&self) -> String { - common::string_from_bytes(&self.pak_name_bytes) - } - fn platform_id(&self) -> String { - common::string_from_bytes(&self.platform_id_bytes) - } -} - -#[derive(BinRead)] -struct Pak { - offset : u32, - 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."); + let epk2_magic = common::read_file(&file, 12, 4)?; //for epk2b + let epak_magic = common::read_file(&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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; //check type of epk1 let epk1_type; let init_pak_count_bytes = common::read_file(&file, 8, 4)?; @@ -58,8 +36,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box 128 { + return Err(format!("Unreasonable calculated pak count {}!!", max_pak_count).into()); + } for _i in 0..max_pak_count { let pak: Pak = file.read_le()?; @@ -99,7 +81,10 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box String { + common::string_from_bytes(&self.ota_id_bytes) + } +} + +#[derive(BinRead)] +pub struct PakEntry { + pub offset: u32, + pub size: u32, + name_bytes: [u8; 4], + _version: [u8; 4], + pub segment_size: u32, +} +impl PakEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +#[derive(BinRead)] +pub struct PakHeader { + _pak_name_bytes: [u8; 4], + pub image_size: u32, + platform_id_bytes: [u8; 64], + _sw_version: u32, + _sw_date: u32, + _build_type: u32, + pub segment_count: u32, + pub segment_size: u32, + pub segment_index: u32, + _pak_magic_bytes: [u8; 4], //MPAK + _reserved: [u8; 24], + _segment_crc32: u32, +} +impl PakHeader { + pub fn platform_id(&self) -> String { + common::string_from_bytes(&self.platform_id_bytes) + } +} +pub struct Pak { + pub offset: u32, + pub _size: u32, + pub name: String, +} \ No newline at end of file diff --git a/src/formats/epk2.rs b/src/formats/epk2/mod.rs similarity index 70% rename from src/formats/epk2.rs rename to src/formats/epk2/mod.rs index 7c13bac..ca1148f 100644 --- a/src/formats/epk2.rs +++ b/src/formats/epk2/mod.rs @@ -1,84 +1,32 @@ -use std::fs::{self, File, OpenOptions}; -use std::path::{Path}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; +use std::path::Path; use std::io::{Write, Seek, SeekFrom, Cursor}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; use crate::keys; use crate::formats::epk::{decrypt_aes_ecb_auto, find_key}; +use include::*; -static SIGNATURE_SIZE: u32 = 128; +pub fn is_epk2_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct Header { - #[br(count = 4)] _magic_bytes: Vec, //epak - file_size: u32, - pak_count: u32, - #[br(count = 4)] _epk2_magic: Vec, //EPK2 - #[br(count = 4)] version: Vec, - #[br(count = 32)] ota_id_bytes: Vec, -} -impl Header { - fn ota_id(&self) -> String { - common::string_from_bytes(&self.ota_id_bytes) - } -} - -#[derive(BinRead)] -struct PakEntry { - offset: u32, - size: u32, - #[br(count = 4)] name_bytes: Vec, - #[br(count = 4)] _version: Vec, - segment_size: u32, -} -impl PakEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -#[derive(BinRead)] -struct PakHeader { - #[br(count = 4)] _pak_name_bytes: Vec, - image_size: u32, - #[br(count = 64)] platform_id_bytes: Vec, - _sw_version: u32, - _sw_date: u32, - _build_type: u32, - segment_count: u32, - segment_size: u32, - segment_index: u32, - #[br(count = 4)] _pak_magic_bytes: Vec, //MPAK - #[br(count = 24)] _reserved: Vec, - _segment_crc32: u32, -} -impl PakHeader { - fn platform_id(&self) -> String { - common::string_from_bytes(&self.platform_id_bytes) - } -} - -struct Pak { - offset: u32, - _size: u32, - name: String, -} - -pub fn is_epk2_file(file: &File) -> bool { - let header = common::read_file(&file, 128, 4).expect("Failed to read from file."); + let header = common::read_file(&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> { - file.seek(SeekFrom::Start(0))?; +pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected 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 let header; @@ -98,8 +46,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box String { + common::string_from_bytes(&self.ota_id_bytes) + } +} + +#[derive(BinRead)] +pub struct PakHeader { + pak_name_bytes: [u8; 4], + pub image_size: u32, + platform_id_bytes: [u8; 64], + _sw_version: u32, + _sw_date: u32, + _build_type: u32, + pub segment_count: u32, + pub segment_size: u32, + pub segment_index: u32, + _pak_magic_bytes: [u8; 4], //MPAK + _reserved: [u8; 24], + _segment_crc32: u32, +} +impl PakHeader { + pub fn pak_name(&self) -> String { + common::string_from_bytes(&self.pak_name_bytes) + } + pub fn platform_id(&self) -> String { + common::string_from_bytes(&self.platform_id_bytes) + } +} + +#[derive(BinRead)] +pub struct Pak { + pub offset : u32, + pub size : u32, +} \ No newline at end of file diff --git a/src/formats/epk2b.rs b/src/formats/epk2b/mod.rs similarity index 53% rename from src/formats/epk2b.rs rename to src/formats/epk2b/mod.rs index b48c234..6d5735d 100644 --- a/src/formats/epk2b.rs +++ b/src/formats/epk2b/mod.rs @@ -1,69 +1,31 @@ -use std::fs::File; +mod include; +use std::any::Any; +use crate::AppContext; + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; +use include::*; -#[derive(BinRead)] -struct EpkHeader { - #[br(count = 4)] _epk_magic: Vec, //epak - file_size: u32, - pak_count: u32, - #[br(count = 4)] _epk2_magic: Vec, //EPK2 - #[br(count = 4)] version: Vec, - #[br(count = 32)] ota_id_bytes: Vec, -} -impl EpkHeader { - fn ota_id(&self) -> String { - common::string_from_bytes(&self.ota_id_bytes) - } -} +pub fn is_epk2b_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct PakHeader { - #[br(count = 4)] pak_name_bytes: Vec, - image_size: u32, - #[br(count = 64)] platform_id_bytes: Vec, - _sw_version: u32, - _sw_date: u32, - _build_type: u32, - segment_count: u32, - segment_size: u32, - segment_index: u32, - #[br(count = 4)] _pak_magic_bytes: Vec, //MPAK - #[br(count = 24)] _reserved: Vec, - _segment_crc32: u32, -} -impl PakHeader { - fn pak_name(&self) -> String { - common::string_from_bytes(&self.pak_name_bytes) - } - fn platform_id(&self) -> String { - common::string_from_bytes(&self.platform_id_bytes) - } -} - -#[derive(BinRead)] -struct Pak { - offset : u32, - 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."); + let epak_magic = common::read_file(&file, 0, 4)?; + let epk2_magic = common::read_file(&file, 12, 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> { - let header: EpkHeader = file.read_le()?; +pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: EpkHeader = file.read_le()?; println!("EPK info -\nData size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}", header.file_size, header.pak_count, header.ota_id(), header.version[2], header.version[1], header.version[0]); @@ -77,7 +39,9 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box String { + common::string_from_bytes(&self.ota_id_bytes) + } +} + +#[derive(BinRead)] +pub struct HeaderNewEx { + _pak_info_magic: [u8; 4], + encrypt_type_bytes: [u8; 6], + update_type_bytes: [u8; 6], + pub update_platform_version: f32, + pub compatible_minimum_version: f32, + pub need_to_check_compatible_version: i32, +} +impl HeaderNewEx { + pub fn encrypt_type(&self) -> String { + common::string_from_bytes(&self.encrypt_type_bytes) + } + pub fn update_type(&self) -> String { + common::string_from_bytes(&self.update_type_bytes) + } +} + +#[derive(BinRead)] +pub struct PkgInfoHeader { + pub package_info_list_size: u32, + pub package_info_count: u32, +} + +#[derive(BinRead)] +pub struct PkgInfoEntry { + _package_type: u32, + _package_info_size: u32, + package_name_bytes: [u8; 128], + _package_version_bytes: [u8; 96], + _package_architecture_bytes: [u8; 32], + _checksum: [u8; 32], + pub package_size: u32, + _dipk: u32, + //segment info + _is_segmented: u32, + pub segment_index: u32, + pub segment_count: u32, + pub segment_size: u32, + // + _unk: u32, +} +impl PkgInfoEntry { + pub fn package_name(&self) -> String { + common::string_from_bytes(&self.package_name_bytes) + } +} diff --git a/src/formats/epk3.rs b/src/formats/epk3/mod.rs similarity index 65% rename from src/formats/epk3.rs rename to src/formats/epk3/mod.rs index ef293fc..804b252 100644 --- a/src/formats/epk3.rs +++ b/src/formats/epk3/mod.rs @@ -1,77 +1,24 @@ -use std::fs::{self, File, OpenOptions}; -use std::path::{Path}; -use std::io::{Write, Seek, SeekFrom, Cursor}; -use binrw::{BinRead, BinReaderExt}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; +use std::path::Path; +use std::io::{Write, Cursor}; +use binrw::BinReaderExt; use crate::utils::common; use crate::keys; use crate::formats::epk::{decrypt_aes_ecb_auto, find_key}; +use include::*; -#[derive(BinRead)] -struct Header { - #[br(count = 4)] _magic_bytes: Vec, //EPK3 - #[br(count = 4)] version: Vec, - #[br(count = 32)] ota_id_bytes: Vec, - package_info_size: u32, - _bchunked: u32, -} -impl Header { - fn ota_id(&self) -> String { - common::string_from_bytes(&self.ota_id_bytes) - } +pub fn is_epk3_file(_app_ctx: &AppContext) -> Result>, Box> { + Ok(None) } -#[derive(BinRead)] -struct HeaderNewEx { - #[br(count = 4)] _pak_info_magic: Vec, - #[br(count = 6)] encrypt_type_bytes: Vec, - #[br(count = 6)] update_type_bytes: Vec, - update_platform_version: f32, - compatible_minimum_version: f32, - need_to_check_compatible_version: i32, -} -impl HeaderNewEx { - fn encrypt_type(&self) -> String { - common::string_from_bytes(&self.encrypt_type_bytes) - } - fn update_type(&self) -> String { - common::string_from_bytes(&self.update_type_bytes) - } -} +pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; - -#[derive(BinRead)] -struct PkgInfoHeader { - package_info_list_size: u32, - package_info_count: u32, -} - -#[derive(BinRead)] -struct PkgInfoEntry { - _package_type: u32, - _package_info_size: u32, - #[br(count = 128)] package_name_bytes: Vec, - #[br(count = 96)] _package_version_bytes: Vec, - #[br(count = 32)] _package_architecture_bytes: Vec, - #[br(count = 32)] _checksum: Vec, - package_size: u32, - _dipk: u32, - //segment info - _is_segmented: u32, - segment_index: u32, - segment_count: u32, - segment_size: u32, - // - _unk: u32, -} -impl PkgInfoEntry { - fn package_name(&self) -> String { - common::string_from_bytes(&self.package_name_bytes) - } -} - -pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box> { - file.seek(SeekFrom::Start(0))?; let stored_header = common::read_exact(&mut file, 1712)?; let header: Vec; let _header_signature; @@ -96,8 +43,7 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box, - entry_count: u16, - file_size: u32, -} - -#[derive(BinRead)] -struct Entry { - entry_type: u16, - entry_size: u32, - _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: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + let header = common::read_file(&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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: Header = file.read_le()?; println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count); @@ -45,16 +38,14 @@ 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)?; println!("- Saved file!"); } - - println!("\nExtraction finished!"); - + Ok(()) } \ No newline at end of file diff --git a/src/formats/invincible_image.rs b/src/formats/invincible_image.rs deleted file mode 100644 index 98f39aa..0000000 --- a/src/formats/invincible_image.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; -use std::io::{Write, Read, Seek, SeekFrom, Cursor}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::aes::{decrypt_aes128_cbc_nopad}; -use crate::utils::common; - -#[derive(BinRead)] -struct Header { - #[br(count = 16)] _magic_bytes: Vec, - #[br(count = 4)] file_version: Vec, - _unk1: u32, - #[br(count = 16)] ver1_bytes: Vec, - #[br(count = 16)] ver2_bytes: Vec, - _unk2: u16, - _type: u8, - keep_size: u32, - _unk3: u8, - data_start_offset: u32, - data_size: u32, - _data_size_2: u32, - skip_size: u32, - _unk4: u16, - _encryption_method: u8, // 0x01 - AES128, 0x02 - AES256 - _hash_type: u8, // 0x01 - MD5, 0x02 - SHA1 - #[br(count = 16)] ver3_bytes: Vec, - #[br(count = 16)] ver4_bytes: Vec, - #[br(count = 11)] _unk6: Vec, - payload_count: u8, -} -impl Header { - fn ver1(&self) -> String { - common::string_from_bytes(&self.ver1_bytes).replace('\n', "") - } - fn ver2(&self) -> String { - common::string_from_bytes(&self.ver2_bytes).replace('\n', "") - } - fn ver3(&self) -> String { - common::string_from_bytes(&self.ver3_bytes).replace('\n', "") - } - fn ver4(&self) -> String { - common::string_from_bytes(&self.ver4_bytes).replace('\n', "") - } -} - -#[derive(BinRead)] -struct Entry { - #[br(count = 16)] name_bytes: Vec, - start_offset: u32, - size: u32, -} -impl Entry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -pub fn is_invincible_image_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 16).expect("Failed to read from file."); - if header == b"INVINCIBLE_IMAGE" { - true - } else { - false - } -} - -pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result<(), Box> { - 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: {}", - header.file_version[0], header.file_version[1], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.data_start_offset, header.keep_size, header.skip_size, header.payload_count); - - let mut entries: Vec = Vec::new(); - - for i in 0..header.payload_count { - let entry: Entry = file.read_le()?; - println!("{}. {}, Start offset: {}, Size: {}", - i + 1, entry.name(), entry.start_offset, entry.size); - entries.push(entry); - } - - if header.file_version[0] != 3 { - println!("\nSorry, this version of the file is not supported!"); - return Ok(()) - } - - let mut encrypted_data = Vec::new(); - let mut buffer = vec![0u8; header.keep_size as usize]; - - file.seek(SeekFrom::Start(header.data_start_offset.into()))?; - - loop { - let bytes_read = file.read(&mut buffer)?; - if bytes_read == 0 { - break; // EOF - } - encrypted_data.extend_from_slice(&buffer[..bytes_read]); - file.seek(SeekFrom::Current(header.skip_size.into()))?; - } - - //v3 key + iv - let key: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36]; - let iv: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43]; - - println!("\nDecrypting data..."); - let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &key, &iv)?; - - let mut data_reader = Cursor::new(decrypted_data); - - let mut i = 1; - for entry in entries { - 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"); - - fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .open(output_path)?; - - out_file.write_all(&data)?; - - println!("- Saved file!"); - i += 1; - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/invincible_image/include.rs b/src/formats/invincible_image/include.rs new file mode 100644 index 0000000..5faa69d --- /dev/null +++ b/src/formats/invincible_image/include.rs @@ -0,0 +1,56 @@ +use crate::utils::common; +use binrw::BinRead; + +//v3 key + iv +pub static V3_KEY: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36]; +pub static V3_IV: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43]; + +#[derive(BinRead)] +pub struct Header { + _magic_bytes: [u8; 16], + pub file_version: [u8; 4], + _unk1: u32, + ver1_bytes: [u8; 16], + ver2_bytes: [u8; 16], + _unk2: u16, + _type: u8, + pub keep_size: u32, + _unk3: u8, + pub data_start_offset: u32, + pub data_size: u32, + _data_size_2: u32, + pub skip_size: u32, + _unk4: u16, + _encryption_method: u8, // 0x01 - AES128, 0x02 - AES256 + _hash_type: u8, // 0x01 - MD5, 0x02 - SHA1 + ver3_bytes: [u8; 16], + ver4_bytes: [u8; 16], + _unk6: [u8; 11], + pub payload_count: u8, +} +impl Header { + pub fn ver1(&self) -> String { + common::string_from_bytes(&self.ver1_bytes).replace('\n', "") + } + pub fn ver2(&self) -> String { + common::string_from_bytes(&self.ver2_bytes).replace('\n', "") + } + pub fn ver3(&self) -> String { + common::string_from_bytes(&self.ver3_bytes).replace('\n', "") + } + pub fn ver4(&self) -> String { + common::string_from_bytes(&self.ver4_bytes).replace('\n', "") + } +} + +#[derive(BinRead)] +pub struct Entry { + name_bytes: [u8; 16], + pub start_offset: u32, + pub size: u32, +} +impl Entry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} \ No newline at end of file diff --git a/src/formats/invincible_image/mod.rs b/src/formats/invincible_image/mod.rs new file mode 100644 index 0000000..8b49fb7 --- /dev/null +++ b/src/formats/invincible_image/mod.rs @@ -0,0 +1,80 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::{Path}; +use std::fs::{self, OpenOptions}; +use std::io::{Write, Read, Seek, SeekFrom, Cursor}; +use binrw::BinReaderExt; + +use crate::utils::aes::{decrypt_aes128_cbc_nopad}; +use crate::utils::common; +use include::*; + +pub fn is_invincible_image_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 16)?; + if header == b"INVINCIBLE_IMAGE" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected 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: {}", + header.file_version[0], header.file_version[1], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.data_start_offset, header.keep_size, header.skip_size, header.payload_count); + + let mut entries: Vec = Vec::new(); + + for i in 0..header.payload_count { + let entry: Entry = file.read_le()?; + println!("{}. {}, Start offset: {}, Size: {}", + i + 1, entry.name(), entry.start_offset, entry.size); + entries.push(entry); + } + + if header.file_version[0] != 3 { + return Err("Unsupported format version! (Only 3 is supported right now)".into()); + } + + let mut encrypted_data = Vec::new(); + let mut buffer = vec![0u8; header.keep_size as usize]; + + file.seek(SeekFrom::Start(header.data_start_offset.into()))?; + + loop { + let bytes_read = file.read(&mut buffer)?; + if bytes_read == 0 { + break; // EOF + } + encrypted_data.extend_from_slice(&buffer[..bytes_read]); + file.seek(SeekFrom::Current(header.skip_size.into()))?; + } + + println!("\nDecrypting data..."); + let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &V3_KEY, &V3_IV)?; + + let mut data_reader = Cursor::new(decrypted_data); + + let mut i = 1; + for entry in entries { + 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(&app_ctx.output_dir).join(entry.name() + ".bin"); + + 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)?; + + println!("- Saved file!"); + i += 1; + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/msd.rs b/src/formats/msd/mod.rs similarity index 86% rename from src/formats/msd.rs rename to src/formats/msd/mod.rs index 54196db..bed6f21 100644 --- a/src/formats/msd.rs +++ b/src/formats/msd/mod.rs @@ -1,3 +1,8 @@ +//MSD OUITH parsers +pub mod msd_ouith_parser_old; +pub mod msd_ouith_parser_tizen_1_8; +pub mod msd_ouith_parser_tizen_1_9; + // COMMON MSD FUNCTIONS use aes::Aes128; use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}}; @@ -7,7 +12,9 @@ type Aes128CbcDec = Decryptor; pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec) -> Result, Box> { let mut data = encrypted_data.to_vec(); - assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!"); + if data[0..8].to_vec() != b"Salted__" { + return Err("Invalid encrypted data!".into()); + } let salt = &data[8..16]; //key = md5 of (passphrase + salt) @@ -33,7 +40,9 @@ pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec) pub fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec) -> Result, Box> { let mut data = encrypted_data.to_vec(); - assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!"); + if data[0..8].to_vec() != b"Salted__" { + return Err("Invalid encrypted data!".into()); + } let salt = &data[8..16]; //iv = md5 of salt diff --git a/src/utils/msd_ouith_parser_old.rs b/src/formats/msd/msd_ouith_parser_old.rs similarity index 100% rename from src/utils/msd_ouith_parser_old.rs rename to src/formats/msd/msd_ouith_parser_old.rs diff --git a/src/utils/msd_ouith_parser_tizen_1_8.rs b/src/formats/msd/msd_ouith_parser_tizen_1_8.rs similarity index 100% rename from src/utils/msd_ouith_parser_tizen_1_8.rs rename to src/formats/msd/msd_ouith_parser_tizen_1_8.rs diff --git a/src/utils/msd_ouith_parser_tizen_1_9.rs b/src/formats/msd/msd_ouith_parser_tizen_1_9.rs similarity index 100% rename from src/utils/msd_ouith_parser_tizen_1_9.rs rename to src/formats/msd/msd_ouith_parser_tizen_1_9.rs diff --git a/src/formats/msd10/include.rs b/src/formats/msd10/include.rs new file mode 100644 index 0000000..4642fea --- /dev/null +++ b/src/formats/msd10/include.rs @@ -0,0 +1,28 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(BinRead)] +pub struct FileHeader { + _magic_bytes: [u8; 6], + pub section_count: u32 +} + +#[derive(BinRead)] +pub struct SectionEntry { + pub index: u32, + pub offset: u32, + pub size: u32, +} + +#[derive(BinRead)] +pub struct HeaderEntry { + pub offset: u32, + pub size: u32, + _name_length: u8, + #[br(count = _name_length)] name_bytes: Vec, +} +impl HeaderEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} \ No newline at end of file diff --git a/src/formats/msd10.rs b/src/formats/msd10/mod.rs similarity index 70% rename from src/formats/msd10.rs rename to src/formats/msd10/mod.rs index 0bd00f0..eb79515 100644 --- a/src/formats/msd10.rs +++ b/src/formats/msd10/mod.rs @@ -1,64 +1,42 @@ -use std::fs::{self, File, OpenOptions}; -use std::path::{Path}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; +use std::path::Path; use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; use crate::keys; use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen}; -use crate::utils::msd_ouith_parser_old::{parse_ouith_blob}; -use crate::utils::msd_ouith_parser_tizen_1_8::{parse_blob_1_8}; +use crate::formats::msd::msd_ouith_parser_old::{parse_ouith_blob}; +use crate::formats::msd::msd_ouith_parser_tizen_1_8::{parse_blob_1_8}; +use include::*; -#[derive(BinRead)] -struct FileHeader { - #[br(count = 6)] _magic_bytes: Vec, - section_count: u32 -} +pub fn is_msd10_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct SectionEntry { - index: u32, - offset: u32, - size: u32, -} - -#[derive(BinRead)] -struct HeaderEntry { - offset: u32, - size: u32, - _name_length: u8, - #[br(count = _name_length)] name_bytes: Vec, -} -impl HeaderEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -struct Section { - index: u32, - offset: u32, - size: u32, -} - -pub fn is_msd10_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); + let header = common::read_file(&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: &AppContext, _ctx: 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 header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); - let mut sections: Vec
= Vec::new(); + let mut sections: Vec = Vec::new(); for _i in 0..header.section_count { let section: SectionEntry = file.read_le()?; println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size); - sections.push(Section {index: section.index, offset: section.offset, size: section.size}); + sections.push(section); } let _zero_padding = common::read_exact(&mut file, 4)?; @@ -93,8 +71,7 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box, +} +impl HeaderEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} \ No newline at end of file diff --git a/src/formats/msd11.rs b/src/formats/msd11/mod.rs similarity index 62% rename from src/formats/msd11.rs rename to src/formats/msd11/mod.rs index 15416b5..09bbb69 100644 --- a/src/formats/msd11.rs +++ b/src/formats/msd11/mod.rs @@ -1,65 +1,40 @@ -use std::fs::{self, File, OpenOptions}; -use std::path::{Path}; -use std::io::{Write}; -use binrw::{BinRead, BinReaderExt}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; +use std::path::Path; +use std::io::Write; +use binrw::BinReaderExt; use crate::utils::common; use crate::keys; use crate::formats::msd::{decrypt_aes_salted_tizen, decrypt_aes_tizen}; -use crate::utils::msd_ouith_parser_tizen_1_9::{parse_blob_1_9}; +use crate::formats::msd::msd_ouith_parser_tizen_1_9::{parse_blob_1_9}; +use include::*; -#[derive(BinRead)] -struct FileHeader { - #[br(count = 6)] _magic_bytes: Vec, - _header_checksum: u32, //CRC32 of data with the size of "_header_size" - _header_size: u64, - section_count: u32, -} +pub fn is_msd11_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct SectionEntry { - index: u32, - offset: u64, - size: u64, -} - -#[derive(BinRead)] -struct HeaderEntry { - offset: u64, - size: u32, - _name_length: u8, - #[br(count = _name_length)] name_bytes: Vec, -} -impl HeaderEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -struct Section { - index: u32, - offset: u64, - size: u64, -} - -pub fn is_msd11_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 6).expect("Failed to read from file."); + let header = common::read_file(&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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: FileHeader = file.read_le()?; println!("\nNumber of sections: {}", header.section_count); - let mut sections: Vec
= Vec::new(); + let mut sections: Vec = Vec::new(); for _i in 0..header.section_count { let section: SectionEntry = file.read_le()?; println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size); - sections.push(Section {index: section.index, offset: section.offset, size: section.size}); + sections.push(section); } let header_count: u32 = file.read_le()?; @@ -90,8 +65,7 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Option { + 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() + } +} \ No newline at end of file diff --git a/src/formats/mstar.rs b/src/formats/mstar/mod.rs similarity index 83% rename from src/formats/mstar.rs rename to src/formats/mstar/mod.rs index aa471a4..28cb790 100644 --- a/src/formats/mstar.rs +++ b/src/formats/mstar/mod.rs @@ -1,37 +1,31 @@ -use std::fs::{self, File, OpenOptions}; -use std::path::{Path}; -use std::io::{Write}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; +use std::path::Path; +use std::io::Write; use crate::utils::common; use crate::utils::compression::{decompress_lzma, decompress_lz4}; use crate::utils::lzop::{unlzop_to_file}; use crate::utils::sparse::{unsparse_to_file}; +use include::*; -//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(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -pub fn is_mstar_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 32768).expect("Failed to read from file."); + let header = common::read_file(&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) } } -fn parse_number(s: &str) -> Option { - 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() - } -} - -pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let file = app_ctx.file().ok_or("Extractor expected file")?; let mut script = common::read_file(&file, 0, 32768)?; @@ -53,7 +47,7 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Option { + data.windows(pattern.len()).position(|window| window == pattern) +} + +pub static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50]; +pub static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT + +#[derive(BinRead)] +pub struct PITITPITEntry { + pub nand_size: u32, + pub pit_offset: u32, + pub pit_size: u32, + _table_id: u32, +} + +#[derive(BinRead)] +pub struct PITITBITEntry { + pub bit_offset: u32, + pub bit_size: u32, + _private_data_1: u32, + _private_data_2: u32, +} + +#[derive(BinRead)] +pub struct PITHeader { + pub pit_magic: [u8; 8], + _version: u32, + pub first_entry_offset: u32, //"header len" + pub entry_size: u32, //"item lenght" + pub entry_count: u32, //"item num" +} + +pub static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85]; + +#[derive(BinRead)] +pub struct PITEntry { + name_bytes: [u8; 16], + pub partition_id: u32, + _part_info: u32, + pub offset_on_nand: u32, + pub size_on_nand: u32, + _enc_size: u32, + _no_enc_size: u32, + _reserve: [u8; 24], +} +impl PITEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +#[derive(BinRead)] +pub struct BITEntry { + pub partition_id: u32, + pub offset: u32, + pub size: u32, + pub offset_in_target_part: u32, + _bin_info: u32, //"Bin info" +} + +pub static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85]; +pub static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT \ No newline at end of file diff --git a/src/formats/mtk_bdp.rs b/src/formats/mtk_bdp/mod.rs similarity index 67% rename from src/formats/mtk_bdp.rs rename to src/formats/mtk_bdp/mod.rs index 4063896..22c9bbc 100644 --- a/src/formats/mtk_bdp.rs +++ b/src/formats/mtk_bdp/mod.rs @@ -1,78 +1,21 @@ -use std::fs::File; +mod include; +use std::any::Any; +use crate::AppContext; + use std::path::{Path}; use std::fs::{self, OpenOptions}; use std::io::{Seek, SeekFrom, Read, Write}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; +use include::*; + pub struct MtkBdpContext { pitit_offset: u64, } -static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50]; -static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT - -#[derive(BinRead)] -struct PITITPITEntry { - nand_size: u32, - pit_offset: u32, - pit_size: u32, - _table_id: u32, -} - -#[derive(BinRead)] -struct PITITBITEntry { - bit_offset: u32, - bit_size: u32, - _private_data_1: u32, - _private_data_2: u32, -} - -#[derive(BinRead)] -struct PITHeader { - #[br(count = 8)] pit_magic: Vec, - _version: u32, - first_entry_offset: u32, //"header len" - entry_size: u32, //"item lenght" - entry_count: u32, //"item num" -} - -static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85]; - -#[derive(BinRead)] -struct PITEntry { - #[br(count = 16)] name_bytes: Vec, - partition_id: u32, - _part_info: u32, - offset_on_nand: u32, - size_on_nand: u32, - _enc_size: u32, - _no_enc_size: u32, - #[br(count = 24)] _reserve: Vec, -} -impl PITEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } -} - -#[derive(BinRead)] -struct BITEntry { - partition_id: u32, - offset: u32, - size: u32, - offset_in_target_part: u32, - _bin_info: u32, //"Bin info" -} - -static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85]; -static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT - -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: &AppContext) -> Result>, Box> { + let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; let file_size = file.metadata()?.len(); let mut data = Vec::new(); @@ -83,14 +26,17 @@ 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: &AppContext, ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let ctx = ctx.downcast::().expect("Missing context"); + + let offset = ctx.pitit_offset; println!("\nReading PITIT at: {}", offset); file.seek(SeekFrom::Start(offset + 8))?; @@ -126,8 +72,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont let mut pit_entries: Vec = Vec::new(); let pit_header: PITHeader = file.read_le()?; if pit_header.pit_magic != PIT_MAGIC { - println!("Invalid PIT Magic!"); - return Ok(()); + return Err("Invalid PIT magic!".into()); } println!("PIT Info - First entry offs: {}, Entry size: {}, Entry count: {}", pit_header.first_entry_offset, pit_header.entry_size, pit_header.entry_count); file.seek(SeekFrom::Start(pit_offset + pit_header.first_entry_offset as u64))?; @@ -144,8 +89,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont let mut bit_entries: Vec = Vec::new(); let bit_magic = common::read_exact(&mut file, 20)?; if bit_magic != BIT_MAGIC { - println!("Invalid BIT Magic!"); - return Ok(()); + return Err("Invalid BIT magic!".into()); } let mut bit_i = 0; @@ -173,16 +117,14 @@ 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)?; println!("-- Saved file!"); } - - println!("\nExtraction finished!"); - + Ok(()) } \ No newline at end of file diff --git a/src/utils/huffman_tables.rs b/src/formats/mtk_pkg/huffman_tables.rs similarity index 100% rename from src/utils/huffman_tables.rs rename to src/formats/mtk_pkg/huffman_tables.rs diff --git a/src/formats/mtk_pkg/include.rs b/src/formats/mtk_pkg/include.rs new file mode 100644 index 0000000..7b01dfd --- /dev/null +++ b/src/formats/mtk_pkg/include.rs @@ -0,0 +1,63 @@ +use crate::utils::common; +use binrw::BinRead; + +pub static HEADER_KEY: [u8; 16] = [ + 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, + 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, +]; + +pub static HEADER_IV: [u8; 16] = [0x00; 16]; + +pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm"; +pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc"; +pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK"; +pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd"; +pub static CRYPTED_HEADER_SIZE: usize = 0x30; + +pub static HEADER_SIZE: usize = 0x90; + +pub static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80; +pub static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100; + +#[derive(BinRead)] +pub struct Header { + pub vendor_magic_bytes: [u8; 4], + _mtk_magic: [u8; 8], //#DH@FiRm + version_bytes: [u8; 60], + pub file_size: u32, + _flags: u32, + product_name_bytes: [u8; 32], +} +impl Header { + pub fn vendor_magic(&self) -> String { + common::string_from_bytes(&self.vendor_magic_bytes) + } + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn product_name(&self) -> String { + common::string_from_bytes(&self.product_name_bytes) + } + +} + +#[derive(BinRead)] +pub struct PartEntry { + name_bytes: [u8; 4], + pub flags: u32, + pub size: u32, +} +impl PartEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } + pub fn is_valid(&self) -> bool { + self.name().is_ascii() + } + pub fn is_encrypted(&self) -> bool { + (self.flags & 1 << 0) != 0 + } + pub fn is_compressed(&self) -> bool { //lzhs fs + (self.flags & 1 << 8) != 0 + } +} \ No newline at end of file diff --git a/src/utils/lzhs.rs b/src/formats/mtk_pkg/lzhs.rs similarity index 98% rename from src/utils/lzhs.rs rename to src/formats/mtk_pkg/lzhs.rs index db82286..634218e 100644 --- a/src/utils/lzhs.rs +++ b/src/formats/mtk_pkg/lzhs.rs @@ -3,7 +3,7 @@ use std::io::{Write, Cursor, Seek, SeekFrom, Read}; use binrw::{BinRead, BinReaderExt}; use std::path::{PathBuf}; -use crate::utils::huffman_tables::{CHARLEN, POS}; +use super::huffman_tables::{CHARLEN, POS}; use crate::utils::compression::{decompress_lz4}; #[derive(BinRead)] @@ -11,7 +11,7 @@ struct LzhsHeader { uncompressed_size: u32, compressed_size: u32, checksum_or_seg_idx: u16, //as checksum in normal lzhs header, as index in lzhs_fs header - #[br(count = 6)] padding: Vec, + padding: [u8; 6], } #[derive(BinRead)] @@ -35,7 +35,7 @@ pub fn decompress_lzhs_fs_file2file(mut file: &File, output_file: PathBuf) -> Re //lz4 type uses a 4 byte checksum instead of 1(2) byte , so the padding is not 0 anymore //maybe this method will be changed - let is_lz4 = if lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false}; + let is_lz4 = if &lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false}; println!("[{}] Segment {} - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}", if is_lz4 {"LZ4"} else {"LZHS"}, segment_header.checksum_or_seg_idx, lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx); diff --git a/src/formats/mtk_pkg.rs b/src/formats/mtk_pkg/mod.rs similarity index 69% rename from src/formats/mtk_pkg.rs rename to src/formats/mtk_pkg/mod.rs index 3a30877..f728ed6 100644 --- a/src/formats/mtk_pkg.rs +++ b/src/formats/mtk_pkg/mod.rs @@ -1,91 +1,38 @@ +pub mod include; +pub mod lzhs; +mod huffman_tables; +use std::any::Any; +use crate::AppContext; + 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}; +use binrw::BinReaderExt; use crate::utils::common; use crate::utils::aes::{decrypt_aes128_cbc_nopad}; -use crate::utils::lzhs::{decompress_lzhs_fs_file2file}; use crate::keys; +use lzhs::{decompress_lzhs_fs_file2file}; +use include::*; pub struct MtkPkgContext { is_philips_variant: bool, decrypted_header: Vec, } -#[derive(BinRead)] -struct Header { - #[br(count = 4)] vendor_magic_bytes: Vec, - #[br(count = 8)] _mtk_magic: Vec, //#DH@FiRm - #[br(count = 60)] version_bytes: Vec, - file_size: u32, - _flags: u32, - #[br(count = 32)] product_name_bytes: Vec, - #[br(count = 32)] _digest: Vec, -} -impl Header { - fn vendor_magic(&self) -> String { - common::string_from_bytes(&self.vendor_magic_bytes) - } - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn product_name(&self) -> String { - common::string_from_bytes(&self.product_name_bytes) - } +pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -} - -#[derive(BinRead)] -struct PartEntry { - #[br(count = 4)] name_bytes: Vec, - flags: u32, - size: u32, -} -impl PartEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn is_valid(&self) -> bool { - self.name().is_ascii() - } - fn is_encrypted(&self) -> bool { - (self.flags & 1 << 0) != 0 - } - fn is_compressed(&self) -> bool { //lzhs fs - (self.flags & 1 << 8) != 0 - } -} - -pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm"; -pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc"; -pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK"; -pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd"; -pub static CRYPTED_HEADER_SIZE: usize = 0x30; - -static HEADER_SIZE: usize = 0x90; - -static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80; -static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100; - -static HEADER_KEY: [u8; 16] = [ - 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, - 0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94, -]; - -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)?; 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)?; 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 +40,19 @@ pub fn is_mtk_pkg_file(file: &File) -> Result, Box 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 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 +139,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!"); @@ -212,7 +162,5 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgCont println!("-- Saved file!"); } - println!("\nExtraction finished!"); - Ok(()) } \ No newline at end of file diff --git a/src/formats/mtk_pkg_new/include.rs b/src/formats/mtk_pkg_new/include.rs new file mode 100644 index 0000000..7a53f4f --- /dev/null +++ b/src/formats/mtk_pkg_new/include.rs @@ -0,0 +1 @@ +pub static HEADER_SIZE: usize = 0x170; \ No newline at end of file diff --git a/src/formats/mtk_pkg_new.rs b/src/formats/mtk_pkg_new/mod.rs similarity index 66% rename from src/formats/mtk_pkg_new.rs rename to src/formats/mtk_pkg_new/mod.rs index 3ba5d5f..1203a8e 100644 --- a/src/formats/mtk_pkg_new.rs +++ b/src/formats/mtk_pkg_new/mod.rs @@ -1,14 +1,18 @@ +mod include; +use std::any::Any; +use crate::AppContext; + 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}; +use binrw::BinReaderExt; use crate::utils::common; use crate::utils::aes::{decrypt_aes128_cbc_nopad}; -use crate::utils::lzhs::{decompress_lzhs_fs_file2file}; use crate::keys; - -use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC}; +use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file}; +use crate::formats::mtk_pkg::include::{Header, PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC}; +use include::*; pub struct MtkPkgNewContext { matching_key_name: String, @@ -17,54 +21,9 @@ pub struct MtkPkgNewContext { decrypted_header: Vec, } -#[derive(BinRead)] -struct Header { - #[br(count = 4)] vendor_magic_bytes: Vec, - #[br(count = 8)] _mtk_magic: Vec, //#DH@FiRm - #[br(count = 56)] version_bytes: Vec, - _unk: u32, - file_size: u32, - _flags: u32, - #[br(count = 32)] product_name_bytes: Vec, - #[br(count = 256)] _digest: Vec, -} -impl Header { - fn vendor_magic(&self) -> String { - common::string_from_bytes(&self.vendor_magic_bytes) - } - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn product_name(&self) -> String { - common::string_from_bytes(&self.product_name_bytes) - } +pub fn is_mtk_pkg_new_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -} - -#[derive(BinRead)] -struct PartEntry { - #[br(count = 4)] name_bytes: Vec, - flags: u32, - size: u32, -} -impl PartEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn is_valid(&self) -> bool { - self.name().is_ascii() - } - fn is_encrypted(&self) -> bool { - (self.flags & 1 << 0) != 0 - } - fn is_compressed(&self) -> bool { //lzhs fs - (self.flags & 1 << 8) != 0 - } -} - -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)?; for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST { let key_array: [u8; 16] = hex::decode(key_hex)?.as_slice().try_into()?; @@ -72,26 +31,29 @@ pub fn is_mtk_pkg_new_file(file: &File) -> Result, Box< 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: &AppContext, ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let ctx = ctx.downcast::().expect("Missing context"); + 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 +109,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!"); @@ -170,7 +132,5 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkg println!("-- Saved file!"); } - println!("\nExtraction finished!"); - Ok(()) } \ No newline at end of file diff --git a/src/formats/mtk_pkg_old/include.rs b/src/formats/mtk_pkg_old/include.rs new file mode 100644 index 0000000..11e36e8 --- /dev/null +++ b/src/formats/mtk_pkg_old/include.rs @@ -0,0 +1,33 @@ +use crate::utils::common; +use binrw::BinRead; + +pub static KEY: u32 = 0x94102909; //09 29 10 94 +// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte +pub static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04 +pub static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04 + +pub static HEADER_SIZE: usize = 0x98; + +#[derive(BinRead)] +pub struct Header { + pub vendor_magic_bytes: [u8; 4], + _mtk_magic: [u8; 8], + version_bytes: [u8; 68], + pub file_size: u32, + _flags: u32, + product_name_bytes: [u8; 32], + _digest: [u8; 32], +} +impl Header { + pub fn vendor_magic(&self) -> String { + common::string_from_bytes(&self.vendor_magic_bytes) + } + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn product_name(&self) -> String { + common::string_from_bytes(&self.product_name_bytes) + } + +} + diff --git a/src/formats/mtk_pkg_old.rs b/src/formats/mtk_pkg_old/mod.rs similarity index 59% rename from src/formats/mtk_pkg_old.rs rename to src/formats/mtk_pkg_old/mod.rs index a03231f..0d91749 100644 --- a/src/formats/mtk_pkg_old.rs +++ b/src/formats/mtk_pkg_old/mod.rs @@ -1,81 +1,42 @@ +mod include; +mod mtk_crypto; +use std::any::Any; +use crate::AppContext; + 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}; +use binrw::BinReaderExt; use crate::utils::common; -use crate::utils::mtk_crypto::{decrypt}; -use crate::utils::lzhs::{decompress_lzhs_fs_file2file_old}; +use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file_old}; +use crate::formats::mtk_pkg::include::{PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC}; +use mtk_crypto::{decrypt}; +use include::*; -static KEY: u32 = 0x94102909; //09 29 10 94 -// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte -static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04 -static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04 - -use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC}; - -#[derive(BinRead)] -struct Header { - #[br(count = 4)] vendor_magic_bytes: Vec, - #[br(count = 8)] _mtk_magic: Vec, //#DH@FiRm - #[br(count = 68)] version_bytes: Vec, - file_size: u32, - _flags: u32, - #[br(count = 32)] product_name_bytes: Vec, - #[br(count = 32)] _digest: Vec, -} -impl Header { - fn vendor_magic(&self) -> String { - common::string_from_bytes(&self.vendor_magic_bytes) - } - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn product_name(&self) -> String { - common::string_from_bytes(&self.product_name_bytes) - } - -} - -#[derive(BinRead)] -struct PartEntry { - #[br(count = 4)] name_bytes: Vec, - flags: u32, - size: u32, -} -impl PartEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn is_encrypted(&self) -> bool { - (self.flags & 1 << 0) == 1 << 0 - } - fn is_compressed(&self) -> bool { //lzhs fs - (self.flags & 1 << 8) != 0 - } -} - -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: &AppContext) -> Result>, Box> { + let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + 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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected 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 +79,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!"); @@ -141,7 +102,5 @@ pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), B println!("-- Saved file!"); } - println!("\nExtraction finished!"); - Ok(()) } \ No newline at end of file diff --git a/src/utils/mtk_crypto.rs b/src/formats/mtk_pkg_old/mtk_crypto.rs similarity index 100% rename from src/utils/mtk_crypto.rs rename to src/formats/mtk_pkg_old/mtk_crypto.rs diff --git a/src/formats/novatek.rs b/src/formats/novatek.rs deleted file mode 100644 index d99c029..0000000 --- a/src/formats/novatek.rs +++ /dev/null @@ -1,80 +0,0 @@ -use std::fs::File; -use std::path::Path; -use std::fs::{self, OpenOptions}; -use std::io::{Write}; - -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; - -#[derive(BinRead)] -struct Header { - #[br(count = 4)] _magic_bytes: Vec, - version_major: u32, - version_minor: u32, - _unused: u32, - #[br(count = 16)] firmware_name_bytes: Vec, - data_size: u32, - #[br(count = 16)] _md5_checksum: Vec, //data checksum - part_count: u32, - _data_start_offset: u32, - #[br(count = 128)] _signature: Vec, - _header_checksum: u32, //CRC32, calculated with the field set to 0 -} -impl Header { - fn firmware_name(&self) -> String { - common::string_from_bytes(&self.firmware_name_bytes) - } -} - -#[derive(BinRead)] -struct PartEntry { - id: u32, - size: u32, - offset: u32, - #[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."); - if header == b"NFWB" { - true - } else { - false - } -} - -pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box> { - let header: Header = file.read_le()?; - - println!("File info:\nFirmware name: {}\nVersion: {}.{}\nData size: {}\nPart count: {}", - header.firmware_name(), header.version_major, header.version_minor, header.data_size, header.part_count); - - let mut entries: Vec = Vec::new(); - for _i in 0..header.part_count { - let part: PartEntry = file.read_le()?; - entries.push(part); - } - - let mut e_i = 0; - for entry in &entries { - e_i += 1; - println!("\n({}/{}) - ID: {}, Offset: {}, Size: {}", e_i, entries.len(), entry.id, entry.offset, entry.size); - - let data = common::read_file(&file, entry.offset as u64, entry.size as usize)?; - - let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", e_i, entry.id)); - - fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .open(output_path)?; - - out_file.write_all(&data)?; - - println!("- Saved file!"); - } - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/novatek/include.rs b/src/formats/novatek/include.rs new file mode 100644 index 0000000..f4eda9a --- /dev/null +++ b/src/formats/novatek/include.rs @@ -0,0 +1,30 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(BinRead)] +pub struct Header { + _magic_bytes: [u8; 4], + pub version_major: u32, + pub version_minor: u32, + _unused: u32, + firmware_name_bytes: [u8; 16], + pub data_size: u32, + _md5_checksum: [u8; 16], //data checksum + pub part_count: u32, + _data_start_offset: u32, + _signature: [u8; 128], + _header_checksum: u32, //CRC32, calculated with the field set to 0 +} +impl Header { + pub fn firmware_name(&self) -> String { + common::string_from_bytes(&self.firmware_name_bytes) + } +} + +#[derive(BinRead)] +pub struct PartEntry { + pub id: u32, + pub size: u32, + pub offset: u32, + _md5_checksum: [u8; 16], +} \ No newline at end of file diff --git a/src/formats/novatek/mod.rs b/src/formats/novatek/mod.rs new file mode 100644 index 0000000..3ef7c6f --- /dev/null +++ b/src/formats/novatek/mod.rs @@ -0,0 +1,54 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::Path; +use std::fs::{self, OpenOptions}; +use std::io::Write; +use binrw::BinReaderExt; + +use crate::utils::common; +use include::*; + +pub fn is_novatek_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 4)?; + if header == b"NFWB" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let header: Header = file.read_le()?; + println!("File info:\nFirmware name: {}\nVersion: {}.{}\nData size: {}\nPart count: {}", + header.firmware_name(), header.version_major, header.version_minor, header.data_size, header.part_count); + + let mut entries: Vec = Vec::new(); + for _i in 0..header.part_count { + let part: PartEntry = file.read_le()?; + entries.push(part); + } + + let mut e_i = 0; + for entry in &entries { + e_i += 1; + println!("\n({}/{}) - ID: {}, Offset: {}, Size: {}", e_i, entries.len(), entry.id, entry.offset, entry.size); + + let data = common::read_file(&file, entry.offset as u64, entry.size as usize)?; + + let output_path = Path::new(&app_ctx.output_dir).join(format!("{}_{}.bin", e_i, entry.id)); + + 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)?; + + println!("- Saved file!"); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/nvt_timg.rs b/src/formats/nvt_timg.rs deleted file mode 100644 index 7739502..0000000 --- a/src/formats/nvt_timg.rs +++ /dev/null @@ -1,106 +0,0 @@ -use std::str; -use std::path::{Path}; -use std::io::{Seek, Write}; -use std::fs::{self, File, OpenOptions}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; -use crate::utils::compression::{decompress_gzip}; -use crate::utils::sparse::{unsparse_to_file}; - -#[derive(Debug, BinRead)] -struct TIMG { - #[br(count = 4)] _magic_bytes: Vec, //TIMG - _unused1: u32, - data_size: u32, - _unused2: u32, - #[br(count = 16)] _md5_checksum: Vec, - #[br(count = 256)] _signature: Vec, -} - -#[derive(Debug, BinRead)] -struct PIMG { - #[br(count = 4)] magic_bytes: Vec, //PIMG - _unused1: u32, - size: u32, - _unused2: u32, - #[br(count = 16)] _md5_checksum: Vec, - #[br(count = 16)] name_bytes: Vec, - #[br(count = 64)] dest_dev_bytes: Vec, - #[br(count = 16)] comp_type_bytes: Vec, - _unknown1: u32, - #[br(count = 1024)] _comment: Vec, - _unknown2: u32, -} -impl PIMG { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn dest_dev(&self) -> String { - common::string_from_bytes(&self.dest_dev_bytes) - } - fn comp_type(&self) -> String { - common::string_from_bytes(&self.comp_type_bytes) - } -} - -pub fn is_nvt_timg_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); - if header == b"TIMG" { - true - } else { - false - } -} - -pub fn extract_nvt_timg(mut file: &File, output_folder: &str) -> Result<(), Box> { - let file_size = file.metadata()?.len(); - let timg: TIMG = file.read_le()?; - println!("File info:\nData size: {}", timg.data_size); - - let mut pimg_i = 0; - while file.stream_position()? < file_size as u64 { - pimg_i += 1; - let pimg: PIMG = file.read_le()?; - if pimg.magic_bytes != b"PIMG" { - println!("Invalid PIMG magic!"); - return Ok(()); - } - - let data = common::read_exact(&mut file, pimg.size as usize)?; - - 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"); - - if pimg.comp_type() == "gzip" && data.starts_with(b"\x1F\x8B") { //additionally check for gzip header, because sometimes its deceptive - println!("- Decompressing gzip..."); - out_data = decompress_gzip(&data)?; - } else if pimg.comp_type() == "none" || pimg.comp_type() == "" { - out_data = data; - } else if pimg.comp_type() == "sparse" { - println!("- Unsparsing..."); - unsparse_to_file(&data, output_path)?; - println!("-- Saved file!"); - continue - } else { - println!("- Warning: unsupported compression type!"); - out_data = data; - } - - fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .open(output_path)?; - - out_file.write_all(&out_data)?; - - println!("-- Saved file!"); - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/nvt_timg/include.rs b/src/formats/nvt_timg/include.rs new file mode 100644 index 0000000..1b91aa1 --- /dev/null +++ b/src/formats/nvt_timg/include.rs @@ -0,0 +1,38 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(Debug, BinRead)] +pub struct TIMG { + _magic_bytes: [u8; 4], //TIMG + _unused1: u32, + pub data_size: u32, + _unused2: u32, + _md5_checksum: [u8; 16], + _signature: [u8; 256], +} + +#[derive(Debug, BinRead)] +pub struct PIMG { + pub magic_bytes: [u8; 4], //PIMG + _unused1: u32, + pub size: u32, + _unused2: u32, + _md5_checksum: [u8; 16], + name_bytes: [u8; 16], + dest_dev_bytes: [u8; 64], + comp_type_bytes: [u8; 16], + _unknown1: u32, + _comment: [u8; 1024], + _unknown2: u32, +} +impl PIMG { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } + pub fn dest_dev(&self) -> String { + common::string_from_bytes(&self.dest_dev_bytes) + } + pub fn comp_type(&self) -> String { + common::string_from_bytes(&self.comp_type_bytes) + } +} \ No newline at end of file diff --git a/src/formats/nvt_timg/mod.rs b/src/formats/nvt_timg/mod.rs new file mode 100644 index 0000000..581e4fd --- /dev/null +++ b/src/formats/nvt_timg/mod.rs @@ -0,0 +1,71 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::Path; +use std::io::{Seek, Write}; +use std::fs::{self, OpenOptions}; +use binrw::BinReaderExt; + +use crate::utils::common; +use crate::utils::compression::{decompress_gzip}; +use crate::utils::sparse::{unsparse_to_file}; +use include::*; + +pub fn is_nvt_timg_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 4)?; + if header == b"TIMG" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_nvt_timg(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let file_size = file.metadata()?.len(); + let timg: TIMG = file.read_le()?; + println!("File info:\nData size: {}", timg.data_size); + + let mut pimg_i = 0; + while file.stream_position()? < file_size as u64 { + pimg_i += 1; + let pimg: PIMG = file.read_le()?; + if &pimg.magic_bytes != b"PIMG" { + return Err("Invalid PIMG magic!".into()); + } + + let data = common::read_exact(&mut file, pimg.size as usize)?; + + 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(&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..."); + out_data = decompress_gzip(&data)?; + } else if pimg.comp_type() == "none" || pimg.comp_type() == "" { + out_data = data; + } else if pimg.comp_type() == "sparse" { + println!("- Unsparsing..."); + unsparse_to_file(&data, output_path)?; + println!("-- Saved file!"); + continue + } else { + println!("- Warning: unsupported compression type, saving stored data!"); + out_data = data; + } + + 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(&out_data)?; + + println!("-- Saved file!"); + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/pana_dvd/include.rs b/src/formats/pana_dvd/include.rs new file mode 100644 index 0000000..080ccb0 --- /dev/null +++ b/src/formats/pana_dvd/include.rs @@ -0,0 +1,132 @@ +use crate::utils::common; +use binrw::BinRead; +use super::pana_dvd_crypto::{decrypt_data}; +use crate::utils::aes::{decrypt_aes128_cbc_nopad}; + +pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result, Box> { + for key_hex in key_array { + let key_bytes = hex::decode(key_hex)?; + let key_array: [u8; 8] = key_bytes.as_slice().try_into()?; + let decrypted = decrypt_data(data, &key_array); + + if decrypted[magic_offset..].starts_with(expected_magic) { + return Ok(Some(key_array)); + } + } + Ok(None) +} + +pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result, Box> { + for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array { + let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?; + let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?; + let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?; + + let key_bytes = hex::decode(cust_key_hex)?; + let key_array: [u8; 8] = key_bytes.as_slice().try_into()?; + let decrypted = decrypt_data(&aes_decrypted, &key_array); + + if decrypted[magic_offset..].starts_with(expected_magic) { + return Ok(Some((aes_key, aes_iv, key_array))); + } + } + Ok(None) +} + +pub static MAX_HEADER_SIZE: usize = 0x2000; + +#[derive(BinRead)] +pub struct AesHeaderFileEntry { + pub offset: u32, + pub size: u32, +} + +pub struct FileEntry { + pub offset: u32, + pub base_offset: u32, +} + +//checksums are mostly Adler32, but some very old files use Checksum32 instead. + +#[derive(BinRead)] +pub struct ModuleEntry { + #[br(count = 4)] pub name_bytes: Vec, + version_bytes: [u8; 4], + _unk: u32, + pub offset: u32, + platform_bytes: [u8; 8], + _unk1: u16, + id_bytes: [u8; 6], + pub size: u32, + pub data_checksum: u32, //checksum of the entrys' DATA + _unk2: u32, + _entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes) +} +impl ModuleEntry { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn platform(&self) -> String { + common::string_from_bytes(&self.platform_bytes) + } + pub fn id(&self) -> String { + common::string_from_bytes(&self.id_bytes) + } + pub fn is_valid(&self) -> bool { + self.name().is_ascii() && self.platform().is_ascii() + } +} + +#[derive(BinRead)] +pub struct MainListHeader { + _checksum: u32, //checksum of the MAIN LIST + _unk: u32, //seems to be always 1? + pub list_size: u32, + pub decompressed_part_size: u32, + _unk2: u32, +} +impl MainListHeader { + pub fn entry_count(&self) -> u32 { + (&self.list_size - 20) / 8 + } +} + +#[derive(BinRead)] +pub struct MainListEntry { + pub size: u32, + pub checksum: u32, //checksum of this MAIN entrys' data +} + +pub const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD"; + +#[derive(BinRead)] +pub struct CompressedFileHeader { + _header_string: [u8; 14], //EXTRHEADDRV \x01\x00 + pub compression_type_byte: u16, + pub decompressed_size: u32, + _destination_address: u32, + pub compressed_size: u32, + _unk: u32, + _footer_offset: u32, + _base_address: u32, + _checksum: u32, //unknown type of checksum + _checksum_flag: u8, + _unused: [u8; 19], +} +impl CompressedFileHeader { + pub fn compression_type(&self) -> &str { + if self.compression_type_byte == 0 { + return "Uncompressed" + } else if self.compression_type_byte == 1 { + return "GZIP" + } else if self.compression_type_byte == 2 { + return "LZSS" + } else { + return "Unknown" + } + } +} + diff --git a/src/utils/lzss.rs b/src/formats/pana_dvd/lzss.rs similarity index 100% rename from src/utils/lzss.rs rename to src/formats/pana_dvd/lzss.rs diff --git a/src/formats/pana_dvd.rs b/src/formats/pana_dvd/mod.rs similarity index 69% rename from src/formats/pana_dvd.rs rename to src/formats/pana_dvd/mod.rs index bad1ce3..541e92d 100644 --- a/src/formats/pana_dvd.rs +++ b/src/formats/pana_dvd/mod.rs @@ -1,15 +1,21 @@ -use std::fs::File; +mod include; +mod pana_dvd_crypto; +mod lzss; +use std::any::Any; +use crate::AppContext; + use std::path::{Path, PathBuf}; use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Cursor, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::keys; use crate::utils::common; -use crate::utils::pana_dvd_crypto::{decrypt_data}; use crate::utils::aes::{decrypt_aes128_cbc_nopad}; use crate::utils::compression::{decompress_gzip}; -use crate::utils::lzss::{decompress_lzss}; +use pana_dvd_crypto::{decrypt_data}; +use lzss::{decompress_lzss}; +use include::*; pub struct PanaDvdContext { matching_key: [u8; 8], @@ -19,165 +25,42 @@ pub struct PanaDvdContext { aes_iv: Option<[u8; 16]> } -#[derive(BinRead)] -struct AesHeaderFileEntry { - offset: u32, - size: u32, -} - -struct FileEntry { - offset: u32, - base_offset: u32, -} - -//checksums are mostly Adler32, but some very old files use Checksum32 instead. - -#[derive(BinRead)] -struct ModuleEntry { - #[br(count = 4)] name_bytes: Vec, - #[br(count = 4)] version_bytes: Vec, - _unk: u32, - offset: u32, - #[br(count = 8)] platform_bytes: Vec, - _unk1: u16, - #[br(count = 6)] id_bytes: Vec, - size: u32, - data_checksum: u32, //checksum of the entrys' DATA - _unk2: u32, - _entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes) -} -impl ModuleEntry { - fn name(&self) -> String { - common::string_from_bytes(&self.name_bytes) - } - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn platform(&self) -> String { - common::string_from_bytes(&self.platform_bytes) - } - fn id(&self) -> String { - common::string_from_bytes(&self.id_bytes) - } - fn is_valid(&self) -> bool { - self.name().is_ascii() && self.platform().is_ascii() - } -} - -#[derive(BinRead)] -struct MainListHeader { - _checksum: u32, //checksum of the MAIN LIST - _unk: u32, //seems to be always 1? - list_size: u32, - decompressed_part_size: u32, - _unk2: u32, -} -impl MainListHeader { - fn entry_count(&self) -> u32 { - (&self.list_size - 20) / 8 - } -} - -#[derive(BinRead)] -struct MainListEntry { - size: u32, - checksum: u32, //checksum of this MAIN entrys' data -} - -const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD"; - -#[derive(BinRead)] -struct CompressedFileHeader { - #[br(count = 14)] _header_string: Vec, //EXTRHEADDRV \x01\x00 - compression_type_byte: u16, - decompressed_size: u32, - _destination_address: u32, - compressed_size: u32, - _unk: u32, - _footer_offset: u32, - _base_address: u32, - _checksum: u32, //unknown type of checksum - _checksum_flag: u8, - #[br(count = 19)] _unused: Vec, -} -impl CompressedFileHeader { - fn compression_type(&self) -> &str { - if self.compression_type_byte == 0 { - return "Uncompressed" - } else if self.compression_type_byte == 1 { - return "GZIP" - } else if self.compression_type_byte == 2 { - return "LZSS" - } else { - return "Unknown" - } - } -} - -static MAX_HEADER_SIZE: usize = 0x2000; - -pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result, Box> { - for key_hex in key_array { - let key_bytes = hex::decode(key_hex)?; - let key_array: [u8; 8] = key_bytes.as_slice().try_into()?; - let decrypted = decrypt_data(data, &key_array); - - if decrypted[magic_offset..].starts_with(expected_magic) { - return Ok(Some(key_array)); - } - } - Ok(None) -} - -pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result, Box> { - for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array { - let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?; - let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?; - let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?; - - let key_bytes = hex::decode(cust_key_hex)?; - let key_array: [u8; 8] = key_bytes.as_slice().try_into()?; - let decrypted = decrypt_data(&aes_decrypted, &key_array); - - if decrypted[magic_offset..].starts_with(expected_magic) { - return Ok(Some((aes_key, aes_iv, key_array))); - } - } - Ok(None) -} - -pub fn is_pana_dvd_file(file: &File) -> Result, Box> { +pub fn is_pana_dvd_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; let header = common::read_file(&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: &AppContext, ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let context = ctx.downcast::().expect("Missing context"); + 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); @@ -213,21 +96,19 @@ 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, &app_ctx.output_dir.join(format!("file_{}", i + 1)))?; } } - println!("\nExtraction finished!"); - Ok(()) } -fn extract_file(file_reader: &mut Cursor>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &str) -> Result<(), Box> { +fn extract_file(file_reader: &mut Cursor>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &PathBuf) -> Result<(), Box> { file_reader.seek(SeekFrom::Start(offset + base_offset))?; let enc_header = common::read_exact(file_reader, MAX_HEADER_SIZE)?; diff --git a/src/utils/pana_dvd_crypto.rs b/src/formats/pana_dvd/pana_dvd_crypto.rs similarity index 100% rename from src/utils/pana_dvd_crypto.rs rename to src/formats/pana_dvd/pana_dvd_crypto.rs diff --git a/src/formats/pfl_upg/include.rs b/src/formats/pfl_upg/include.rs new file mode 100644 index 0000000..233e540 --- /dev/null +++ b/src/formats/pfl_upg/include.rs @@ -0,0 +1,67 @@ +use crate::utils::common; +use binrw::BinRead; +use aes::Aes256; +use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}}; + +pub static AUTO_FWS: &[(&str, &str)] = &[ + ("Q5551", "q5551"), + ("Q5553", "q5551"), + ("Q554E", "q5551"), + ("Q554M", "q5551"), + ("QF1EU", "qf1eu"), + ("QF2EU", "qf1eu"), + ("Q591E", "q591e"), + ("Q522E", "q522e"), + ("Q582E", "q522e"), + ("Q5481", "q5481"), + ("Q5431", "q5431"), + ("Q5492", "q5492"), + ("S5551", "q5551"), //Sharp +]; + +type Aes256EcbDec = Decryptor; + +pub fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result, Box> { + let key_array: [u8; 32] = key.try_into()?; + + let mut decryptor = Aes256EcbDec::new(&key_array.into()); + let mut buffer = ciphertext.to_vec(); + + for chunk in buffer.chunks_exact_mut(16) { + let block: &mut [u8; 16] = chunk.try_into()?; + decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block)); + } + + Ok(buffer) +} + +#[derive(BinRead)] +pub struct Header { + _magic_bytes: [u8; 8], + pub header_size: u32, + pub data_size: u32, + _crc32: u32, + pub mask: u32, + _data_size_decompressed: u32, + _padding2: u32, + description_bytes: [u8; 512], +} +impl Header { + pub fn description(&self) -> String { + common::string_from_bytes(&self.description_bytes) + } +} + +#[derive(BinRead)] +pub struct FileHeader { + file_name_bytes: [u8; 60], + pub real_size: u32, + pub stored_size: u32, + pub header_size: u32, + pub attributes: [u8; 4], +} +impl FileHeader { + pub fn file_name(&self) -> String { + common::string_from_bytes(&self.file_name_bytes) + } +} \ No newline at end of file diff --git a/src/formats/pfl_upg.rs b/src/formats/pfl_upg/mod.rs similarity index 62% rename from src/formats/pfl_upg.rs rename to src/formats/pfl_upg/mod.rs index 2b5b886..df189ea 100644 --- a/src/formats/pfl_upg.rs +++ b/src/formats/pfl_upg/mod.rs @@ -1,90 +1,32 @@ -use std::fs::{File}; +mod include; +use std::any::Any; +use crate::AppContext; + use rsa::{RsaPublicKey, BigUint}; use hex::decode; use std::path::Path; use std::io::{Read, Cursor, Write}; use std::fs::{self, OpenOptions}; - -use aes::Aes256; -use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; use crate::keys; +use include::*; -#[derive(BinRead)] -struct Header { - #[br(count = 8)] _magic_bytes: Vec, - header_size: u32, - data_size: u32, - #[br(count = 4)] _crc32: Vec, - mask: u32, - _data_size_decompressed: u32, - _padding2: u32, - #[br(count = 512)] description_bytes: Vec, -} -impl Header { - fn description(&self) -> String { - common::string_from_bytes(&self.description_bytes) - } -} +pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct FileHeader { - #[br(count = 60)] file_name_bytes: Vec, - real_size: u32, - stored_size: u32, - header_size: u32, - #[br(count = 4)] attributes: Vec, -} -impl FileHeader { - fn file_name(&self) -> String { - common::string_from_bytes(&self.file_name_bytes) - } -} - -pub fn is_pfl_upg_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 8).expect("Failed to read from file."); + let header = common::read_file(&file, 0, 8)?; if header == b"2SWU3TXV" { - true + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -static AUTO_FWS: &[(&str, &str)] = &[ - ("Q5551", "q5551"), - ("Q5553", "q5551"), - ("Q554E", "q5551"), - ("Q554M", "q5551"), - ("QF1EU", "qf1eu"), - ("QF2EU", "qf1eu"), - ("Q591E", "q591e"), - ("Q522E", "q522e"), - ("Q582E", "q522e"), - ("Q5481", "q5481"), - ("Q5431", "q5431"), - ("Q5492", "q5492"), - ("S5551", "q5551"), //Sharp -]; +pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; -type Aes256EcbDec = Decryptor; - -fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result, Box> { - let key_array: [u8; 32] = key.try_into()?; - - let mut decryptor = Aes256EcbDec::new(&key_array.into()); - let mut buffer = ciphertext.to_vec(); - - for chunk in buffer.chunks_exact_mut(16) { - let block: &mut [u8; 16] = chunk.try_into()?; - decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block)); - } - - Ok(buffer) -} - -pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box> { let header: Header = file.read_le()?; let signature = common::read_exact(&mut file, 128)?; let _ = common::read_exact(&mut file, 32)?; //unknown @@ -110,8 +52,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box Result<(), Box u32 { + self.flags >> 20 + } + pub fn is_compressed(&self) -> bool { + (self.flags & 8) != 0 + } + pub fn is_blocked(&self) -> bool { + (self.flags & 0x800) != 0 + } + pub fn is_block_table(&self) -> bool { + (self.flags & 1) != 0 + } +} + +#[derive(BinRead, Clone)] +pub struct BlockEntry { + pub offset: u32, + pub size: u32, +} \ No newline at end of file diff --git a/src/formats/pup.rs b/src/formats/pup/mod.rs similarity index 71% rename from src/formats/pup.rs rename to src/formats/pup/mod.rs index e70400d..bf1d3ba 100644 --- a/src/formats/pup.rs +++ b/src/formats/pup/mod.rs @@ -1,67 +1,31 @@ +mod include; +use std::any::Any; +use crate::AppContext; + use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; -use binrw::{BinRead, BinReaderExt}; +use std::fs::{self, OpenOptions}; +use binrw::BinReaderExt; use std::io::{Write, Seek, SeekFrom}; use crate::utils::common; use crate::utils::compression::{decompress_zlib}; +use include::*; -#[derive(BinRead)] -struct Header { - #[br(count = 4)] _magic_bytes: Vec, - _unk1: u32, - _unk2: u16, - _flags: u8, - _unk3: u8, - _header_size: u16, - _hash_size: u16, - file_size: u64, - entry_count: u16, - _hash_count: u16, - _unk4: u32, -} +pub fn is_pup_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead, Clone)] -struct Entry { - flags: u32, - _unk1: u32, - offset: u64, - compressed_size: u64, - uncompressed_size: u64, -} -impl Entry { - fn id(&self) -> u32 { - self.flags >> 20 - } - fn is_compressed(&self) -> bool { - (self.flags & 8) != 0 - } - fn is_blocked(&self) -> bool { - (self.flags & 0x800) != 0 - } - fn is_block_table(&self) -> bool { - (self.flags & 1) != 0 - } -} - -#[derive(BinRead, Clone)] -struct BlockEntry { - offset: u32, - size: u32, -} - -pub fn is_pup_file(file: &File) -> bool { - 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"\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> { - let header: Header = file.read_le()?; +pub fn extract_pup(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let header: Header = file.read_le()?; println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count); @@ -132,14 +96,10 @@ pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box bool { + self.flags == 0x80 + } + pub fn type_string(&self) -> &str { + if self.image_type == 0xa { + return "initfs" + } else if self.image_type == 0x18 { + return "uImage" + } else if self.image_type == 0x3 { + return "loader" + } else if self.image_type == 0xd { + return "app_cramfs" + } else if self.image_type == 0x6 { + return "Customization Package" + } else if self.image_type == 0xe { + return "firmware_blob" + } else { + return "unknown" + } + } +} \ No newline at end of file diff --git a/src/formats/roku.rs b/src/formats/roku/mod.rs similarity index 62% rename from src/formats/roku.rs rename to src/formats/roku/mod.rs index 3021ac2..5459fc0 100644 --- a/src/formats/roku.rs +++ b/src/formats/roku/mod.rs @@ -1,72 +1,33 @@ -use std::fs::{self, File, OpenOptions}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::fs::{self, OpenOptions}; use std::path::Path; use std::io::{Write, Seek, Read, Cursor}; use tar::Archive; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use crate::utils::common; use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7}; +use include::*; -static FILE_KEY: [u8; 16] = [ - 0x2A, 0x54, 0xA5, 0x30, 0xE0, 0x09, 0xA3, 0xDC, - 0x03, 0xFB, 0xC3, 0x5E, 0x23, 0xA2, 0xC1, 0x0D, -]; +pub fn is_roku_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -static FILE_IV: [u8; 16] = [0x00; 16]; - -#[derive(BinRead)] -struct ImageHeader { - #[br(count = 8)] _empty: Vec, - #[br(count = 8)] _magic_bytes: Vec, //imgARMcC - #[br(count = 4)] _target_bytes: Vec, - #[br(count = 4)] _platform_id: Vec, - image_type: u32, - size1: u32, - _size2: u32, - data_start_offset: u32, - _data_link_address: u32, - _data_entry_point_offset: u32, - flags: u32, - _timestamp: u32, - #[br(count = 4)] _build_host: Vec, - #[br(count = 4)] _unk: Vec, - #[br(count = 192)] _rest_of_header: Vec, -} -impl ImageHeader { - fn is_encrypted(&self) -> bool { - self.flags == 0x80 - } - fn type_string(&self) -> &str { - if self.image_type == 0xa { - return "initfs" - } else if self.image_type == 0x18 { - return "uImage" - } else if self.image_type == 0x3 { - return "loader" - } else if self.image_type == 0xd { - return "app_cramfs" - } else if self.image_type == 0x6 { - return "Customization Package" - } else if self.image_type == 0xe { - return "firmware_blob" - } else { - return "unknown" - } - } -} - -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!"); + let header = common::read_file(&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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let mut encrypted_data = Vec::new(); file.read_to_end(&mut encrypted_data)?; @@ -109,7 +70,7 @@ pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box String { + common::string_from_bytes(&self.date_time_bytes) + } + pub fn buyer(&self) -> String { + common::string_from_bytes(&self.buyer_bytes) + } + pub fn model(&self) -> String { + common::string_from_bytes(&self.model_bytes) + } + pub fn region_info(&self) -> String { + common::string_from_bytes(&self.region_info_bytes) + } + pub fn is_dual_ruf(&self) -> bool { + if self.dual_ruf_flag == 0x44 {true} else {false} + } +} + +#[derive(BinRead)] +pub struct RufEntry { + _metadata: [u8; 32], + pub payload_type_bytes: u32, + pub size: u32, + _unk1: u32, + _unk2: [u8; 20], +} +impl RufEntry { + pub fn payload_type(&self) -> &str { + if self.payload_type_bytes == 1 { + return "squashfs" + } else if self.payload_type_bytes == 2 { + return "cfe" + } else if self.payload_type_bytes == 3 { + return "vmlinuz" + } else if self.payload_type_bytes == 4 { + return "loader" + } else if self.payload_type_bytes == 5 { + return "splash" + } else { + return "unknown" + } + } +} \ No newline at end of file diff --git a/src/formats/ruf.rs b/src/formats/ruf/mod.rs similarity index 50% rename from src/formats/ruf.rs rename to src/formats/ruf/mod.rs index 2d5b056..3419e86 100644 --- a/src/formats/ruf.rs +++ b/src/formats/ruf/mod.rs @@ -1,99 +1,45 @@ -use std::path::{Path}; +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::{Path, PathBuf}; use std::fs::{self, File, OpenOptions}; -use binrw::{BinRead, BinReaderExt}; +use binrw::BinReaderExt; use std::io::{Write, Seek, SeekFrom, Cursor}; use crate::utils::common; use crate::keys; use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; +use include::*; -#[derive(BinRead)] -struct RufHeader { - #[br(count = 6)] _magic_bytes: Vec, - #[br(count = 2)] _upgrade_type_bytes: Vec, - _unk1: u32, - #[br(count = 32)] date_time_bytes: Vec, - #[br(count = 8)] buyer_bytes: Vec, - #[br(count = 32)] model_bytes: Vec, - #[br(count = 32)] region_info_bytes: Vec, - #[br(count = 4)] version_bytes: Vec, - data_size: u32, - #[br(count = 20)] _unk2: Vec, - dual_ruf_flag: u32, - #[br(count = 44)] _unk3: Vec, - payload_count: u16, - _payload_entry_size: u16, - payloads_start_offset: u32, -} -impl RufHeader { - fn date_time(&self) -> String { - common::string_from_bytes(&self.date_time_bytes) - } - fn buyer(&self) -> String { - common::string_from_bytes(&self.buyer_bytes) - } - fn model(&self) -> String { - common::string_from_bytes(&self.model_bytes) - } - fn region_info(&self) -> String { - common::string_from_bytes(&self.region_info_bytes) - } - fn is_dual_ruf(&self) -> bool { - if self.dual_ruf_flag == 0x44 {true} else {false} - } -} +pub fn is_ruf_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; -#[derive(BinRead)] -struct RufEntry { - #[br(count = 32)] _metadata: Vec, - payload_type_bytes: u32, - size: u32, - _unk1: u32, - #[br(count = 20)] _unk2: Vec, -} -impl RufEntry { - fn payload_type(&self) -> &str { - if self.payload_type_bytes == 1 { - return "squashfs" - } else if self.payload_type_bytes == 2 { - return "cfe" - } else if self.payload_type_bytes == 3 { - return "vmlinuz" - } else if self.payload_type_bytes == 4 { - return "loader" - } else if self.payload_type_bytes == 5 { - return "splash" - } else { - return "unknown" - } - } -} - -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 + let header = common::read_file(&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: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected 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, &app_ctx.output_dir.join("RUF_1"), 0)?; println!("\nExtracting 2nd RUF...\n"); - actually_extract_ruf(&file, &format!("{}/RUF_2", output_folder), 41943088)?; + actually_extract_ruf(file, &app_ctx.output_dir.join("RUF_2"), 41943088)?; } else { - actually_extract_ruf(&file, &output_folder, 0)?; + actually_extract_ruf(file, &app_ctx.output_dir, 0)?; } - println!("\nExtraction finished!"); Ok(()) } -fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64) -> Result<(), Box> { +fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset: u64) -> Result<(), Box> { file.seek(SeekFrom::Start(start_offset))?; let header: RufHeader = file.read_be()?; @@ -139,8 +85,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64) println!("\nKey: {}", k); key_bytes = hex::decode(k)?.as_slice().try_into()?; } else { - println!("\nSorry, this firmware is not supported!"); - std::process::exit(1); + return Err("This firmware is not supported!".into()); } file.seek(SeekFrom::Start(start_offset + 2048))?; @@ -159,10 +104,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64) let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", entry.payload_type_bytes, entry.payload_type())); fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .open(output_path)?; + let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; out_file.write_all(&data)?; diff --git a/src/formats/rvp/include.rs b/src/formats/rvp/include.rs new file mode 100644 index 0000000..0429955 --- /dev/null +++ b/src/formats/rvp/include.rs @@ -0,0 +1,7 @@ +pub fn decrypt_xor(data: &[u8]) -> Vec { + let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA"; + data.iter() + .enumerate() + .map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()]) + .collect() +} \ No newline at end of file diff --git a/src/formats/rvp.rs b/src/formats/rvp/mod.rs similarity index 78% rename from src/formats/rvp.rs rename to src/formats/rvp/mod.rs index 9400fb7..1f896fc 100644 --- a/src/formats/rvp.rs +++ b/src/formats/rvp/mod.rs @@ -1,38 +1,43 @@ -use std::fs::File; +mod include; +use std::any::Any; +use crate::AppContext; + use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write, Read, Cursor, Seek}; use crate::utils::common; +use include::*; -fn decrypt_xor(data: &[u8]) -> Vec { - let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA"; - data.iter() - .enumerate() - .map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()]) - .collect() +pub struct RvpContext { + header_offset: u64, } -pub fn is_rvp_file(mut file: &File) -> bool { +pub fn is_rvp_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; //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; + return Ok(Some(Box::new(RvpContext {header_offset: 36}))) } //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 + + Ok(Some(Box::new(RvpContext {header_offset: 64}))) } -pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box> { +pub fn extract_rvp(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"); + + file.seek(std::io::SeekFrom::Start(ctx.header_offset))?; + 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.."); @@ -109,21 +114,18 @@ pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box Vec { + let key_bytes = key.as_bytes(); + data.iter() + .enumerate() + .map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()]) + .collect() +} \ No newline at end of file diff --git a/src/formats/samsung_old.rs b/src/formats/samsung_old/mod.rs similarity index 82% rename from src/formats/samsung_old.rs rename to src/formats/samsung_old/mod.rs index 9d130eb..8424f5d 100644 --- a/src/formats/samsung_old.rs +++ b/src/formats/samsung_old/mod.rs @@ -1,33 +1,32 @@ +mod include; +use std::any::Any; +use crate::AppContext; + use std::fs; -use std::path::{Path, PathBuf}; +use std::path::{Path}; use std::fs::{File, OpenOptions}; use std::io::{Write}; use hex::decode; use sha1::{Digest, Sha1}; +use md5; use crate::utils::common; use crate::keys; use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; +use include::decrypt_xor; -use md5; +pub fn is_samsung_old_dir(app_ctx: &AppContext) -> Result>, Box> { + let dir = match app_ctx.dir() {Some(d) => d, None => return Ok(None)}; -pub fn is_samsung_old_dir(path: &PathBuf) -> bool { - if Path::new(&path).join("image").is_dir() & Path::new(&path).join("image/info.txt").exists(){ - true + if Path::new(&dir).join("image").is_dir() & Path::new(&dir).join("image/info.txt").exists(){ + Ok(Some(Box::new(()))) } else { - false + Ok(None) } } -fn decrypt_xor(data: &[u8], key: &str) -> Vec { - let key_bytes = key.as_bytes(); - data.iter() - .enumerate() - .map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()]) - .collect() -} - -pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Box> { +pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let path = app_ctx.dir().ok_or("Extractor expected directory")?; let fw_info = fs::read_to_string(Path::new(&path).join("image/info.txt"))?; println!("Firmware info: {}", fw_info); @@ -46,8 +45,7 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo if let Some(p) = secret { println!("Secret: {}", p); } else { - println!("Sorry, this firmware is not supported!"); - std::process::exit(1); + return Err("This firmware is not supported!".into()); } for entry in fs::read_dir(image_path)? { @@ -114,9 +112,9 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo let xor_key = fw_info.split_whitespace().next().unwrap(); let out_data = decrypt_xor(&decrypted_data, xor_key); - let output_path = Path::new(&output_folder).join(filename.rsplit_once('.').map(|(left, _)| left).unwrap()); + let output_path = Path::new(&app_ctx.output_dir).join(filename.rsplit_once('.').map(|(left, _)| left).unwrap()); - fs::create_dir_all(&output_folder)?; + fs::create_dir_all(&app_ctx.output_dir)?; let mut out_file = OpenOptions::new() .write(true) .create(true) @@ -130,7 +128,5 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo } } - println!("\nExtraction finished!"); - Ok(()) } \ No newline at end of file diff --git a/src/formats/sddl_sec.rs b/src/formats/sddl_sec.rs deleted file mode 100644 index d08346a..0000000 --- a/src/formats/sddl_sec.rs +++ /dev/null @@ -1,364 +0,0 @@ -//sddl_dec 6.0 https://github.com/theubusu/sddl_dec - -use std::path::{Path, PathBuf}; -use std::fs::{self, File, OpenOptions}; -use std::io::{Cursor, Seek, SeekFrom, Write}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; -use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; -use crate::utils::compression::{decompress_zlib}; - -pub fn decipher(s: &[u8]) -> Vec { - let len_ = s.len(); - let mut v3: u32 = 904; - let mut out = s.to_vec(); - let mut cnt = 0; - - if len_ > 0 { - let true_len = if len_ >= 0x80 { - 128 - } else { - len_ - }; - - if true_len > 0 { - let mut i = 0; - let mut j: u8 = 0; - - while i < s.len() { - let iter_ = s[i]; - i += 1; - j = j.wrapping_add(1); - - let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff; - let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8; - v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff; - - if j == 0 { - v3 = 904; - } - - if cnt < out.len() { - out[cnt] = v12; - cnt += 1; - } - } - } - } - - out -} - -// -- STRUCTURES -- -// -- SECFILE -- - -pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44]; - -#[derive(Debug, BinRead)] -pub struct SecHeader { - pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? - key_id_str_bytes: [u8; 4], //"key_id", purpose unknown - grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file - prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files - _unused_or_reserved: [u8; 16], //not used, is zeros -} -impl SecHeader { - pub fn key_id(&self) -> u32 { - let string = common::string_from_bytes(&self.key_id_str_bytes); - string.parse().unwrap() - } - pub fn grp_num(&self) -> u32 { - let string = common::string_from_bytes(&self.grp_num_str_bytes); - string.parse().unwrap() - } - pub fn prg_num(&self) -> u32 { - let string = common::string_from_bytes(&self.prg_num_str_bytes); - string.parse().unwrap() - } -} - -pub static INFO_FILE_EXTENSION: &str = ".TXT"; - -#[derive(Debug, BinRead)] -pub struct FileHeader { - name_str_bytes: [u8; 12], - size_str_bytes: [u8; 12], -} -impl FileHeader { - pub fn name(&self) -> String { - common::string_from_bytes(&self.name_str_bytes) - } - pub fn size(&self) -> u64 { - let string = common::string_from_bytes(&self.size_str_bytes); - string.parse().unwrap() - } -} - -// -- MODULE -- -#[derive(Debug, BinRead)] -pub struct ModuleComHeader { //"com_header" - pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? - _outer_maker_id: u8, - _outer_model_id: u8, - _inner_maker_id: u8, - _reserve1: u8, - _reserve2: u32, - _reserve3: u32, - _start_version: [u8; 4], //the first version that can upgrade to the new version - _end_version: [u8; 4], //the last version that can upgrade to the new version - _new_version: [u8; 4], //the new version, as in the version of the data in this module - _reserve4: u16, - _module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1. -} - -#[derive(Debug, BinRead)] -pub struct ModuleHeader { //"header", appears after com_header - _module_id: u16, - module_atr: u8, - _target_id: u8, - pub cmp_size: u32, - _org_size: u32, - _crc_value: u32, -} -impl ModuleHeader { - pub fn is_ciphered(&self) -> bool { - (self.module_atr & 0x02) != 0 - } - pub fn is_compressed(&self) -> bool { - (self.module_atr & 0x01) != 0 - } -} - -#[derive(Debug, BinRead)] -pub struct ContentHeader { - _magic1: u8, //always 0x01? - _dest_offset: u32, - _source_offset: u32, - pub size: u32, - _magic2: u8, //always 0x21? -} -impl ContentHeader { - //these hacks are needed because for some reason older files have the first nibble of the offset set to D/C - //no idea why, but masking them off makes it works properly - pub fn dest_offset(&self) -> u32 { - if ((self._dest_offset >> 28) & 0xF) == 0xD { - self._dest_offset & 0x0FFFFFFF - } else { - self._dest_offset - } - } - pub fn source_offset(&self) -> u32 { - if ((self._source_offset >> 28) & 0xF) == 0xC { - self._source_offset & 0x0FFFFFFF - } else { - self._source_offset - } - } - pub fn has_subfile(&self) -> bool { - self.source_offset() == 0x10E - } -} - -// -- TDI -- -// Called SDIT.FDI in the secfile - -pub static TDI_FILENAME: &str = "SDIT.FDI"; -pub static SUPPORTED_TDI_VERSION: u16 = 2; - -#[derive(Debug, BinRead)] -pub struct TdiHead { - pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? - pub num_of_group: u8, - _reserve1: u8, - pub format_version: u16, //checks for "2" here -} - -#[derive(Debug, BinRead)] -pub struct TdiGroupHead { - pub group_id: u8, - pub num_of_target: u8, //logic checks that this is not more than 5 - _reserved: u16, -} - -#[derive(Debug, BinRead)] -pub struct TdiTgtInf { - _outer_maker_id: u8, - _outer_model_id: u8, - _inner_maker_id: u8, - _reserve3: u8, - _inner_model_id: [u8; 4], - _ext_model_id: [u8; 4], - pub _start_version: [u8; 4], //the first version that can upgrade to the new version - pub _end_version: [u8; 4], //the last version that can upgrade to the new version - pub new_version: [u8; 4], //the new version, as in the version of the data in this module - pub target_id: u8, - _num_of_compatible_target: u8, - pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI" - _unknown: [u8; 8], - module_name_bytes: [u8; 8], -} -impl TdiTgtInf { - pub fn module_name(&self) -> String { - common::string_from_bytes(&self.module_name_bytes) - } - pub fn version_string(&self) -> String { - format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3]) - } -} - -// -- dec key -- -static DEC_KEY: [u8; 16] = [ - 0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB, - 0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C, -]; - -static DEC_IV: [u8; 16] = [ - 0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54, - 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."); - let deciph_header = decipher(&header); - if deciph_header.starts_with(b"\x11\x22\x33\x44") { - true - } else { - false - } -} - -fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec), Box> { - let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?); - let file_header: FileHeader = hdr_reader.read_be()?; - let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?; - - Ok((file_header, file_data)) -} - -fn parse_tdi_to_modules(tdi_data: Vec) -> Result, Box> { - let mut tdi_reader = Cursor::new(tdi_data); - let tdi_header: TdiHead = tdi_reader.read_be()?; - if tdi_header.download_id != DOWNLOAD_ID { - return Err("Invalid TDI header!".into()); - } - if tdi_header.format_version != SUPPORTED_TDI_VERSION { - return Err(format!("Unsupported TDI format version {}! (The supported version is {})", tdi_header.format_version, SUPPORTED_TDI_VERSION).into()); - } - - println!("[TDI] Group count: {}", tdi_header.num_of_group); - let mut modules: Vec = Vec::new(); - - for _i in 0..tdi_header.num_of_group { - let group_head: TdiGroupHead = tdi_reader.read_be()?; - println!("[TDI] Group ID: {}, Target count: {}", group_head.group_id, group_head.num_of_target); - - for _i in 0..group_head.num_of_target { - let tgt_inf: TdiTgtInf = tdi_reader.read_be()?; - println!("[TDI] - {}, Target ID: {}, Segment count: {}, Version: {}", - tgt_inf.module_name(), tgt_inf.target_id, tgt_inf.num_of_txx, tgt_inf.version_string()); - - //push unique modules - if !modules.iter().any(|m| m.module_name() == tgt_inf.module_name()) { - modules.push(tgt_inf); - } - } - } - - Ok(modules) -} - -pub fn extract_sddl_sec(mut file: &File, output_folder: &str) -> Result<(), Box> { - file.seek(SeekFrom::Start(0))?; - let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?)); - let secfile_header: SecHeader = secfile_hdr_reader.read_be()?; - - println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num()); - fs::create_dir_all(&output_folder)?; - - let (tdi_file, tdi_data) = get_sec_file(&file)?; - println!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size()); - //if save_extra { //Save SDIT - // let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&output_folder).join(tdi_file.name()))?; - // out_file.write_all(&tdi_data)?; - //} - if tdi_file.name() != TDI_FILENAME { - return Err(format!("Invalid TDI filename {}!, expected: {}", tdi_file.name(), TDI_FILENAME).into()); - } - //parse TDI - let modules = parse_tdi_to_modules(tdi_data)?; - - //get info files, each info file belongs to its respecitve group in the TDI - for i in 0..secfile_header.grp_num() { - let (info_file, info_data) = get_sec_file(&file)?; - println!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size()); - if !info_file.name().ends_with(INFO_FILE_EXTENSION) { - return Err(format!("Info file {} does not have the expected extension {}!", info_file.name(), INFO_FILE_EXTENSION).into()); - } - //if save_extra { //Save info file - // let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&output_folder).join(info_file.name()))?; - // out_file.write_all(&info_data)?; - //} - //print info file - println!("{}", String::from_utf8_lossy(&info_data)); - } - - //parse module data - for (i, module) in modules.iter().enumerate(){ - println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}", - i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string()); - - for i in 0..module.num_of_txx { - let (module_file, module_data) = get_sec_file(&file)?; - if !module_file.name().starts_with(&module.module_name()) { - return Err(format!("Module file {} does not start with the module's name: {}!", module_file.name(), module.module_name()).into()); - } - println!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size()); - - let mut module_reader = Cursor::new(module_data); - let com_header: ModuleComHeader = module_reader.read_be()?; - if com_header.download_id != DOWNLOAD_ID { - return Err("Invalid module com_header!".into()); - } - - let module_header: ModuleHeader = module_reader.read_be()?; - let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?; - if module_header.is_ciphered() { - println!(" - Deciphering..."); - module_data = decipher(&module_data); - } - if module_header.is_compressed() { - println!(" - Decompressing..."); - module_data = decompress_zlib(&module_data)?; - } - - let mut content_reader = Cursor::new(module_data); - let content_header: ContentHeader = content_reader.read_be()?; - println!(" --> 0x{:X} @ 0x{:X}", content_header.size, content_header.dest_offset()); - - let output_path: PathBuf; - if content_header.has_subfile() { - let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?; - let sub_filename = common::string_from_bytes(&sub_filename_bytes); - println!(" --> {}", sub_filename); - - let sub_folder_path = Path::new(&output_folder).join(module.module_name()); - fs::create_dir_all(&sub_folder_path)?; - output_path = Path::new(&sub_folder_path).join(sub_filename); - - } else { - output_path = Path::new(&output_folder).join(format!("{}.bin", module.module_name())); - } - - let data = common::read_exact(&mut content_reader, content_header.size as usize)?; - let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?; - out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?; - out_file.write_all(&data)?; - - } - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/sddl_sec/include.rs b/src/formats/sddl_sec/include.rs new file mode 100644 index 0000000..6c29ade --- /dev/null +++ b/src/formats/sddl_sec/include.rs @@ -0,0 +1,212 @@ +//sddl_dec 6.0 https://github.com/theubusu/sddl_dec +use crate::utils::common; +use binrw::BinRead; + +pub fn decipher(s: &[u8]) -> Vec { + let len_ = s.len(); + let mut v3: u32 = 904; + let mut out = s.to_vec(); + let mut cnt = 0; + + if len_ > 0 { + let true_len = if len_ >= 0x80 { + 128 + } else { + len_ + }; + + if true_len > 0 { + let mut i = 0; + let mut j: u8 = 0; + + while i < s.len() { + let iter_ = s[i]; + i += 1; + j = j.wrapping_add(1); + + let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff; + let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8; + v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff; + + if j == 0 { + v3 = 904; + } + + if cnt < out.len() { + out[cnt] = v12; + cnt += 1; + } + } + } + } + + out +} + +// -- STRUCTURES -- +// -- SECFILE -- + +pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44]; + +#[derive(Debug, BinRead)] +pub struct SecHeader { + pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? + key_id_str_bytes: [u8; 4], //"key_id", purpose unknown + grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file + prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files + _unused_or_reserved: [u8; 16], //not used, is zeros +} +impl SecHeader { + pub fn key_id(&self) -> u32 { + let string = common::string_from_bytes(&self.key_id_str_bytes); + string.parse().unwrap() + } + pub fn grp_num(&self) -> u32 { + let string = common::string_from_bytes(&self.grp_num_str_bytes); + string.parse().unwrap() + } + pub fn prg_num(&self) -> u32 { + let string = common::string_from_bytes(&self.prg_num_str_bytes); + string.parse().unwrap() + } +} + +pub static INFO_FILE_EXTENSION: &str = ".TXT"; + +#[derive(Debug, BinRead)] +pub struct FileHeader { + name_str_bytes: [u8; 12], + size_str_bytes: [u8; 12], +} +impl FileHeader { + pub fn name(&self) -> String { + common::string_from_bytes(&self.name_str_bytes) + } + pub fn size(&self) -> u64 { + let string = common::string_from_bytes(&self.size_str_bytes); + string.parse().unwrap() + } +} + +// -- MODULE -- +#[derive(Debug, BinRead)] +pub struct ModuleComHeader { //"com_header" + pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? + _outer_maker_id: u8, + _outer_model_id: u8, + _inner_maker_id: u8, + _reserve1: u8, + _reserve2: u32, + _reserve3: u32, + _start_version: [u8; 4], //the first version that can upgrade to the new version + _end_version: [u8; 4], //the last version that can upgrade to the new version + _new_version: [u8; 4], //the new version, as in the version of the data in this module + _reserve4: u16, + _module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1. +} + +#[derive(Debug, BinRead)] +pub struct ModuleHeader { //"header", appears after com_header + _module_id: u16, + module_atr: u8, + _target_id: u8, + pub cmp_size: u32, + _org_size: u32, + _crc_value: u32, +} +impl ModuleHeader { + pub fn is_ciphered(&self) -> bool { + (self.module_atr & 0x02) != 0 + } + pub fn is_compressed(&self) -> bool { + (self.module_atr & 0x01) != 0 + } +} + +#[derive(Debug, BinRead)] +pub struct ContentHeader { + _magic1: u8, //always 0x01? + _dest_offset: u32, + _source_offset: u32, + pub size: u32, + _magic2: u8, //always 0x21? +} +impl ContentHeader { + //these hacks are needed because for some reason older files have the first nibble of the offset set to D/C + //no idea why, but masking them off makes it works properly + pub fn dest_offset(&self) -> u32 { + if ((self._dest_offset >> 28) & 0xF) == 0xD { + self._dest_offset & 0x0FFFFFFF + } else { + self._dest_offset + } + } + pub fn source_offset(&self) -> u32 { + if ((self._source_offset >> 28) & 0xF) == 0xC { + self._source_offset & 0x0FFFFFFF + } else { + self._source_offset + } + } + pub fn has_subfile(&self) -> bool { + self.source_offset() == 0x10E + } +} + +// -- TDI -- +// Called SDIT.FDI in the secfile + +pub static TDI_FILENAME: &str = "SDIT.FDI"; +pub static SUPPORTED_TDI_VERSION: u16 = 2; + +#[derive(Debug, BinRead)] +pub struct TdiHead { + pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic? + pub num_of_group: u8, + _reserve1: u8, + pub format_version: u16, //checks for "2" here +} + +#[derive(Debug, BinRead)] +pub struct TdiGroupHead { + pub group_id: u8, + pub num_of_target: u8, //logic checks that this is not more than 5 + _reserved: u16, +} + +#[derive(Debug, BinRead)] +pub struct TdiTgtInf { + _outer_maker_id: u8, + _outer_model_id: u8, + _inner_maker_id: u8, + _reserve3: u8, + _inner_model_id: [u8; 4], + _ext_model_id: [u8; 4], + pub _start_version: [u8; 4], //the first version that can upgrade to the new version + pub _end_version: [u8; 4], //the last version that can upgrade to the new version + pub new_version: [u8; 4], //the new version, as in the version of the data in this module + pub target_id: u8, + _num_of_compatible_target: u8, + pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI" + _unknown: [u8; 8], + module_name_bytes: [u8; 8], +} +impl TdiTgtInf { + pub fn module_name(&self) -> String { + common::string_from_bytes(&self.module_name_bytes) + } + pub fn version_string(&self) -> String { + format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3]) + } +} + +// -- dec key -- +pub static DEC_KEY: [u8; 16] = [ + 0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB, + 0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C, +]; + +pub static DEC_IV: [u8; 16] = [ + 0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54, + 0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66, +]; \ No newline at end of file diff --git a/src/formats/sddl_sec/mod.rs b/src/formats/sddl_sec/mod.rs new file mode 100644 index 0000000..76009b1 --- /dev/null +++ b/src/formats/sddl_sec/mod.rs @@ -0,0 +1,160 @@ +//sddl_dec 6.0 https://github.com/theubusu/sddl_dec +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::{Path, PathBuf}; +use std::fs::{self, File, OpenOptions}; +use std::io::{Cursor, Seek, SeekFrom, Write}; +use binrw::BinReaderExt; + +use crate::utils::common; +use crate::utils::aes::{decrypt_aes128_cbc_pcks7}; +use crate::utils::compression::{decompress_zlib}; +use include::*; + +pub fn is_sddl_sec_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + let header = common::read_file(&file, 0, 32).expect("Failed to read from file."); + let deciph_header = decipher(&header); + if deciph_header.starts_with(b"\x11\x22\x33\x44") { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec), Box> { + let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?); + let file_header: FileHeader = hdr_reader.read_be()?; + let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?; + + Ok((file_header, file_data)) +} + +fn parse_tdi_to_modules(tdi_data: Vec) -> Result, Box> { + let mut tdi_reader = Cursor::new(tdi_data); + let tdi_header: TdiHead = tdi_reader.read_be()?; + if tdi_header.download_id != DOWNLOAD_ID { + return Err("Invalid TDI header!".into()); + } + if tdi_header.format_version != SUPPORTED_TDI_VERSION { + return Err(format!("Unsupported TDI format version {}! (The supported version is {})", tdi_header.format_version, SUPPORTED_TDI_VERSION).into()); + } + + println!("[TDI] Group count: {}", tdi_header.num_of_group); + let mut modules: Vec = Vec::new(); + + for _i in 0..tdi_header.num_of_group { + let group_head: TdiGroupHead = tdi_reader.read_be()?; + println!("[TDI] Group ID: {}, Target count: {}", group_head.group_id, group_head.num_of_target); + + for _i in 0..group_head.num_of_target { + let tgt_inf: TdiTgtInf = tdi_reader.read_be()?; + println!("[TDI] - {}, Target ID: {}, Segment count: {}, Version: {}", + tgt_inf.module_name(), tgt_inf.target_id, tgt_inf.num_of_txx, tgt_inf.version_string()); + + //push unique modules + if !modules.iter().any(|m| m.module_name() == tgt_inf.module_name()) { + modules.push(tgt_inf); + } + } + } + + Ok(modules) +} + +pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + let save_extra = app_ctx.options.iter().any(|e| e == "sddl_sec:save_extra"); + + let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?)); + let secfile_header: SecHeader = secfile_hdr_reader.read_be()?; + + println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num()); + fs::create_dir_all(&app_ctx.output_dir)?; + + let (tdi_file, tdi_data) = get_sec_file(&file)?; + println!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size()); + if save_extra { //Save SDIT + let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&app_ctx.output_dir).join(tdi_file.name()))?; + out_file.write_all(&tdi_data)?; + } + if tdi_file.name() != TDI_FILENAME { + return Err(format!("Invalid TDI filename {}!, expected: {}", tdi_file.name(), TDI_FILENAME).into()); + } + //parse TDI + let modules = parse_tdi_to_modules(tdi_data)?; + + //get info files, each info file belongs to its respecitve group in the TDI + for i in 0..secfile_header.grp_num() { + let (info_file, info_data) = get_sec_file(&file)?; + println!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size()); + if !info_file.name().ends_with(INFO_FILE_EXTENSION) { + return Err(format!("Info file {} does not have the expected extension {}!", info_file.name(), INFO_FILE_EXTENSION).into()); + } + if save_extra { //Save info file + let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&app_ctx.output_dir).join(info_file.name()))?; + out_file.write_all(&info_data)?; + } + //print info file + println!("{}", String::from_utf8_lossy(&info_data)); + } + + //parse module data + for (i, module) in modules.iter().enumerate(){ + println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}", + i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string()); + + for i in 0..module.num_of_txx { + let (module_file, module_data) = get_sec_file(&file)?; + if !module_file.name().starts_with(&module.module_name()) { + return Err(format!("Module file {} does not start with the module's name: {}!", module_file.name(), module.module_name()).into()); + } + println!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size()); + + let mut module_reader = Cursor::new(module_data); + let com_header: ModuleComHeader = module_reader.read_be()?; + if com_header.download_id != DOWNLOAD_ID { + return Err("Invalid module com_header!".into()); + } + + let module_header: ModuleHeader = module_reader.read_be()?; + let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?; + if module_header.is_ciphered() { + println!(" - Deciphering..."); + module_data = decipher(&module_data); + } + if module_header.is_compressed() { + println!(" - Decompressing..."); + module_data = decompress_zlib(&module_data)?; + } + + let mut content_reader = Cursor::new(module_data); + let content_header: ContentHeader = content_reader.read_be()?; + println!(" --> 0x{:X} @ 0x{:X}", content_header.size, content_header.dest_offset()); + + let output_path: PathBuf; + if content_header.has_subfile() { + let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?; + let sub_filename = common::string_from_bytes(&sub_filename_bytes); + println!(" --> {}", sub_filename); + + let sub_folder_path = Path::new(&app_ctx.output_dir).join(module.module_name()); + fs::create_dir_all(&sub_folder_path)?; + output_path = Path::new(&sub_folder_path).join(sub_filename); + + } else { + output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", module.module_name())); + } + + let data = common::read_exact(&mut content_reader, content_header.size as usize)?; + let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?; + out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?; + out_file.write_all(&data)?; + + } + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/slp.rs b/src/formats/slp.rs deleted file mode 100644 index 9308f10..0000000 --- a/src/formats/slp.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::path::{Path}; -use std::fs::{self, File, OpenOptions}; -use std::io::{Write, Seek, SeekFrom}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; - -#[derive(BinRead)] -struct Header { - #[br(count = 4)] _magic_bytes: Vec, - #[br(count = 8)] version_bytes: Vec, - #[br(count = 16)] model_bytes: Vec, - #[br(count = 16)] firmware_bytes: Vec, - _unk: u32, - #[br(count = 8)] check: Vec, - #[br(count = 8)] _unk2: Vec, -} -impl Header { - fn version(&self) -> String { - common::string_from_bytes(&self.version_bytes) - } - fn model(&self) -> String { - common::string_from_bytes(&self.model_bytes) - } - fn firmware(&self) -> String { - common::string_from_bytes(&self.firmware_bytes) - } - fn is_new_type(&self) -> bool { - self.check == b"\x01VER_PR1" - } -} - -#[derive(BinRead)] -struct EntryOld { - size: u32, - _unk: u32, - offset: u32, - _unk2: u32, -} - -#[derive(BinRead)] -struct EntryNew { - size: u32, - _unk: u32, - offset: u32, - #[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."); - if header == b"SLP\x00" { - true - } else { - false - } -} - -pub fn extract_slp(mut file: &File, output_folder: &str) -> Result<(), Box> { - let header: Header = file.read_le()?; - - println!("File info:\nModel: {}\nVersion: {}\nFirmware: {}\nNew type: {}\n", - header.model(), header.version(), header.firmware(), header.is_new_type()); - - let mut first_entry_offset = 0; - let mut entries: Vec = Vec::new(); - - for i in 0..100 { - if (i != 0) && (file.stream_position()? >= first_entry_offset) { - break - } - let offset; - let size; - if header.is_new_type() { - let entry: EntryNew = file.read_le()?; - offset = entry.offset; - size = entry.size; - } else { - let entry: EntryOld = file.read_le()?; - offset = entry.offset; - size = entry.size; - } - if i == 0 { - first_entry_offset = offset as u64; - } - println!("{}. Offset: {}, Size: {}", i + 1, offset, size); - entries.push(EntryOld {size: size, _unk: 0, offset: offset, _unk2: 0}); - } - - let mut i = 1; - for entry in &entries { - println!("\n({}/{}) - Offset: {}, Size: {}", i, &entries.len(), entry.offset, entry.size); - file.seek(SeekFrom::Start(entry.offset.into()))?; - let data = common::read_exact(&mut file, entry.size as usize)?; - - let output_path = Path::new(&output_folder).join(format!("{}.bin", i)); - - fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .write(true) - .create(true) - .open(output_path)?; - - out_file.write_all(&data)?; - - println!("- Saved file!"); - - i += 1; - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/slp/include.rs b/src/formats/slp/include.rs new file mode 100644 index 0000000..cc31b6d --- /dev/null +++ b/src/formats/slp/include.rs @@ -0,0 +1,43 @@ +use crate::utils::common; +use binrw::BinRead; + +#[derive(BinRead)] +pub struct Header { + _magic_bytes: [u8; 4], + version_bytes: [u8; 8], + model_bytes: [u8; 16], + firmware_bytes: [u8; 16], + _unk: u32, + check: [u8; 8], + _unk2: [u8; 8], +} +impl Header { + pub fn version(&self) -> String { + common::string_from_bytes(&self.version_bytes) + } + pub fn model(&self) -> String { + common::string_from_bytes(&self.model_bytes) + } + pub fn firmware(&self) -> String { + common::string_from_bytes(&self.firmware_bytes) + } + pub fn is_new_type(&self) -> bool { + &self.check == b"\x01VER_PR1" + } +} + +#[derive(BinRead)] +pub struct EntryOld { + pub size: u32, + pub _unk: u32, + pub offset: u32, + pub _unk2: u32, +} + +#[derive(BinRead)] +pub struct EntryNew { + pub size: u32, + _unk: u32, + pub offset: u32, + _unk2: [u8; 12], +} \ No newline at end of file diff --git a/src/formats/slp/mod.rs b/src/formats/slp/mod.rs new file mode 100644 index 0000000..6b3813b --- /dev/null +++ b/src/formats/slp/mod.rs @@ -0,0 +1,74 @@ +mod include; +use std::any::Any; +use crate::AppContext; + +use std::path::{Path}; +use std::fs::{self, OpenOptions}; +use std::io::{Write, Seek, SeekFrom}; +use binrw::BinReaderExt; + +use crate::utils::common; +use include::*; + +pub fn is_slp_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&file, 0, 4)?; + if header == b"SLP\x00" { + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_slp(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let header: Header = file.read_le()?; + println!("File info:\nModel: {}\nVersion: {}\nFirmware: {}\nNew type: {}\n", + header.model(), header.version(), header.firmware(), header.is_new_type()); + + let mut first_entry_offset = 0; + let mut entries: Vec = Vec::new(); + + for i in 0..100 { + if (i != 0) && (file.stream_position()? >= first_entry_offset) { + break + } + let offset; + let size; + if header.is_new_type() { + let entry: EntryNew = file.read_le()?; + offset = entry.offset; + size = entry.size; + } else { + let entry: EntryOld = file.read_le()?; + offset = entry.offset; + size = entry.size; + } + if i == 0 { + first_entry_offset = offset as u64; + } + println!("{}. Offset: {}, Size: {}", i + 1, offset, size); + entries.push(EntryOld {size: size, _unk: 0, offset: offset, _unk2: 0}); + } + + let mut i = 1; + for entry in &entries { + println!("\n({}/{}) - Offset: {}, Size: {}", i, &entries.len(), entry.offset, entry.size); + file.seek(SeekFrom::Start(entry.offset.into()))?; + let data = common::read_exact(&mut file, entry.size as usize)?; + + let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", i)); + + 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)?; + + println!("- Saved file!"); + + i += 1; + } + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/sony_bdp.rs b/src/formats/sony_bdp.rs deleted file mode 100644 index e3e5033..0000000 --- a/src/formats/sony_bdp.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::fs::File; -use std::path::{Path, PathBuf}; -use std::fs::{self, OpenOptions}; -use std::io::{Cursor, Write}; -use binrw::{BinRead, BinReaderExt}; - -use crate::utils::common; -use crate::formats; - -//thx sony -static HEX_SUBSTITUTION: [u8; 256] = [ - 0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30, - 0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39, - 0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5, - 0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE, - 0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F, - 0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48, - 0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2, - 0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3, -]; - -fn hex_substitute(data: &[u8]) -> Vec { - data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect() -} - -#[derive(BinRead)] -struct Header { - #[br(count = 8)] firmware_name_bytes: Vec, - #[br(count = 8)] _unk_version_bytes: Vec, - #[br(count = 16)] firmware_version_bytes: Vec, - #[br(count = 16)] date_bytes: Vec, - #[br(count = 8)] _unk2_version_bytes: Vec, - #[br(count = 8)] _unk3_version_bytes: Vec, - #[br(count = 16)] _unk4_version_bytes: Vec, - _unk: u32, - _checksum: u64, - file_size: u32, - #[br(count = 16)] _unk5_version_bytes: Vec, - #[br(count = 16)] _unk6_version_bytes: Vec, - #[br(count = 32)] _unk2: Vec, -} -impl Header { - fn firmware_name(&self) -> String { - common::string_from_bytes(&self.firmware_name_bytes) - } - fn firmware_version(&self) -> String { - common::string_from_bytes(&self.firmware_version_bytes) - } - fn date(&self) -> String { - common::string_from_bytes(&self.date_bytes) - } -} - -#[derive(BinRead)] -struct Entry { - offset: u32, - 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."); - if header == b"\x01\x73\xEC\xC9" || header == b"\x01\x73\xEC\x1F" || header == b"\xEC\x7D\xB0\xB0" { //MSB1x, MSB0x, BDPPxx - true - } else { - false - } -} - -pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box> { - let obf_header = common::read_exact(&mut file, 300)?; - let header = hex_substitute(&obf_header); - let mut hdr_reader = Cursor::new(header); - let hdr: Header = hdr_reader.read_le()?; - - println!("File info:\nFirmware: {}\nVersion: {}\nDate: {}\nFile size: {}", - hdr.firmware_name(), hdr.firmware_version(), hdr.date(), hdr.file_size); - - let mut last_file_path: Option = None; - let mut first_entry_offset = 0; - let mut i = 1; - loop { - if (i != 1) && (hdr_reader.position() >= first_entry_offset) { - break - } - - let entry: Entry = hdr_reader.read_le()?; - if entry.size == 0 { - continue - } - - println!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size); - if i == 1 { - first_entry_offset = entry.offset as u64; - } - - 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)); - last_file_path = Some(output_path.clone()); - - fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?; - out_file.write_all(&data)?; - - println!("- Saved file!"); - i += 1; - } - - //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)? { - 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)?; - } else { - println!("- Not an MTK BDP file."); - } - } - - println!("\nExtraction finished!"); - - Ok(()) -} \ No newline at end of file diff --git a/src/formats/sony_bdp/include.rs b/src/formats/sony_bdp/include.rs new file mode 100644 index 0000000..1859f1d --- /dev/null +++ b/src/formats/sony_bdp/include.rs @@ -0,0 +1,52 @@ +use crate::utils::common; +use binrw::BinRead; + +//thx sony +static HEX_SUBSTITUTION: [u8; 256] = [ + 0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30, + 0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39, + 0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5, + 0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE, + 0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F, + 0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48, + 0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2, + 0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3, +]; + +pub fn hex_substitute(data: &[u8]) -> Vec { + data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect() +} + +#[derive(BinRead)] +pub struct Header { + firmware_name_bytes: [u8; 8], + _unk_version_bytes: [u8; 8], + firmware_version_bytes: [u8; 16], + date_bytes: [u8; 16], + _unk2_version_bytes: [u8; 8], + _unk3_version_bytes: [u8; 8], + _unk4_version_bytes: [u8; 16], + _unk: u32, + _checksum: u64, + pub file_size: u32, + _unk5_version_bytes: [u8; 16], + _unk6_version_bytes: [u8; 16], + _unk2: [u8; 32], +} +impl Header { + pub fn firmware_name(&self) -> String { + common::string_from_bytes(&self.firmware_name_bytes) + } + pub fn firmware_version(&self) -> String { + common::string_from_bytes(&self.firmware_version_bytes) + } + pub fn date(&self) -> String { + common::string_from_bytes(&self.date_bytes) + } +} + +#[derive(BinRead)] +pub struct Entry { + pub offset: u32, + pub size: u32, +} \ No newline at end of file diff --git a/src/formats/sony_bdp/mod.rs b/src/formats/sony_bdp/mod.rs new file mode 100644 index 0000000..646c206 --- /dev/null +++ b/src/formats/sony_bdp/mod.rs @@ -0,0 +1,89 @@ +mod include; +use std::any::Any; +use crate::{InputTarget, AppContext}; + +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::fs::{self, OpenOptions}; +use std::io::{Cursor, Write}; +use binrw::BinReaderExt; + +use crate::utils::common; +use crate::formats; +use include::*; + +pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result>, Box> { + let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)}; + + let header = common::read_file(&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 + Ok(Some(Box::new(()))) + } else { + Ok(None) + } +} + +pub fn extract_sony_bdp(app_ctx: &AppContext, _ctx: Box) -> Result<(), Box> { + let mut file = app_ctx.file().ok_or("Extractor expected file")?; + + let obf_header = common::read_exact(&mut file, 300)?; + let header = hex_substitute(&obf_header); + let mut hdr_reader = Cursor::new(header); + let hdr: Header = hdr_reader.read_le()?; + + println!("File info:\nFirmware: {}\nVersion: {}\nDate: {}\nFile size: {}", + hdr.firmware_name(), hdr.firmware_version(), hdr.date(), hdr.file_size); + + let mut last_file_path: Option = None; + let mut first_entry_offset = 0; + let mut i = 1; + loop { + if (i != 1) && (hdr_reader.position() >= first_entry_offset) { + break + } + + let entry: Entry = hdr_reader.read_le()?; + if entry.size == 0 { + continue + } + + println!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size); + if i == 1 { + first_entry_offset = entry.offset as u64; + } + + 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(&app_ctx.output_dir).join(format!("{}.bin", i)); + last_file_path = Some(output_path.clone()); + + 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)?; + + println!("- Saved file!"); + i += 1; + } + + //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())?; + let mtk_extraction_path = app_ctx.output_dir.join(format!("{}", i + 1)); + + //this is getting stupid... + let ctx: AppContext = AppContext { input: InputTarget::File(last_file), output_dir: mtk_extraction_path, options: app_ctx.options.clone() }; + + if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&ctx)? { + println!("- MTK BDP file detected!\n"); + + formats::mtk_bdp::extract_mtk_bdp(&ctx, result)?; + } else { + println!("- Not an MTK BDP file."); + } + } + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5bf66f2..6247135 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,165 +4,112 @@ mod utils; use clap::Parser; use std::path::{PathBuf}; -use std::io::{self}; +use std::io::{self, Seek, SeekFrom}; use std::fs::{self, File}; +use crate::formats::{Format, get_registry}; #[derive(Parser, Debug)] struct Args { input_target: String, - output_folder: Option, + output_directory: Option, + + ///format specific options + #[arg(short, long)] + options: Vec, +} + +pub enum InputTarget { + File(File), + Directory(PathBuf), +} + +pub struct AppContext { + pub input: InputTarget, + pub output_dir: PathBuf, + pub options: Vec, +} +impl AppContext { + pub fn file(&self) -> Option<&File> { + match &self.input { + InputTarget::File(f) => Some(f), + _ => None, + } + } + + pub fn dir(&self) -> Option<&PathBuf> { + match &self.input { + InputTarget::Directory(p) => Some(p), + _ => None, + } + } } fn main() -> Result<(), Box> { println!("unixtract Firmware extractor"); let args = Args::parse(); - let target_path = args.input_target; - println!("Input target: {}", target_path); - let path = PathBuf::from(target_path); - - let output_path = if args.output_folder.is_some() { - args.output_folder.unwrap() + let target_path_str = args.input_target; + println!("Input target: {}", target_path_str); + let target_path = PathBuf::from(&target_path_str); + + let output_path_str = if args.output_directory.is_some() { + args.output_directory.unwrap() } else { - format!("_{}", path.file_name().and_then(|s| s.to_str()).unwrap()) + format!("_{}", target_path.file_name().and_then(|s| s.to_str()).unwrap()) }; - println!("Output folder: {}\n", output_path); + println!("Output directory: {}", output_path_str); + let output_directory_path = PathBuf::from(&output_path_str); - let output_folder_path = PathBuf::from(&output_path); - if output_folder_path.exists() { - if output_folder_path.is_dir() { - let is_empty = fs::read_dir(&output_folder_path)?.next().is_none(); + if output_directory_path.exists() { + if output_directory_path.is_dir() { + let is_empty = fs::read_dir(&output_directory_path)?.next().is_none(); if !is_empty { - println!("Warning: Output folder already exists and is NOT empty! Files may be overwritten!"); + println!("\nWarning: Output folder already exists and is NOT empty! Files may be overwritten!"); println!("Press Enter if you want to continue..."); io::stdin().read_line(&mut String::new())?; } } } - - 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!"); - } + + let app_ctx; + + if target_path.is_file() { + let file = File::open(&target_path)?; + app_ctx = AppContext { + input: InputTarget::File(file), + output_dir: output_directory_path, + options: args.options, + }; + } else if target_path.is_dir() { + app_ctx = AppContext { + input: InputTarget::Directory(target_path), + output_dir: output_directory_path, + options: args.options, + }; } 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!"); + return Err("Invalid input path!".into()); + } + + let formats: Vec = get_registry(); + println!("Loaded {} formats!", formats.len()); + + for format in formats { + if let Some(ctx) = (format.detector_func)(&app_ctx)? { + println!("\n{} detected!", format.name); + + //reset seek of the file if present + if let Some(mut file) = app_ctx.file() { + file.seek(SeekFrom::Start(0))?; + } + + (format.extractor_func)(&app_ctx, ctx)?; + + //extractor returned with no error + println!("\nExtraction finished! Saved extracted files to {}", output_path_str); + return Ok(()); } } + println!("\nInput format not recognized!"); Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index b155bb2..5eec0cd 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,14 +1,5 @@ pub mod common; pub mod aes; -pub mod pana_dvd_crypto; -pub mod lzss; pub mod lzop; pub mod sparse; -pub mod compression; -pub mod android_ota_update_metadata; -pub mod mtk_crypto; -pub mod lzhs; -pub mod huffman_tables; -pub mod msd_ouith_parser_old; -pub mod msd_ouith_parser_tizen_1_8; -pub mod msd_ouith_parser_tizen_1_9; \ No newline at end of file +pub mod compression; \ No newline at end of file