mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
Overhaul epk(epk1,epk2,epk3), add epk2b type extractor
This commit is contained in:
@@ -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;
|
||||
|
||||
+4
-1
@@ -43,9 +43,12 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool {
|
||||
}
|
||||
|
||||
pub fn extract_epk(file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
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");
|
||||
|
||||
+37
-42
@@ -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<u8>,
|
||||
stored_size: u32,
|
||||
#[br(count = 15)] platform_id_bytes: Vec<u8>,
|
||||
image_size: u32,
|
||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
||||
#[br(count = 56)] _reserved: Vec<u8>,
|
||||
}
|
||||
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<dyn
|
||||
if init_pak_count > 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<dyn
|
||||
|
||||
for _i in 0..10 { //header can fit max 10 pak entries
|
||||
let pak: Pak = file.read_be()?;
|
||||
|
||||
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!");
|
||||
assert!(header.pak_count as usize == paks.len(), "Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len());
|
||||
|
||||
let version = common::read_exact(&mut file, 4)?;
|
||||
|
||||
println!("EPK info:\nFile size: {}\nPak count: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
|
||||
println!("EPK info -\nData size: {}\nPak count: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
|
||||
header.file_size, header.pak_count, version[1], version[2], version[3]);
|
||||
|
||||
} else if epk1_type == "le" {
|
||||
let header: CommonHeader = file.read_le()?;
|
||||
|
||||
for _i in 0..20 { //header can fit max 20 pak entries
|
||||
let pak: Pak = file.read_le()?;
|
||||
//this is to make an odd variant with 32 max pak entries work
|
||||
let header_size_bytes = common::read_file(&file, 12, 4)?; //offset of first entry, can be treated as header size
|
||||
let header_size = u32::from_le_bytes(header_size_bytes.try_into().unwrap());
|
||||
let max_pak_count = (header_size - 48) / 8; //header size minus common header + ota id (48) divide by size of pak entry (8).
|
||||
assert!(max_pak_count < 128, "Unreasonable calculated pak count {}!!", max_pak_count);
|
||||
|
||||
for _i in 0..max_pak_count {
|
||||
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!");
|
||||
assert!(header.pak_count as usize == paks.len(), "Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len());
|
||||
|
||||
let version = common::read_exact(&mut file, 4)?;
|
||||
|
||||
let ota_id_bytes = common::read_exact(&mut file, 32)?;
|
||||
let ota_id = common::string_from_bytes(&ota_id_bytes);
|
||||
|
||||
println!("EPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
|
||||
header.file_size, header.pak_count, ota_id, version[2], version[1], version[0]);
|
||||
println!("EPK info -\nData size: {}\nHeader size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
|
||||
header.file_size, header_size, header.pak_count, ota_id, version[2], version[1], version[0]);
|
||||
}
|
||||
|
||||
for (i, pak) in paks.iter().enumerate() {
|
||||
file.seek(SeekFrom::Start(pak.offset as u64))?;
|
||||
let pak_header: PakHeader = if epk1_type == "be" {file.read_be()?} else {file.read_le()?};
|
||||
file.seek(SeekFrom::Start(pak.offset as u64))?;
|
||||
let pak_header: PakHeader = if epk1_type == "be" {file.read_be()?} else {file.read_le()?};
|
||||
|
||||
let data = common::read_file(&file, pak.offset as u64 + 128, pak.size as usize - 128)?;
|
||||
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Platform: {}",
|
||||
i + 1, paks.len(), pak_header.pak_name(), pak.offset, pak_header.image_size, pak_header.platform_id());
|
||||
|
||||
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Platform: {}",
|
||||
i + 1, paks.len(), pak_header.pak_name(), pak.offset, pak.size, pak_header.platform_id());
|
||||
let data = common::read_exact(&mut file, pak_header.image_size as usize)?;
|
||||
|
||||
let output_path = Path::new(&output_folder).join(pak_header.pak_name() + ".bin");
|
||||
let output_path = Path::new(&output_folder).join(pak_header.pak_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)?;
|
||||
|
||||
fs::create_dir_all(&output_folder)?;
|
||||
let mut out_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.open(output_path)?;
|
||||
|
||||
out_file.write_all(&data[..pak_header.stored_size as usize])?;
|
||||
|
||||
println!("- Saved file!");
|
||||
}
|
||||
println!("- Saved file!");
|
||||
}
|
||||
|
||||
println!("\nExtraction finished!");
|
||||
|
||||
|
||||
+58
-42
@@ -7,12 +7,14 @@ use crate::utils::common;
|
||||
use crate::keys;
|
||||
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
||||
|
||||
static SIGNATURE_SIZE: u32 = 128;
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct Header {
|
||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //epak
|
||||
file_size: u32,
|
||||
pak_count: u32,
|
||||
#[br(count = 4)] _epk2_magic: Vec<u8>,
|
||||
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
|
||||
#[br(count = 4)] version: Vec<u8>,
|
||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
||||
}
|
||||
@@ -36,6 +38,33 @@ impl PakEntry {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct PakHeader {
|
||||
#[br(count = 4)] _pak_name_bytes: Vec<u8>,
|
||||
image_size: u32,
|
||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
||||
_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<u8>, //MPAK
|
||||
#[br(count = 24)] _reserved: Vec<u8>,
|
||||
_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<dyn std::error::Error>> {
|
||||
file.seek(SeekFrom::Start(128))?; //inital signature
|
||||
|
||||
@@ -68,7 +91,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
||||
println!("Header is encrypted...");
|
||||
println!("\nFinding key...");
|
||||
//find the key, knowing that the header should start with "epak"
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK2, &stored_header, b"epak")? {
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header, b"epak")? {
|
||||
println!("Found valid key: {}", key_name);
|
||||
matching_key = Some(key_bytes);
|
||||
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
||||
@@ -81,27 +104,25 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
||||
let mut hdr_reader = Cursor::new(header);
|
||||
let hdr: Header = hdr_reader.read_le()?;
|
||||
|
||||
println!("\nEPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\n",
|
||||
hdr.file_size, hdr.pak_count, hdr.ota_id(), hdr.version[3], hdr.version[2], hdr.version[1]);
|
||||
println!("\nEPK info -\nData size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}.{:02x?}\n",
|
||||
hdr.file_size, hdr.pak_count, hdr.ota_id(), hdr.version[3], hdr.version[2], hdr.version[1], hdr.version[0]);
|
||||
|
||||
let mut paks: Vec<Pak> = 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<dyn
|
||||
if matching_key.is_none() {
|
||||
println!("\nFinding key...");
|
||||
//find the key, knowing that the header should start with with the paks name
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK2, &encrypted_header, pak.name.as_bytes())? {
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &encrypted_header, pak.name.as_bytes())? {
|
||||
println!("Found correct key: {}", key_name);
|
||||
matching_key = Some(key_bytes);
|
||||
} else {
|
||||
@@ -118,59 +139,54 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let matching_key_bytes = matching_key.as_ref().unwrap();
|
||||
|
||||
let header = decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?;
|
||||
let mut pak_header_reader = Cursor::new(decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?);
|
||||
let mut pak_header: PakHeader = pak_header_reader.read_le()?;
|
||||
|
||||
let segment_count = u32::from_le_bytes(header[84..88].try_into().unwrap());
|
||||
let mut segment_size = u32::from_le_bytes(header[88..92].try_into().unwrap());
|
||||
println!("\n({}/{}) - {}, Size: {}, Segment count: {}, Platform: {}",
|
||||
pak_n + 1, paks.len(), pak.name, pak_header.image_size, pak_header.segment_count, pak_header.platform_id());
|
||||
|
||||
println!("\n({}/{}) - {}, Size: {}, Segments: {}", pak_n + 1, paks.len(), pak.name, pak.size, segment_count);
|
||||
|
||||
for i in 0..segment_count {
|
||||
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 {
|
||||
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!");
|
||||
|
||||
@@ -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<u8>, //epak
|
||||
file_size: u32,
|
||||
pak_count: u32,
|
||||
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
|
||||
#[br(count = 4)] version: Vec<u8>,
|
||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
||||
}
|
||||
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<u8>,
|
||||
image_size: u32,
|
||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
||||
_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<u8>, //MPAK
|
||||
#[br(count = 24)] _reserved: Vec<u8>,
|
||||
_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<dyn std::error::Error>> {
|
||||
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<Pak> = 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(())
|
||||
}
|
||||
+37
-14
@@ -9,10 +9,11 @@ use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
||||
|
||||
#[derive(BinRead)]
|
||||
struct Header {
|
||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //EPK3
|
||||
#[br(count = 4)] version: Vec<u8>,
|
||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
||||
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<u8>,
|
||||
#[br(count = 6)] encrypt_type_bytes: Vec<u8>,
|
||||
#[br(count = 6)] update_type_bytes: Vec<u8>,
|
||||
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<dyn
|
||||
println!("Finding key...");
|
||||
|
||||
// find the key, knowing that the header should start with "EPK3"
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK3, &stored_header, b"EPK3")? {
|
||||
if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header, b"EPK3")? {
|
||||
println!("Found valid key: {}", key_name);
|
||||
matching_key = Some(key_bytes);
|
||||
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
||||
|
||||
//try for new format epk3 where theres an additional 128byte signature at the beginning
|
||||
} else if let Some((key_name, key_bytes)) = find_key(&keys::EPK3, &stored_header[128..], b"EPK3")? {
|
||||
} else if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header[128..], b"EPK3")? {
|
||||
println!("Found valid key: {}", key_name);
|
||||
matching_key = Some(key_bytes);
|
||||
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
||||
@@ -89,10 +109,18 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
||||
if new_type {let _signature = common::read_exact(&mut hdr_reader, 128)?;};
|
||||
let hdr: Header = hdr_reader.read_le()?;
|
||||
|
||||
println!("\nEPK info:\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\nPackage Info size: {}\n",
|
||||
hdr.ota_id(), hdr.version[3], hdr.version[2], hdr.version[1], hdr.package_info_size);
|
||||
//
|
||||
let _versions = common::read_exact(&mut file, 36)?;
|
||||
println!("\nEPK info -\nEPK3 type: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}.{:02x?}\nPackage Info size: {}",
|
||||
if new_type {"New"} else {"Old"}, hdr.ota_id(), hdr.version[3], hdr.version[2], hdr.version[1], hdr.version[0], hdr.package_info_size);
|
||||
|
||||
if new_type {
|
||||
let ex_hdr: HeaderNewEx = hdr_reader.read_le()?;
|
||||
println!("Encrypt type: {}\nUpdate type: {}\nUpdate platform version: {:.6}\nCompatible minimum version: {:.6}\nNeed to check compatible version: {}",
|
||||
ex_hdr.encrypt_type(), ex_hdr.update_type(), ex_hdr.update_platform_version, ex_hdr.compatible_minimum_version, ex_hdr.need_to_check_compatible_version);
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
let _platform_versions = common::read_exact(&mut file, 36)?;
|
||||
let _signature = common::read_exact(&mut file, signature_size)?;
|
||||
|
||||
//PKG INFO
|
||||
@@ -125,14 +153,9 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
||||
let encrypted_data = common::read_exact(&mut file, entry.segment_size as usize + extra_segment_size)?;
|
||||
let out_data = decrypt_aes_ecb_auto(matching_key_bytes, &encrypted_data)?;
|
||||
|
||||
let output_path = Path::new(&output_folder).join(entry.package_name().clone() + ".bin");
|
||||
|
||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.package_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[extra_segment_size..])?;
|
||||
|
||||
println!("-- Saved to file!");
|
||||
|
||||
+32
-34
@@ -58,9 +58,35 @@ pub static PFLUPG: &[(&str, &str)] = &[
|
||||
("q5492", "F28ED197C5C40B7F7356D4EDD5526BC238C812EB59400087BBA2D6402B41BBE695EAAE5F6051F1B5EBAC1756F17FED9DA0678F2E6EE59ABF530847DB9249DD025D00FFC67B2ECFF320E99F5DA8A46B1E41479BA3B5CDC110D247B7AF31E887463C8E7A9929BE511C724FB7B4BACBF055F276ED53687E24543815D5437405B0CB"),
|
||||
];
|
||||
|
||||
//epk2 keys
|
||||
//pana dvd keys (no AES)
|
||||
pub static PANA_DVD_KEYONLY: &[&str] = &[
|
||||
("08E03D859AF9F3EE"), //B3A25A0B9D864F08 # 87 firmwares, ..-~2011
|
||||
];
|
||||
|
||||
//pana dvd keys (PANAEUSB pair (AES + cust))
|
||||
pub static PANA_DVD_AESPAIR: &[(&str, &str)] = &[
|
||||
("62A39E1C5594AE09244EB326EF7938FA", "06C943F3B997F7E0"), //# 9 firmwares, ~2018
|
||||
];
|
||||
|
||||
//mtk pkg custom keys
|
||||
//key, iv, desc
|
||||
pub static MTK_PKG_CUST: &[(&str, &str, &str)] = &[
|
||||
("D378EAF81D378A801B556985789A7C31", "73079FD19183715E130858588479C652", "Philips 2012"),
|
||||
("47FBF8CAD62BB95AF3AD9509E5C2175D", "63120FB321B0410F216D6DC2D8641A11", "Philips 2013"),
|
||||
("135AFB6DE91CD56496244BC7C0E08D63", "C6A38C89F0AF5637EB6E19D35E12E257", "TPV_MTK2K14PLF_EU1"),
|
||||
("7B17F7764818AE2C897BA69D428D0CB3", "672041CCB9FCD4272B11E57AB6047163", "TPV_MTK2K16PLF_EU0"),
|
||||
("1B569AA7D2E4CCE66584A7A3D8A45679", "A0E88D5D52A813260D3A34A14AA89416", "TPV_MTK2K17PLF_EU0"),
|
||||
|
||||
("B1BFAA407F70C80C650379DFEAFAA40F", "BC1443A0D17AAB2DB1EA0302EF280717", "Common"), //new mtk pkg, worked for Hisense/CVT/Changhong
|
||||
("DB7D510A8DA635645A844B2F55C0B5BA", "93A97FF5C4BCE585122C7109FC2E1A3F", "Sony 2017 Linux"),
|
||||
("9320FC32BCDCE3178B7B2BA2A1AF3391", "D93F84B9BDDC9E1BD3E7C114AAA2FAA3", "Sony 2015 Android"),
|
||||
("F888FC0268C6692D7EB76243E53D47E5", "EE40FE4700EC3197336940D4B30D4BE8", "Sony 2016 Android"),
|
||||
("07A30C2E4AAE4849EC3DD2301C381868", "BC1443A0D17AAB2DB1EA0302EF280717", "Philips Android"), //worked on TPM171E(2017), TPM191E(2019), TPM211E(2021)
|
||||
];
|
||||
|
||||
//EPK keys
|
||||
//github.com/openlgtv/epk2extract
|
||||
pub static EPK2: &[(&str, &str)] = &[
|
||||
pub static EPK: &[(&str, &str)] = &[
|
||||
("214BF3C129547AF31D32A5ECB4742192", "unknown"),
|
||||
("1F1E1D1C1B1A19180706050403020100", "BCM35230/early MTK5369 and LG1152"),
|
||||
("7184C9C428D03C445188234D5A827196", "mtk5369 - Mediatek GP4"),
|
||||
@@ -76,39 +102,11 @@ pub static EPK2: &[(&str, &str)] = &[
|
||||
("68A284B4953CAD15024BED2C4F852A09", "lm14 - MStar NetCast 4.5"),
|
||||
("4813B5B63C998A2874EF3320684AC8D9", "lg1152 - LX GP4"),
|
||||
("14B3623488212250C7C992AACD537447", "lg115x - LX NC4"),
|
||||
];
|
||||
|
||||
//epk3 keys
|
||||
//github.com/openlgtv/epk2extract
|
||||
pub static EPK3: &[(&str, &str)] = &[
|
||||
|
||||
("34CC219D3AFC102433109BBC1DA44095", "m14 (m14tv) - LX webOS 1 (2014)"),
|
||||
("5A167D8C342EF094800E7CFA2D10F2D0", "m14 (m14tv) - LX webOS 2 (2015)"),
|
||||
("ADB92D9E23035522F4708CC259B31EA2", "m2 - MStar webOS 3.0 (2016)"),
|
||||
("E529BCDEDF8E49667C0FA3A81174B65E", "o18 - LX webOS 4.0 (2018)"),
|
||||
("74514676D68B9A72A0093CEF56D3067484E1F4D5CF7D4B4ED389BED030FA1B09", "k7lp - Realtek webOS 6 (2021)"),
|
||||
("52A208FA24E7E70730A40999B1C22C148F4920484BC50B515D243E35D14689F1", "o24n - LX webOS 10 (2025)"),
|
||||
];
|
||||
|
||||
//pana dvd keys (no AES)
|
||||
pub static PANA_DVD_KEYONLY: &[&str] = &[
|
||||
("08E03D859AF9F3EE"), //B3A25A0B9D864F08 # 87 firmwares, ..-~2011
|
||||
];
|
||||
|
||||
//pana dvd keys (PANAEUSB pair (AES + cust))
|
||||
pub static PANA_DVD_AESPAIR: &[(&str, &str)] = &[
|
||||
("62A39E1C5594AE09244EB326EF7938FA", "06C943F3B997F7E0"), //# 9 firmwares, ~2018
|
||||
];
|
||||
|
||||
//mtk pkg custom keys
|
||||
//github.com/openlgtv/epk2extract
|
||||
//key, iv, desc
|
||||
pub static MTK_PKG_CUST: &[(&str, &str, &str)] = &[
|
||||
("D378EAF81D378A801B556985789A7C31", "73079FD19183715E130858588479C652", "Philips 2012"),
|
||||
("47FBF8CAD62BB95AF3AD9509E5C2175D", "63120FB321B0410F216D6DC2D8641A11", "Philips 2013"),
|
||||
("135AFB6DE91CD56496244BC7C0E08D63", "C6A38C89F0AF5637EB6E19D35E12E257", "TPV_MTK2K14PLF_EU1"),
|
||||
("7B17F7764818AE2C897BA69D428D0CB3", "672041CCB9FCD4272B11E57AB6047163", "TPV_MTK2K16PLF_EU0"),
|
||||
("1B569AA7D2E4CCE66584A7A3D8A45679", "A0E88D5D52A813260D3A34A14AA89416", "TPV_MTK2K17PLF_EU0"),
|
||||
|
||||
("B1BFAA407F70C80C650379DFEAFAA40F", "BC1443A0D17AAB2DB1EA0302EF280717", "Common"), //new mtk pkg, worked for Hisense/CVT/Changhong
|
||||
("DB7D510A8DA635645A844B2F55C0B5BA", "93A97FF5C4BCE585122C7109FC2E1A3F", "Sony 2017 Linux"),
|
||||
("9320FC32BCDCE3178B7B2BA2A1AF3391", "D93F84B9BDDC9E1BD3E7C114AAA2FAA3", "Sony 2015 Android"),
|
||||
("F888FC0268C6692D7EB76243E53D47E5", "EE40FE4700EC3197336940D4B30D4BE8", "Sony 2016 Android"),
|
||||
("07A30C2E4AAE4849EC3DD2301C381868", "BC1443A0D17AAB2DB1EA0302EF280717", "Philips Android"), //worked on TPM171E(2017), TPM191E(2019), TPM211E(2021)
|
||||
];
|
||||
+8
-4
@@ -90,16 +90,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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)?;
|
||||
|
||||
Reference in New Issue
Block a user