From 060d808b441f71adb28673ef3ebbd45e4a8f4391 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Sat, 24 Jan 2026 16:36:20 +0100 Subject: [PATCH] Overhaul epk(epk1,epk2,epk3), add epk2b type extractor --- src/formats.rs | 1 + src/formats/epk.rs | 5 +- src/formats/epk1.rs | 79 +++++++++++++-------------- src/formats/epk2.rs | 100 ++++++++++++++++++++--------------- src/formats/epk2b.rs | 123 +++++++++++++++++++++++++++++++++++++++++++ src/formats/epk3.rs | 51 +++++++++++++----- src/keys.rs | 66 +++++++++++------------ src/main.rs | 12 +++-- 8 files changed, 300 insertions(+), 137 deletions(-) create mode 100644 src/formats/epk2b.rs diff --git a/src/formats.rs b/src/formats.rs index 9fce3b9..c3dd604 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -24,6 +24,7 @@ pub mod msd11; pub mod epk; pub mod epk1; pub mod epk2; +pub mod epk2b; pub mod epk3; pub mod mtk_pkg; diff --git a/src/formats/epk.rs b/src/formats/epk.rs index 888083e..45283c1 100644 --- a/src/formats/epk.rs +++ b/src/formats/epk.rs @@ -43,9 +43,12 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool { } pub fn extract_epk(file: &File, output_folder: &str) -> Result<(), Box> { - let versions = common::read_file(&file, 1712, 36)?; let epk_version = check_epk_version(&versions); + + 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()) { println!("EPK2 detected!\n"); diff --git a/src/formats/epk1.rs b/src/formats/epk1.rs index 02a1163..38ec105 100644 --- a/src/formats/epk1.rs +++ b/src/formats/epk1.rs @@ -13,17 +13,12 @@ struct CommonHeader { pak_count: u32, } -#[derive(BinRead)] -struct Pak { - offset : u32, - size : u32, -} - #[derive(BinRead)] struct PakHeader { #[br(count = 4)] pak_name_bytes: Vec, - stored_size: u32, - #[br(count = 15)] platform_id_bytes: Vec, + image_size: u32, + #[br(count = 64)] platform_id_bytes: Vec, + #[br(count = 56)] _reserved: Vec, } impl PakHeader { fn pak_name(&self) -> String { @@ -34,9 +29,16 @@ impl PakHeader { } } +#[derive(BinRead)] +struct Pak { + offset : u32, + size : u32, +} + pub fn is_epk1_file(file: &File) -> bool { - let header = common::read_file(&file, 0, 4).expect("Failed to read from file."); - if header == b"epak" { + 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."); + if epak_magic == b"epak" && epk2_magic != b"EPK2" { true } else { false @@ -52,13 +54,11 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box 256 { println!("\nBig endian EPK1 detected."); epk1_type = "be"; - } else if init_pak_count < 21 { + } else if init_pak_count < 33 { println!("\nLittle endian EPK1 detected."); epk1_type = "le"; } else { - //println!("\nEPK1(new) detected."); - //epk1_type = "new"; - println!("\nNot supported!"); + println!("\nUnknown EPK1 variant!"); return Ok(()); } @@ -71,66 +71,61 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box, + #[br(count = 4)] _magic_bytes: Vec, //epak file_size: u32, pak_count: u32, - #[br(count = 4)] _epk2_magic: Vec, + #[br(count = 4)] _epk2_magic: Vec, //EPK2 #[br(count = 4)] version: Vec, #[br(count = 32)] ota_id_bytes: Vec, } @@ -36,6 +38,33 @@ impl PakEntry { } } +#[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."); if header == b"epak" { @@ -45,12 +74,6 @@ pub fn is_epk2_file(file: &File) -> bool { } } -struct Pak { - offset: u32, - size: u32, - name: String, -} - pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box> { file.seek(SeekFrom::Start(128))?; //inital signature @@ -68,7 +91,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box = Vec::new(); //parse paks in header for i in 0..hdr.pak_count { let pak: PakEntry = hdr_reader.read_le()?; - - println!("Pak {}: {}, offset: {}, size: {}, segment size: {}", i + 1, pak.name(), pak.offset + 128, pak.size, pak.segment_size); - - paks.push(Pak { offset: pak.offset + 128, size: pak.size, name: pak.name() }); + //here the accounted for signature is the one at the beginning of the EPK file + println!("Pak {} - {}, offset: {}, size: {}, segment size: {}", i + 1, pak.name(), pak.offset + SIGNATURE_SIZE, pak.size, pak.segment_size); + paks.push(Pak { offset: pak.offset + SIGNATURE_SIZE, _size: pak.size, name: pak.name() }); } let mut signature_count = 0; //extract paks for (pak_n, pak) in paks.iter().enumerate() { - let actual_offset = pak.offset + (128 * signature_count); - + let actual_offset = pak.offset + (SIGNATURE_SIZE * signature_count); file.seek(SeekFrom::Start(actual_offset as u64))?; - let _signature = common::read_exact(&mut file, 128)?; + let _signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?; signature_count += 1; let encrypted_header = common::read_exact(&mut file, 128)?; @@ -110,7 +131,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box 0 { let _signature = common::read_exact(&mut file, 128)?; signature_count += 1; let encrypted_header = common::read_exact(&mut file, 128)?; - let header = decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?; - segment_size = u32::from_le_bytes(header[88..92].try_into().unwrap()); + let mut pak_header_reader = Cursor::new(decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?); + pak_header = pak_header_reader.read_le()?; } + assert!(i == pak_header.segment_index, "Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index); + let actual_segment_size = // check if this is the last segment and not the last PAK - if i == segment_count - 1 && pak_n < paks.len() - 1 { + if i == pak_header.segment_count - 1 && pak_n < paks.len() - 1{ // calculate distance to next PAK - let next_pak_offset = &paks[pak_n + 1].offset + (128 * signature_count); + let next_pak_offset = &paks[pak_n + 1].offset + (SIGNATURE_SIZE * signature_count); let current_pos = file.stream_position()?; let distance = next_pak_offset - current_pos as u32; // if distance less than segment size, use the distance as actual size - if distance < segment_size { + if distance < pak_header.segment_size { distance } else { - segment_size + pak_header.segment_size } } else { - segment_size + pak_header.segment_size }; + println!("- Segment {}/{} - Size: {}", i + 1, pak_header.segment_count, actual_segment_size); + let segment_data = common::read_exact(&mut file, actual_segment_size as usize)?; let out_data = decrypt_aes_ecb_auto(&matching_key_bytes, &segment_data)?; - println!("- Segment {}/{} - Size: {}", i + 1, segment_count, actual_segment_size); - - let output_path = Path::new(&output_folder).join(pak.name.clone() + ".bin"); - + let output_path = Path::new(&output_folder).join(format!("{}.bin", pak.name)); fs::create_dir_all(&output_folder)?; - let mut out_file = OpenOptions::new() - .append(true) - .create(true) - .open(output_path)?; - + let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?; out_file.write_all(&out_data)?; println!("-- Saved to file!"); diff --git a/src/formats/epk2b.rs b/src/formats/epk2b.rs new file mode 100644 index 0000000..b48c234 --- /dev/null +++ b/src/formats/epk2b.rs @@ -0,0 +1,123 @@ +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 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) + } +} + +#[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."); + if epak_magic == b"epak" && epk2_magic == b"EPK2" { + true + } else { + false + } +} + +pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box> { + 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]); + + let mut paks: Vec = Vec::new(); + + for _i in 0..10 { //header can fit max 10 pak entries + let pak: Pak = file.read_le()?; + if pak.offset == 0 && pak.size == 0 { + continue; + } + paks.push(Pak { offset: pak.offset, size: pak.size }); + } + + assert!(header.pak_count as usize == paks.len(), "Paks count in header does not match the amount of non empty pak entries!"); + + for (i, pak) in paks.iter().enumerate() { + file.seek(SeekFrom::Start(pak.offset as u64))?; + let mut pak_header: PakHeader = file.read_le()?; + + let mut all_segment_size = 0; + + println!("\n({}/{}) - {}, Size: {}, Segment count: {}, Platform: {}", + i + 1, paks.len(), pak_header.pak_name(), pak_header.image_size, pak_header.segment_count, pak_header.platform_id()); + + for i in 0..pak_header.segment_count { + // for first segment we already read the header so skip doing that for it + if i > 0 { + pak_header = file.read_le()?; + } + + assert!(i == pak_header.segment_index, "Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index); + + println!("- Segment {}/{} - Size: {}", i + 1, pak_header.segment_count, pak_header.segment_size); + let out_data = common::read_exact(&mut file, pak_header.segment_size as usize)?; + all_segment_size += pak_header.segment_size; + + //for the last segment, the extra data should be calculated and stripped. + let segment_limit = if i == pak_header.segment_count - 1 { + let diff = all_segment_size - pak_header.image_size; + pak_header.segment_size - diff + } else { + pak_header.segment_size + }; + + let output_path = Path::new(&output_folder).join(format!("{}.bin", pak_header.pak_name())); + fs::create_dir_all(&output_folder)?; + let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?; + out_file.write_all(&out_data[..segment_limit as usize])?; + + println!("-- Saved to file!"); + } + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/formats/epk3.rs b/src/formats/epk3.rs index 958e96b..36f4b96 100644 --- a/src/formats/epk3.rs +++ b/src/formats/epk3.rs @@ -9,10 +9,11 @@ use crate::formats::epk::{decrypt_aes_ecb_auto, find_key}; #[derive(BinRead)] struct Header { - #[br(count = 4)] _magic_bytes: Vec, + #[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 { @@ -20,6 +21,25 @@ impl Header { } } +#[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) + } +} + + #[derive(BinRead)] struct PkgInfoHeader { package_info_list_size: u32, @@ -62,13 +82,13 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box Result<(), Box Result<(), Box Result<(), Box> { println!("EPK1 file detected!"); formats::epk1::extract_epk1(&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::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)?;