2025-10-07 21:43:49 +02:00
|
|
|
use std::fs::{self, File, OpenOptions};
|
|
|
|
|
use std::path::{Path};
|
2025-10-08 13:04:41 +02:00
|
|
|
use std::io::{Write, Seek, SeekFrom, Cursor};
|
2025-10-07 21:43:49 +02:00
|
|
|
|
|
|
|
|
use crate::common;
|
2025-10-08 15:14:53 +02:00
|
|
|
use crate::keys;
|
2025-10-09 20:45:37 +02:00
|
|
|
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
2025-10-07 21:43:49 +02:00
|
|
|
|
|
|
|
|
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" {
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Pak {
|
|
|
|
|
offset: u32,
|
|
|
|
|
size: u32,
|
|
|
|
|
name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
2025-10-08 13:04:41 +02:00
|
|
|
file.seek(SeekFrom::Start(128))?; //inital signature
|
|
|
|
|
|
|
|
|
|
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
|
|
|
|
|
let header;
|
2025-10-08 00:40:29 +02:00
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let mut matching_key: Option<Vec<u8>> = None;
|
2025-10-08 00:40:29 +02:00
|
|
|
|
|
|
|
|
//check if header is encrypted
|
2025-10-08 13:04:41 +02:00
|
|
|
let epak = &stored_header[0..4]; // epak magic
|
2025-10-08 00:40:29 +02:00
|
|
|
if epak == b"epak" {
|
|
|
|
|
println!("Header is not encrypted.");
|
2025-10-08 13:04:41 +02:00
|
|
|
header = stored_header;
|
2025-10-08 00:40:29 +02:00
|
|
|
} else {
|
2025-10-08 13:04:41 +02:00
|
|
|
println!("Header is encrypted...");
|
|
|
|
|
println!("\nFinding key...");
|
|
|
|
|
//find the key, knowing that the header should start with "epak"
|
2025-10-09 20:45:37 +02:00
|
|
|
if let Some((key_name, key_bytes)) = find_key(&keys::EPK2, &stored_header, b"epak")? {
|
2025-10-08 13:04:41 +02:00
|
|
|
println!("Found valid key: {}", key_name);
|
|
|
|
|
matching_key = Some(key_bytes);
|
2025-10-09 20:45:37 +02:00
|
|
|
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
2025-10-08 13:04:41 +02:00
|
|
|
} else {
|
|
|
|
|
println!("No valid key found!");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
2025-10-08 00:40:29 +02:00
|
|
|
}
|
2025-10-08 13:04:41 +02:00
|
|
|
//parse header
|
|
|
|
|
let mut hdr_reader = Cursor::new(header);
|
2025-10-07 21:43:49 +02:00
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let _epk = common::read_exact(&mut hdr_reader, 4)?;
|
|
|
|
|
|
|
|
|
|
let file_size_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let file_size = u32::from_le_bytes(file_size_bytes.try_into().unwrap());
|
|
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let pak_count_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let pak_count = u32::from_le_bytes(pak_count_bytes.try_into().unwrap());
|
|
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let _epk2 = common::read_exact(&mut hdr_reader, 4)?; // EPK2 magic
|
2025-10-07 21:43:49 +02:00
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let version = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let ota_id_bytes = common::read_exact(&mut hdr_reader, 32)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let ota_id = common::string_from_bytes(&ota_id_bytes);
|
|
|
|
|
|
|
|
|
|
println!("\nEPK info:\nFile size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}\n",
|
|
|
|
|
file_size, pak_count, ota_id, version[3], version[2], version[1]);
|
|
|
|
|
|
|
|
|
|
let mut paks: Vec<Pak> = Vec::new();
|
2025-10-08 13:04:41 +02:00
|
|
|
//parse paks in header
|
2025-10-07 21:43:49 +02:00
|
|
|
for i in 0..pak_count {
|
2025-10-08 13:04:41 +02:00
|
|
|
let offset_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let offset = u32::from_le_bytes(offset_bytes.try_into().unwrap()) + 128; //add 128 bytes of initial signature
|
|
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let size_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let size = u32::from_le_bytes(size_bytes.try_into().unwrap());
|
|
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let name_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let name = common::string_from_bytes(&name_bytes);
|
|
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let _version = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
|
2025-10-08 13:04:41 +02:00
|
|
|
let segment_size_bytes = common::read_exact(&mut hdr_reader, 4)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
let segment_size = u32::from_le_bytes(segment_size_bytes.try_into().unwrap());
|
|
|
|
|
|
|
|
|
|
println!("Pak {}: {}, offset: {}, size: {}, segment size: {}", i + 1, name, offset, size, segment_size);
|
|
|
|
|
|
|
|
|
|
paks.push(Pak { offset, size, name });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut signature_count = 0;
|
2025-10-08 13:04:41 +02:00
|
|
|
//extract paks
|
|
|
|
|
for (pak_n, pak) in paks.iter().enumerate() {
|
2025-10-07 21:43:49 +02:00
|
|
|
let actual_offset = pak.offset + (128 * signature_count);
|
|
|
|
|
|
|
|
|
|
file.seek(SeekFrom::Start(actual_offset as u64))?;
|
|
|
|
|
|
|
|
|
|
let _signature = common::read_exact(&mut file, 128)?;
|
|
|
|
|
signature_count += 1;
|
|
|
|
|
|
|
|
|
|
let encrypted_header = common::read_exact(&mut file, 128)?;
|
2025-10-08 13:04:41 +02:00
|
|
|
|
|
|
|
|
//the file's header was not encrypted so we dont have the key yet
|
|
|
|
|
if matching_key.is_none() {
|
|
|
|
|
println!("\nFinding key...");
|
|
|
|
|
//find the key, knowing that the header should start with with the paks name
|
2025-10-09 20:45:37 +02:00
|
|
|
if let Some((key_name, key_bytes)) = find_key(&keys::EPK2, &encrypted_header, pak.name.as_bytes())? {
|
2025-10-08 13:04:41 +02:00
|
|
|
println!("Found correct key: {}", key_name);
|
|
|
|
|
matching_key = Some(key_bytes);
|
|
|
|
|
} else {
|
|
|
|
|
println!("No valid key found!");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let matching_key_bytes = matching_key.as_ref().unwrap();
|
|
|
|
|
|
2025-10-09 20:45:37 +02:00
|
|
|
let header = decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
|
|
|
|
|
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!("\nPak {}/{} - {}, Size: {}, Segments: {}", pak_n + 1, paks.len(), pak.name, pak.size, segment_count);
|
|
|
|
|
|
|
|
|
|
for i in 0..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)?;
|
2025-10-09 20:45:37 +02:00
|
|
|
let header = decrypt_aes_ecb_auto(&matching_key_bytes, &encrypted_header)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
segment_size = u32::from_le_bytes(header[88..92].try_into().unwrap());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
// calculate distance to next PAK
|
|
|
|
|
let next_pak_offset = &paks[pak_n + 1].offset + (128 * 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 {
|
|
|
|
|
distance
|
|
|
|
|
} else {
|
|
|
|
|
segment_size
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
segment_size
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let segment_data = common::read_exact(&mut file, actual_segment_size as usize)?;
|
2025-10-09 20:45:37 +02:00
|
|
|
let out_data = decrypt_aes_ecb_auto(&matching_key_bytes, &segment_data)?;
|
2025-10-07 21:43:49 +02:00
|
|
|
|
|
|
|
|
println!("- Segment {}/{}, size: {}", i + 1, segment_count, actual_segment_size);
|
|
|
|
|
|
|
|
|
|
let output_path = Path::new(&output_folder).join(pak.name.clone() + ".bin");
|
|
|
|
|
|
|
|
|
|
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)?;
|
|
|
|
|
|
|
|
|
|
println!("-- Saved to file!");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("\nExtraction finished!");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|