diff --git a/Cargo.lock b/Cargo.lock index 64aea39..284b756 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "autocfg" version = "1.5.0" @@ -81,6 +87,30 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "binrw" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81419ff39e6ed10a92a7f125290859776ced35d9a08a665ae40b23e7ca702f30" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "376404e55ec40d0d6f8b4b7df3f87b87954bd987f0cf9a7207ea3b6ea5c9add4" +dependencies = [ + "either", + "owo-colors", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -99,6 +129,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + [[package]] name = "byteorder" version = "1.5.0" @@ -266,6 +302,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "find-msvc-tools" version = "0.1.1" @@ -449,6 +491,12 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -644,6 +692,7 @@ name = "unixtract" version = "0.1.0" dependencies = [ "aes", + "binrw", "cbc", "clap", "ecb", diff --git a/Cargo.toml b/Cargo.toml index 0894dd4..00a0390 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,6 @@ sha1 = "0.10" flate2 = "1.0" rsa = { version = "0.9", features = ["hazmat"] } hex = "0.4" -ecb = "0.1" \ No newline at end of file +ecb = "0.1" + +binrw = "0.15" \ No newline at end of file diff --git a/TODO.md b/TODO.md index 6a435b8..81240f3 100644 --- a/TODO.md +++ b/TODO.md @@ -9,4 +9,6 @@ - Samsung MSD11(Tizen 2016+) - Complete - Panasonic SDDL.SEC(2011+) - Complete - LG Epk2 (Crypted and plain header)- 100% complete (unless unencrypted versions exist >) -- LG epk3 - Probably fine??? but needs more testing (and more stolen keys) \ No newline at end of file +- LG epk3 - Probably fine??? but needs more testing (and more stolen keys) + +Todo: use STRUCTS on everything \ No newline at end of file diff --git a/src/formats/novatek.rs b/src/formats/novatek.rs index 6dd1edc..038d4ff 100644 --- a/src/formats/novatek.rs +++ b/src/formats/novatek.rs @@ -3,8 +3,31 @@ use std::path::Path; use std::fs::{self, OpenOptions}; use std::io::{Write, Seek}; +use binrw::{BinRead, BinReaderExt}; + use crate::common; +#[derive(Debug, BinRead)] +#[br(little)] +struct Header { + #[br(count = 4)] _magic_bytes: Vec, + #[br(count = 4)] _flags: Vec, + _header_size: u32, + #[br(count = 40)] _unknown1: Vec, + part_count: u32, + _first_part_offset: u32, + #[br(count = 116)] _unknown2: Vec, +} + +#[derive(Debug, BinRead)] +#[br(little)] +struct PartEntry { + #[br(count = 16)] _unknown: Vec, + index: u32, + size: u32, + offset: u32, +} + 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" { @@ -15,37 +38,18 @@ pub fn is_novatek_file(file: &File) -> bool { } pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box> { - let _magic = common::read_exact(&mut file, 4)?; //NFWB magic - let _flags = common::read_exact(&mut file, 4)?; - let _header_size = common::read_exact(&mut file, 4)?; - let _ = common::read_exact(&mut file, 40)?; //unknown + let header: Header = file.read_le()?; - let part_count_bytes = common::read_exact(&mut file, 4)?; - let part_count = u32::from_le_bytes(part_count_bytes.try_into().unwrap()); + println!("\nPart count: {}", header.part_count); - let _first_part_offset = common::read_exact(&mut file, 4)?; - - println!("\nPart count: {}", part_count); - - let _ = common::read_exact(&mut file, 116)?; - - for i in 0..part_count { - let _ = common::read_exact(&mut file, 16)?; //unknown - - let index_bytes = common::read_exact(&mut file, 4)?; - let index = u32::from_le_bytes(index_bytes.try_into().unwrap()); - - let size_bytes = common::read_exact(&mut file, 4)?; - let size = u32::from_le_bytes(size_bytes.try_into().unwrap()); - - let offset_bytes = common::read_exact(&mut file, 4)?; - let offset = u32::from_le_bytes(offset_bytes.try_into().unwrap()); + for i in 0..header.part_count { + let part: PartEntry = file.read_le()?; let current_pos = file.stream_position()?; - let data = common::read_file(&file, offset as u64, size as usize)?; + let data = common::read_file(&file, part.offset as u64, part.size as usize)?; - println!("\nPart {}: index: {}, size: {}, offset: {}", i + 1, index, size, offset); + println!("\nPart {}: index: {}, size: {}, offset: {}", i + 1, part.index, part.size, part.offset); let output_path = Path::new(&output_folder).join(format!("part_{}.bin", i + 1)); diff --git a/src/formats/pfl_upg.rs b/src/formats/pfl_upg.rs index b43d232..6a48c59 100644 --- a/src/formats/pfl_upg.rs +++ b/src/formats/pfl_upg.rs @@ -7,10 +7,46 @@ use std::fs::{self, OpenOptions}; use aes::Aes256; use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}}; +use binrw::{BinRead, BinReaderExt}; use crate::common; use crate::keys; +#[derive(Debug, BinRead)] +#[br(little)] +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) + } +} + +#[derive(Debug, BinRead)] +#[br(little)] +struct FileHeader { + #[br(count = 60)] file_name_bytes: Vec, + real_size: u32, + stored_size: u32, + _header_size: u32, + _attributes: u32, +} + +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."); if header == b"2SWU3TXV" { @@ -32,6 +68,7 @@ static AUTO_FWS: &[(&str, &str)] = &[ ("Q582E", "q522e"), ("Q5481", "q5481"), ("Q5431", "q5431"), + ("S5551", "q5551"), //Sharp ]; type Aes256EcbDec = Decryptor; @@ -51,39 +88,19 @@ fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result, Box Result<(), Box> { - let _ = common::read_exact(&mut file, 8)?; //2SWU3TXV magic - - let header_size_bytes = common::read_exact(&mut file, 4)?; - let header_size = u32::from_le_bytes(header_size_bytes.try_into().unwrap()); - - let data_size_bytes = common::read_exact(&mut file, 4)?; - let data_size = u32::from_le_bytes(data_size_bytes.try_into().unwrap()); - - let _crc32 = common::read_exact(&mut file, 4)?; - - let mask_bytes = common::read_exact(&mut file, 4)?; - let mask = u32::from_le_bytes(mask_bytes.try_into().unwrap()); - - let _data_size_decompressed = common::read_exact(&mut file, 4)?; - - let _padding2 = common::read_exact(&mut file, 4)?; - - let description_bytes = common::read_exact(&mut file, 512)?; - let description = common::string_from_bytes(&description_bytes); - + let header: Header = file.read_le()?; let signature = common::read_exact(&mut file, 128)?; - let _ = common::read_exact(&mut file, 32)?; //unknown - let version_bytes = common::read_exact(&mut file, header_size as usize - 704)?; + let version_bytes = common::read_exact(&mut file, header.header_size as usize - 704)?; let version = common::string_from_bytes(&version_bytes); println!("\nVersion: {}", version); - println!("Description: \n{}", description); - println!("Data size: {}", data_size); + println!("Description: \n{}", header.description()); + println!("Data size: {}", header.data_size); let decrypted_data; - if (mask & 0x2000_0000) != 0 { + if (header.mask & 0x2000_0000) != 0 { println!("File is encrypted."); let mut key = None; let mut n_hex = None; @@ -122,33 +139,24 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box, + _unknown1: u32, + size: u32, + _unknown2: u32, + #[br(count = 16)] _checksum: Vec, + #[br(count = 16)] name_bytes: Vec, + #[br(count = 64)] dest_dev_bytes: Vec, + #[br(count = 16)] comp_type_bytes: Vec, + #[br(count = 1032)] _comment: Vec, +} + +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_tpv_timg_file(file: &File) -> bool { let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); if header == b"TIMG" { @@ -28,51 +55,27 @@ pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box< let _timg = common::read_exact(&mut file, 288)?; //TIMG magic + header loop { - //PIMG - let mut pimg = [0u8; 4]; - if file.read_exact(&mut pimg).is_err() { - break; //EOF - } else { - assert!(&pimg == b"PIMG", "Invalid PIMG section!"); - } + let pimg = match file.read_le::() { + Ok(val) => val, + Err(_) => break, // EOF + }; + let data = common::read_exact(&mut file, pimg.size as usize)?; - let _ = common::read_exact(&mut file, 4)?; //4 bytes 00 - - let size_bytes = common::read_exact(&mut file, 4)?; - let size = u32::from_le_bytes(size_bytes.try_into().unwrap()); - - let _ = common::read_exact(&mut file, 4)?; //4 bytes 00 - - let _checksum = common::read_exact(&mut file, 16)?; //16 bytes checksum? or maybe signature - - let name_bytes = common::read_exact(&mut file, 16)?; - let name = common::string_from_bytes(&name_bytes); - - let dev_bytes = common::read_exact(&mut file, 64)?; - let dev = common::string_from_bytes(&dev_bytes); - - let comp_bytes = common::read_exact(&mut file, 16)?; - let comp_type = common::string_from_bytes(&comp_bytes); - - let _ = common::read_exact(&mut file, 1032)?; //1032 bytes maybe comment? skip this - - let data = common::read_exact(&mut file, size as usize)?; - - println!("\nPIMG: Name: {}, Size: {}, Dest: {}, Compression: {}", name, size, dev, comp_type); + println!("\nPIMG: Name: {}, Size: {}, Dest: {}, Compression: {}", pimg.name(), pimg.size, pimg.dest_dev(), pimg.comp_type()); let out_data; - if comp_type == "gzip" { + if pimg.comp_type() == "gzip" { println!("- Decompressing gzip..."); out_data = decompress_gzip(&data)?; - } else if comp_type == "none" { + } else if pimg.comp_type() == "none" { out_data = data; } else { println!("- Warning: unsupported compression type!"); out_data = data; } - let output_path = Path::new(&output_folder).join(name + ".bin"); + let output_path = Path::new(&output_folder).join(pimg.name() + ".bin"); fs::create_dir_all(&output_folder)?; let mut out_file = OpenOptions::new() @@ -88,5 +91,4 @@ pub fn extract_tpv_timg(mut file: &File, output_folder: &str) -> Result<(), Box< println!("\nExtraction finished!"); Ok(()) -} - +} \ No newline at end of file