2026-02-05 15:18:26 +01:00
|
|
|
use std::any::Any;
|
2026-02-17 18:57:14 +01:00
|
|
|
use std::io::Seek;
|
2026-02-17 17:28:59 +01:00
|
|
|
use crate::AppContext;
|
2025-10-08 00:40:29 +02:00
|
|
|
|
2025-11-26 17:29:25 +01:00
|
|
|
use crate::utils::common;
|
2026-06-04 19:46:23 +08:00
|
|
|
use crate::utils::aes::decrypt_aes_ecb_auto;
|
2025-10-08 00:40:29 +02:00
|
|
|
use crate::formats;
|
2026-06-05 21:02:40 +08:00
|
|
|
use log::info;
|
2025-10-08 00:40:29 +02:00
|
|
|
|
2026-02-05 15:18:26 +01:00
|
|
|
pub struct EpkContext {
|
|
|
|
|
epk_version: u8,
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 18:53:35 +01:00
|
|
|
pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
2026-02-06 16:27:13 +01:00
|
|
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
2025-10-08 00:40:29 +02:00
|
|
|
|
2026-02-06 15:08:38 +01:00
|
|
|
let versions = common::read_file(&file, 1712, 36)?;
|
2026-02-05 15:18:26 +01:00
|
|
|
if let Some(epk_version) = check_epk_version(&versions) {
|
|
|
|
|
Ok(Some(Box::new(EpkContext {epk_version})))
|
2025-10-08 00:40:29 +02:00
|
|
|
} else {
|
2026-02-05 15:18:26 +01:00
|
|
|
Ok(None)
|
2025-10-08 00:40:29 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-05 15:18:26 +01:00
|
|
|
fn check_epk_version(versions: &[u8]) -> Option<u8> {
|
2025-10-08 16:46:21 +02:00
|
|
|
// _ - 0x00 X - a number . - a dot
|
2026-03-19 20:17:22 +01:00
|
|
|
let epk2_pattern = "____XXXX.XXXX.XXXX__XX.XX.XXX_______";
|
|
|
|
|
let epk3_pattern = "____X.X.X___________X.X.X___________";
|
|
|
|
|
let epk3_another_pattern = "____X.XX.X__________X.XX.X__________";
|
|
|
|
|
let epk3_new_pattern = "____XX.X.X__________XX.X.X__________";
|
2025-10-08 00:40:29 +02:00
|
|
|
|
|
|
|
|
if match_with_pattern(&versions, epk2_pattern) {
|
2026-02-05 15:18:26 +01:00
|
|
|
Some(2)
|
2026-06-04 19:46:23 +08:00
|
|
|
} else if match_with_pattern(&versions, epk3_pattern) {
|
2026-02-05 15:18:26 +01:00
|
|
|
Some(3)
|
2026-06-04 19:46:23 +08:00
|
|
|
} else if match_with_pattern(&versions, epk3_another_pattern) {
|
2026-03-19 20:17:22 +01:00
|
|
|
Some(3)
|
2026-06-04 19:46:23 +08:00
|
|
|
} else if match_with_pattern(&versions, epk3_new_pattern) {
|
2026-02-05 15:18:26 +01:00
|
|
|
Some(3)
|
2026-06-04 19:46:23 +08:00
|
|
|
} else {
|
2025-10-08 00:40:29 +02:00
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn match_with_pattern(data: &[u8], pattern: &str) -> bool {
|
|
|
|
|
for (&b, p) in data.iter().zip(pattern.bytes()) {
|
|
|
|
|
match p {
|
2025-10-08 13:04:41 +02:00
|
|
|
b'_' if b != 0x00 => return false,
|
2025-10-08 00:40:29 +02:00
|
|
|
b'X' if !b.is_ascii_digit() => return false,
|
2025-10-08 13:04:41 +02:00
|
|
|
b'.' if b != b'.' => return false,
|
2025-10-08 00:40:29 +02:00
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 16:27:13 +01:00
|
|
|
pub fn extract_epk(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
2026-02-17 18:57:14 +01:00
|
|
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
2026-06-05 21:02:40 +08:00
|
|
|
let ctx = ctx.downcast::<EpkContext>().map_err(|_| "Invalid context type")?;
|
2026-02-05 15:18:26 +01:00
|
|
|
|
2026-02-06 15:08:38 +01:00
|
|
|
let versions = common::read_file(&file, 1712, 36)?;
|
2026-01-24 16:36:20 +01:00
|
|
|
|
|
|
|
|
let platform_version = common::string_from_bytes(&versions[4..20]);
|
|
|
|
|
let sdk_version = common::string_from_bytes(&versions[20..36]);
|
2026-06-05 21:02:40 +08:00
|
|
|
info!("Platform version: {}\nSDK version: {}", platform_version, sdk_version);
|
2026-06-04 19:46:23 +08:00
|
|
|
|
2026-02-17 18:57:14 +01:00
|
|
|
file.seek(std::io::SeekFrom::Start(0))?;
|
|
|
|
|
|
2026-02-05 15:18:26 +01:00
|
|
|
if ctx.epk_version == 2 {
|
2026-06-05 21:02:40 +08:00
|
|
|
info!("EPK2 detected!\n");
|
2026-02-06 16:27:13 +01:00
|
|
|
formats::epk2::extract_epk2(app_ctx, Box::new(()))?;
|
2026-02-05 15:18:26 +01:00
|
|
|
} else if ctx.epk_version == 3 {
|
2026-06-05 21:02:40 +08:00
|
|
|
info!("EPK3 detected!\n");
|
2026-02-06 16:27:13 +01:00
|
|
|
formats::epk3::extract_epk3(app_ctx, Box::new(()))?;
|
2025-10-08 00:40:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2025-10-09 20:45:37 +02:00
|
|
|
}
|
|
|
|
|
|
2026-06-04 19:46:23 +08:00
|
|
|
// COMMON EPK FUNCTIONS
|
|
|
|
|
|
|
|
|
|
/// Try each key in `key_array` to find one that decrypts `data` with a header
|
|
|
|
|
/// matching `expected_magic`. Returns the key name and the raw key bytes on
|
|
|
|
|
/// success, or `None` if no key works.
|
2025-10-09 20:45:37 +02:00
|
|
|
pub fn find_key<'a>(key_array: &'a [(&'a str, &'a str)], data: &[u8], expected_magic: &[u8]) -> Result<Option<(&'a str, Vec<u8>)>, Box<dyn std::error::Error>> {
|
|
|
|
|
for (key_hex, name) in key_array {
|
|
|
|
|
let key_bytes = hex::decode(key_hex)?;
|
|
|
|
|
let decrypted = match decrypt_aes_ecb_auto(&key_bytes, data) {
|
|
|
|
|
Ok(d) => d,
|
|
|
|
|
Err(_) => continue,
|
|
|
|
|
};
|
2026-06-04 19:46:23 +08:00
|
|
|
|
2025-10-09 20:45:37 +02:00
|
|
|
if decrypted.starts_with(expected_magic) {
|
|
|
|
|
return Ok(Some((name, key_bytes)));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(None)
|
|
|
|
|
}
|