+ fixed verbose flags

This commit is contained in:
Pari Malam
2026-06-06 01:05:43 +08:00
parent c27e22b09f
commit 7d298cb176
46 changed files with 188 additions and 74 deletions
+1 -1
View File
@@ -73,4 +73,4 @@ pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+1 -1
View File
@@ -87,4 +87,4 @@ pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) ->
}
Ok(())
}
}
+1 -1
View File
@@ -71,4 +71,4 @@ pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
}
Ok(())
}
}
+1 -1
View File
@@ -89,4 +89,4 @@ pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
}
Ok(())
}
}
+5 -1
View File
@@ -5,7 +5,7 @@ use crate::AppContext;
use crate::utils::common;
use crate::utils::aes::decrypt_aes_ecb_auto;
use crate::formats;
use log::info;
use log::{debug, info};
pub struct EpkContext {
epk_version: u8,
@@ -16,9 +16,12 @@ pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let versions = common::read_file(&file, 1712, 36)?;
debug!("epk: version bytes at 1712: {:02X?}", versions);
if let Some(epk_version) = check_epk_version(&versions) {
debug!("epk: matched EPK v{}", epk_version);
Ok(Some(Box::new(EpkContext {epk_version, versions})))
} else {
debug!("epk: no version pattern matched");
Ok(None)
}
}
@@ -58,6 +61,7 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool {
pub fn extract_epk(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let ctx = ctx.downcast::<EpkContext>().map_err(|_| "Invalid context type")?;
debug!("epk: dispatching EPK v{} extraction", ctx.epk_version);
let platform_version = common::string_from_bytes(&ctx.versions[4..20]);
let sdk_version = common::string_from_bytes(&ctx.versions[20..36]);
+1 -1
View File
@@ -114,4 +114,4 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
}
Ok(())
}
}
+1 -1
View File
@@ -150,4 +150,4 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
}
Ok(())
}
}
+1 -1
View File
@@ -86,4 +86,4 @@ pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
Ok(())
}
}
+1 -1
View File
@@ -118,4 +118,4 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
}
Ok(())
}
}
+1 -1
View File
@@ -141,4 +141,4 @@ pub fn uncomp_image_rom(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Erro
}
Ok(out_data)
}
}
+1 -1
View File
@@ -88,4 +88,4 @@ pub fn extract_funai_mstar(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
}
Ok(())
}
}
+1 -1
View File
@@ -77,4 +77,4 @@ pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
}
Ok(())
}
}
+1 -1
View File
@@ -76,4 +76,4 @@ pub fn extract_funai_upg_phl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result
}
Ok(())
}
}
+2 -1
View File
@@ -8,7 +8,7 @@ use crate::AppContext;
use crate::utils::common;
use binrw::BinReaderExt;
use include::*;
use log::info;
use log::{debug, info};
pub fn is_gx_dvb_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -26,6 +26,7 @@ pub fn extract_gx_dvb(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
file.seek(SeekFrom::Start(TABLE_OFFSET))?;
let table: PartTable = file.read_be()?;
debug!("Table raw: part_count={}", table.part_count);
info!("Part count: {}", table.part_count);
+1 -1
View File
@@ -87,4 +87,4 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Res
}
Ok(())
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
pub enum FirmwareType{
Old,
Tizen,
+16 -2
View File
@@ -14,7 +14,7 @@ use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decr
use crate::formats::msd::msd_ouith_parser_old::{parse_ouith_blob};
use crate::formats::msd::msd_ouith_parser_tizen_1_8::{parse_blob_1_8};
use include::*;
use log::info;
use log::{debug, info};
pub fn is_msd10_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -48,6 +48,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
for i in 0..header_count {
let header: HeaderEntry = file.read_le()?;
info!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
debug!("msd10: header[{}] name={} offset={} size={}", i + 1, header.name(), header.offset, header.size);
headers.push(header);
}
@@ -65,9 +66,12 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut firmware_type: Option<FirmwareType> = None;
for (key_hex, name) in keys::MSD10 {
let key_bytes = hex::decode(key_hex)?;
debug!("msd10: trying key '{}' ({} bytes, {})", name, key_bytes.len(),
if key_bytes.len() == 20 { "OLD" } else { "TIZEN" });
if key_bytes.len() == 20 {
match decrypt_aes_salted_old(&toc_data, &key_bytes) {
Ok(_) => {
debug!("msd10: key '{}' matched (OLD firmware)", name);
passphrase_bytes = Some(key_bytes);
passphrase_name = name;
firmware_type = Some(FirmwareType::Old);
@@ -79,6 +83,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
else if key_bytes.len() == 16 {
match decrypt_aes_salted_tizen(&toc_data, &key_bytes) {
Ok(_) => {
debug!("msd10: key '{}' matched (Tizen firmware)", name);
passphrase_bytes = Some(key_bytes);
passphrase_name = name;
firmware_type = Some(FirmwareType::Tizen);
@@ -90,18 +95,22 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
let (passphrase_bytes, firmware_type) = if let (Some(p), Some(t)) = (passphrase_bytes, firmware_type) {
debug!("msd10: matched key '{}' -> firmware_type={:?}", passphrase_name, t);
info!("Using passphrase: {}", passphrase_name);
(p, t)
} else {
debug!("msd10: no key matched out of {} keys tried", keys::MSD10.len());
return Err("No matching key found!".into());
};
//parse TOC
if firmware_type == FirmwareType::Tizen {
let toc = decrypt_aes_salted_tizen(&toc_data, &passphrase_bytes)?;
debug!("msd10: TOC decrypted (Tizen), {} bytes", toc.len());
opt_dump_dec_hdr(app_ctx, &toc, "toc")?;
let (items, info) = parse_blob_1_8(&toc, app_ctx.has_option("msd:print_ouith"))?;
debug!("msd10: parsed {} TOC items", items.len());
if let Some(info) = info {
info!("\nImage info:\n{} {}.{} {}/{}/{}",
@@ -111,6 +120,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
for (i, item) in items.iter().enumerate() {
let size = sections[i as usize].size;
let offset = sections[i as usize].offset;
debug!("msd10: item[{}] id={} offset={} size={} aes={}", i+1, item.item_id, offset, size, item.aes_encryption);
info!("\n({}/{}) - {}, Size: {}",
i + 1, items.len(), item.name, size);
@@ -141,9 +151,11 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
} else if firmware_type == FirmwareType::Old {
let toc = decrypt_aes_salted_old(&toc_data, &passphrase_bytes)?;
debug!("msd10: TOC decrypted (Old), {} bytes", toc.len());
opt_dump_dec_hdr(app_ctx, &toc, "toc")?;
let (items, info) = parse_ouith_blob(&toc, app_ctx.has_option("msd:print_ouith"))?;
debug!("msd10: parsed {} OUITH items", items.len());
if let Some(info) = info {
info!("\nImage info:\n{} {}.{} {}/{}/20{}",
@@ -159,6 +171,8 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
if sections[i as usize].index != item.item_id {
return Err("Item ID in TOC does not match ID from header!".into());
}
debug!("msd10: processing item[{}] type={} offset={} heading={} data={} aes={}",
i+1, type_str, offset, item.heading_size, item.data_size, item.aes_encryption);
let mut out_filename = format!("{}", item.name);
if item.item_type == 0x11 { //CMAC DATA
@@ -194,4 +208,4 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
Ok(())
}
}
+1 -1
View File
@@ -120,4 +120,4 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
Ok(())
}
}
+9 -2
View File
@@ -12,7 +12,7 @@ use crate::utils::compression::{decompress_lzma, decompress_lz4};
use crate::utils::lzop::{unlzop_to_file};
use crate::utils::sparse::{unsparse_to_file};
use include::*;
use log::info;
use log::{debug, info};
pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -28,11 +28,13 @@ pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<d
pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let file = app_ctx.file().ok_or("Extractor expected file")?;
debug!("mstar: starting extraction, script at 0x0 and fallback at 0x1000");
let mut script = common::read_file(&file, 0, 32768)?;
if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) {
script.truncate(pos);
debug!("mstar: script truncated at offset {}", pos);
}
let mut script_string = String::from_utf8_lossy(&script);
@@ -40,23 +42,28 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
//try for hisense
info!("Failed to get script at 0x0, trying 0x1000...");
script = common::read_file(&file, 4096, 32768)?;
debug!("mstar: fallback script read {} bytes at 0x1000", script.len());
if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) {
script.truncate(pos);
debug!("mstar: fallback script truncated at offset {}", pos);
}
script_string = String::from_utf8_lossy(&script);
if script_string.is_empty() {
debug!("mstar: both script reads empty, failing");
return Err("Failed to get script".into());
}
}
opt_dump_dec_hdr(app_ctx, &script, "script")?;
let lines: Vec<&str> = script_string.lines().map(|l| l.trim()).collect();
debug!("mstar: script has {} non-empty lines", lines.len());
for (i, line) in lines.iter().enumerate() {
if line.starts_with("filepartload") {
debug!("mstar: line[{}] = filepartload: {}", i, line);
let parts: Vec<&str> = line.split_whitespace().collect();
let offset = parse_number(parts[3]).unwrap_or(0);
let size = parse_number(parts[4]).unwrap_or(0);
@@ -172,4 +179,4 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
Ok(())
}
}
+1 -1
View File
@@ -74,4 +74,4 @@ pub fn extract_mstar_secure_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Resu
}
Ok(())
}
}
+1 -1
View File
@@ -128,4 +128,4 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
}
Ok(())
}
}
+8 -4
View File
@@ -15,7 +15,7 @@ use crate::utils::aes::{decrypt_aes128_cbc_nopad};
use crate::keys;
use lzhs::{decompress_mtk_to_file};
use include::*;
use log::{info, warn};
use log::{debug, info, warn};
pub struct MtkPkgContext {
is_philips_variant: bool,
@@ -27,17 +27,21 @@ pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
let mut encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
debug!("mtk_pkg detection: default magic={:02X?}", &header[4..12]);
if &header[4..12] == MTK_HEADER_MAGIC {
Ok(Some(Box::new(MtkPkgContext { is_philips_variant: false, decrypted_header: header})))
debug!("mtk_pkg: matched standard variant");
return Ok(Some(Box::new(MtkPkgContext { is_philips_variant: false, decrypted_header: header})));
} else {
// try for philips which has additional 128 bytes at beginning
encrypted_header = common::read_file(&file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?;
header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
debug!("mtk_pkg detection: philips magic={:02X?}", &header[4..12]);
if &header[4..12] == MTK_HEADER_MAGIC {
Ok(Some(Box::new(MtkPkgContext { is_philips_variant: true, decrypted_header: header })))
debug!("mtk_pkg: matched philips variant");
return Ok(Some(Box::new(MtkPkgContext { is_philips_variant: true, decrypted_header: header })));
} else {
debug!("mtk_pkg: no magic match, skipping");
Ok(None)
}
}
}
+1 -1
View File
@@ -52,4 +52,4 @@ pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+25 -5
View File
@@ -7,7 +7,7 @@ use crate::AppContext;
use crate::utils::aes::decrypt_aes_ecb_auto;
use crate::utils::common;
use crate::keys;
use log::{info, warn};
use log::{debug, info, warn};
// shredder format detectordecryptor
// Detects files starting with bsl magic and attempts XOR/AES-ECB decryption
@@ -37,18 +37,22 @@ pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let file_size = file.metadata()?.len();
debug!("novatek_raw detection: file_size={}", file_size);
if file_size < SHREDDER_MAGIC.len() as u64 {
debug!("novatek_raw: file too small, skipping");
return Ok(None);
}
let magic = common::read_file(&file, 0, SHREDDER_MAGIC.len())?;
debug!("novatek_raw: magic bytes={:02X?}", magic);
if magic == SHREDDER_MAGIC {
info!("- Detected shredder format");
return Ok(Some(Box::new(())));
}
if app_ctx.has_option("nvt_raw:key") {
debug!("novatek_raw: forced via nvt_raw:key option");
return Ok(Some(Box::new(())));
}
@@ -57,32 +61,42 @@ pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let file = app_ctx.file().ok_or("Extractor expected file")?;
debug!("Starting novatek_raw extraction");
let key_hex = app_ctx.options.iter()
.find(|o| o.starts_with("nvt_raw:key="))
.map(|o| o.strip_prefix("nvt_raw:key=").unwrap());
let key_hex = match key_hex {
Some(k) => k,
Some(k) => {
debug!("Using CLI-provided key");
k
}
None => keys::NOVATEK_RAW.first().map(|(_, k)| *k).ok_or("nvt_raw:key=<hex_key> option required for decryption")?,
};
debug!("Key source: {}", key_hex);
let key = hex::decode(key_hex).map_err(|e| format!("Invalid hex key: {}", e))?;
debug!("Decoded key length: {} bytes", key.len());
let file_size = file.metadata()?.len() as usize;
let enc_data = common::read_file(&file, 0, file_size)?;
debug!("Read {} encrypted bytes from file", enc_data.len());
info!("- File size: {} bytes", file_size);
info!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" });
let dec_data = match decrypt_aes_ecb_auto(&key, &enc_data) {
Ok(d) => {
debug!("AES-ECB decryption succeeded, output size: {} bytes", d.len());
info!("- Decryption complete");
d
}
Err(_) => {
warn!("- AES-ECB failed, trying XOR...");
xor_decrypt(&key, &enc_data)
Err(e) => {
warn!("- AES-ECB failed ({}), trying XOR...", e);
let xored = xor_decrypt(&key, &enc_data);
debug!("XOR fallback decryption output size: {} bytes", xored.len());
xored
}
};
@@ -94,6 +108,7 @@ pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
info!("- Saved decrypted image to {}", output_path.display());
info!("\nExtracting partitions:");
let mut extracted_count = 0;
for (name, offset, size) in NVT_PARTITIONS {
if (offset + size) as usize <= dec_data.len() && size > 0 {
let part_data = &dec_data[offset as usize..(offset + size) as usize];
@@ -101,8 +116,13 @@ pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
let mut part_file = OpenOptions::new().write(true).create(true).open(&part_path)?;
part_file.write_all(part_data)?;
info!(" {}: offset=0x{:x}, size={}", name, offset, size);
debug!("Wrote {}: {} bytes to {}", name, part_data.len(), part_path.display());
extracted_count += 1;
} else if size > 0 {
debug!("Skipping {} (out of range: offset=0x{:x}, size={}, total={})", name, offset, size, dec_data.len());
}
}
debug!("Total partitions extracted: {}", extracted_count);
Ok(())
}
+1 -1
View File
@@ -103,4 +103,4 @@ pub fn extract_nvt_timg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+1 -1
View File
@@ -132,4 +132,4 @@ pub fn extract_onkyo(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<
}
Ok(())
}
}
+9 -3
View File
@@ -1,4 +1,4 @@
use log::info;
use log::{debug, info};
mod include;
mod pana_dvd_crypto;
@@ -65,6 +65,7 @@ pub fn is_pana_dvd_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let context = ctx.downcast::<PanaDvdContext>().map_err(|_| "Invalid context type")?;
debug!("pana_dvd extraction: is_aes={}, base_hdr_size={}", context.is_aes, context.base_hdr_size);
let matching_key = context.matching_key;
let mut file_entries: Vec<FileEntry> = Vec::new();
@@ -73,6 +74,7 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
if context.is_aes {
let (aes_key, aes_iv) = (context.aes_key.unwrap(), context.aes_iv.unwrap());
info!("Using key: {} + AES key: {}, IV: {}", hex::encode_upper(matching_key), hex::encode_upper(aes_key), hex::encode_upper(aes_iv));
debug!("pana_dvd: AES mode, reading 48-byte file table");
//read inner file table
let file_table = common::read_exact(&mut file, 48)?;
@@ -82,17 +84,21 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
for _i in 0..4 {
let file_entry: AesHeaderFileEntry = file_table_reader.read_le()?;
if file_entry.size == 0 && file_entry.offset == 0 {
debug!("pana_dvd: AES file table entry {} is zero, stopping", _i);
break
}
//ignore duplicate entries
if !file_entries.iter().any(|f| f.offset == file_entry.offset ){
debug!("pana_dvd: AES file table entry {}: offset={}, size={}", _i, file_entry.offset, file_entry.size);
file_entries.push(FileEntry { offset: file_entry.offset, size: file_entry.size, header_size: context.base_hdr_size });
}
}
} else {
info!("Using key: {}", hex::encode_upper(matching_key));
file_entries.push(FileEntry { offset: 0, size: file.metadata()?.len() as u32, header_size: context.base_hdr_size });
let file_size = file.metadata()?.len() as u32;
debug!("pana_dvd: non-AES mode, treating entire file as single entry, size={}", file_size);
file_entries.push(FileEntry { offset: 0, size: file_size, header_size: context.base_hdr_size });
}
info!("File contains {} sub-files...", file_entries.len());
@@ -305,4 +311,4 @@ fn extract_drv(mut data: Vec<u8>, key: &[u8; 8]) -> Result<Vec<u8>, Box<dyn std:
};
Ok(out_data)
}
}
+16 -3
View File
@@ -10,7 +10,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::decrypt_aes256_ecb;
use include::*;
use log::info;
use log::{debug, info};
pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -26,12 +26,15 @@ pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: Header = file.read_le()?;
let header: Header = file.read_le()?;
debug!("pfl_upg header: header_size={}, data_size={}, mask=0x{:08X}",
header.header_size, header.data_size, header.mask);
let signature = common::read_exact(&mut file, 128)?;
let _ = common::read_exact(&mut file, 32)?; //unknown
let version_bytes = common::read_exact(&mut file, 28)?;
let version = common::string_from_bytes(&version_bytes);
debug!("pfl_upg version: {}", version);
info!("\nVersion: {}", version);
if header.description() != "" { //look ugly when empty
@@ -39,6 +42,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
info!("-------------------");
}
info!("Data size: {}", header.data_size);
debug!("pfl_upg: is_encrypted={}", header.is_encrypted());
file.seek(SeekFrom::Start(header.header_size as u64))?;
@@ -48,8 +52,10 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
//get some data as test ciphertext for key finding
let ciphertext = common::read_file(&mut file, header.header_size as u64, 64)?;
debug!("pfl_upg: ciphertext for key search is {} bytes", ciphertext.len());
let aes_key;
if let Some((key_name, key)) = try_find_key(&signature, &ciphertext)? {
debug!("pfl_upg: matched RSA pubkey '{}'", key_name);
info!("Matched pubkey: {}, AES key: {}", key_name, hex::encode(key));
aes_key = key;
} else {
@@ -58,6 +64,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
//need to align to 16 bytes for AES blocksize
let encrypted_data = common::read_exact(&mut file, (header.data_size as usize + 0xf) & !0xf)?;
debug!("pfl_upg: encrypted data read {} bytes", encrypted_data.len());
info!("Decrypting data...");
data = decrypt_aes256_ecb(&aes_key, &encrypted_data)?;
@@ -65,16 +72,22 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
} else {
data = common::read_exact(&mut file, header.data_size as usize)?;
debug!("pfl_upg: unencrypted data read {} bytes", data.len());
}
let mut data_reader = Cursor::new(data);
debug!("pfl_upg: data_reader length={}", data_reader.get_ref().len());
while (data_reader.position() as usize) < data_reader.get_ref().len() {
let file_header: FileHeader = data_reader.read_le()?;
debug!("pfl_upg: file_header: name_len={}, stored={}, real={}, is_folder={}, is_pkg={}",
file_header.file_name().len(), file_header.stored_size, file_header.real_size,
file_header.is_folder(), file_header.is_package());
//sometimes has extra header data
let ex_header_size = file_header.header_size - 76; //76 is base file header size
let ex_header_bytes = common::read_exact(&mut data_reader, ex_header_size as usize)?;
debug!("pfl_upg: ex_header_size={}", ex_header_size);
if file_header.is_folder() {
info!("\nFolder - {}", file_header.file_name());
@@ -138,4 +151,4 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+1 -1
View File
@@ -91,4 +91,4 @@ pub fn extract_philips_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
}
Ok(())
}
}
+9 -2
View File
@@ -10,12 +10,13 @@ use std::io::{Write, Seek, SeekFrom};
use crate::utils::common;
use crate::utils::compression::{decompress_zlib};
use include::*;
use log::info;
use log::{debug, info};
pub fn is_pup_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let header = common::read_file(&file, 0, 4)?;
debug!("pup: magic check {:02X?} -> ps4={} ps5={}", header, header==b"\x4F\x15\x3D\x1D", header==b"\x54\x14\xF5\xEE");
if header == b"\x4F\x15\x3D\x1D" || header == b"\x54\x14\xF5\xEE" { //ps4, ps5
Ok(Some(Box::new(())))
} else {
@@ -25,10 +26,12 @@ pub fn is_pup_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn
pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
debug!("pup: starting extraction");
let header: Header = file.read_le()?;
info!("File info:\nFile size: {}\nEntry count: {}",
header.file_size, header.entry_count);
debug!("pup: header file_size={}, entry_count={}", header.file_size, header.entry_count);
let mut entries: Vec<Entry> = Vec::new();
let mut block_tables: Vec<Entry> = Vec::new();
@@ -36,13 +39,17 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
for _i in 0..header.entry_count {
let entry: Entry = file.read_le()?;
if entry.is_block_table() {
debug!("pup: entry {} is a block table", _i);
block_tables.push(entry.clone())
}
entries.push(entry);
}
debug!("pup: parsed {} entries, {} block tables", entries.len(), block_tables.len());
let mut e_i = 0;
for entry in &entries {
debug!("pup: entry[{}] id={} offset={} comp={} blocked={} block_table={}",
e_i, entry.id(), entry.offset, entry.is_compressed(), entry.is_blocked(), entry.is_block_table());
info!("\n({}/{}) - ID: {} Offset: {}, Compressed Size: {}, Uncompressed Size: {}\nCompressed: {}, Blocked: {}, Block table: {}",
e_i + 1, entries.len(), entry.id(), entry.offset, entry.compressed_size, entry.uncompressed_size, entry.is_compressed(), entry.is_blocked(), entry.is_block_table());
@@ -135,4 +142,4 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
}
Ok(())
}
}
+9 -3
View File
@@ -11,7 +11,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
use include::*;
use log::info;
use log::{debug, info};
pub fn is_roku_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
@@ -28,19 +28,25 @@ pub fn is_roku_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dy
pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
debug!("roku: starting extraction");
let mut encrypted_data = Vec::new();
file.read_to_end(&mut encrypted_data)?;
let read_size = file.read_to_end(&mut encrypted_data)?;
debug!("roku: read {} bytes from file", read_size);
info!("\nDecrypting...\n");
let tar_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &FILE_KEY, &FILE_IV)?;
debug!("roku: decrypted to {} bytes of tar data", tar_data.len());
let tar_reader = Cursor::new(tar_data);
let mut tar_archive = Archive::new(tar_reader);
let mut entry_count = 0;
for entry_result in tar_archive.entries_with_seek()? {
let mut entry = entry_result?;
entry_count += 1;
let path = entry.path()?.to_path_buf();
debug!("roku: tar entry[{}] = {:?}, size={}", entry_count, path, entry.header().size()?);
if path == std::path::Path::new("manifest") {
let size = entry.header().size()? as usize;
@@ -100,4 +106,4 @@ pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
}
Ok(())
}
}
+1 -1
View File
@@ -115,4 +115,4 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
}
Ok(())
}
}
+1 -1
View File
@@ -143,4 +143,4 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
}
Ok(())
}
}
+6 -2
View File
@@ -14,7 +14,7 @@ use crate::utils::common;
use crate::keys;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use include::decrypt_xor;
use log::info;
use log::{debug, info};
pub fn is_samsung_old_dir(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let dir = match app_ctx.dir() {Some(d) => d, None => return Ok(None)};
@@ -28,9 +28,11 @@ pub fn is_samsung_old_dir(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let path = app_ctx.dir().ok_or("Extractor expected directory")?;
debug!("samsung_old: scanning directory {}", path.display());
let fw_info = fs::read_to_string(Path::new(&path).join("image/info.txt"))?;
info!("Firmware info: {}", fw_info);
debug!("samsung_old: fw_info raw: {}", fw_info);
let image_path = Path::new(&path).join("image");
@@ -44,6 +46,7 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
}
}
if let Some(p) = secret {
debug!("samsung_old: matched secret for prefix '{}'", fw_info.split_whitespace().next().unwrap_or("?"));
info!("Secret: {}", p);
} else {
return Err("This firmware is not supported!".into());
@@ -59,6 +62,7 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
let file = File::open(&path)?;
let filename = path.file_name().unwrap().to_str().unwrap();
let file_size = file.metadata()?.len();
debug!("samsung_old: processing .sec file '{}' ({} bytes)", filename, file_size);
info!("\nFile - {}", filename);
let data = common::read_file(&file, 0, file_size.try_into().unwrap())?;
@@ -128,4 +132,4 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
}
Ok(())
}
}
+1 -1
View File
@@ -134,4 +134,4 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
}
Ok(())
}
}
+1 -1
View File
@@ -247,4 +247,4 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
}
Ok(())
}
}
+1 -1
View File
@@ -55,4 +55,4 @@ pub fn extract_sdimage(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+1 -1
View File
@@ -111,4 +111,4 @@ pub fn extract_slp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
}
Ok(())
}
}
+1
View File
@@ -1,6 +1,7 @@
use crate::utils::common;
use binrw::BinRead;
#[derive(Debug)]
pub enum EncryptionType {
HexSubst,
AesOfb(([u8; 16], [u8; 16], String)), //key, iv, key name
+12 -4
View File
@@ -12,7 +12,7 @@ use crate::utils::common;
use crate::formats;
use crate::keys;
use include::*;
use log::info;
use log::{debug, info};
struct SonyBdpCtx {
encryption_type: EncryptionType,
@@ -21,9 +21,11 @@ struct SonyBdpCtx {
pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let header_magic = common::read_file(&file, 0, 16)?;
debug!("sony_bdp: header_magic={:02X?}", header_magic);
//try old encryption (hex subst)
if is_valid_header_magic(&hex_substitute(&header_magic)) {
debug!("sony_bdp: matched HexSubst encryption");
return Ok(Some(Box::new(
SonyBdpCtx {encryption_type:
EncryptionType::HexSubst
@@ -38,6 +40,7 @@ pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
let try_decrypt = ver_up_decrypt_aes128ofb(&key_array, &iv_array, &header_magic);
if is_valid_header_magic(&try_decrypt) {
debug!("sony_bdp: matched AES-OFB key '{}'", name);
return Ok(Some(Box::new(
SonyBdpCtx {encryption_type:
EncryptionType::AesOfb((key_array, iv_array, name.to_string()))
@@ -45,17 +48,19 @@ pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
)));
}
}
debug!("sony_bdp: no encryption match, skipping");
Ok(None)
}
pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let ctx = ctx.downcast::<SonyBdpCtx>().map_err(|_| "Invalid context type")?;
debug!("sony_bdp: encryption_type={:?}", ctx.encryption_type);
//need to decrypt entire file of new aes enc
let mut enc_data = Vec::new();
file.read_to_end(&mut enc_data)?;
let enc_size = file.read_to_end(&mut enc_data)?;
debug!("sony_bdp: read {} encrypted bytes", enc_size);
let dec_data = match ctx.encryption_type {
EncryptionType::HexSubst => {
@@ -67,11 +72,13 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
ver_up_decrypt_aes128ofb(&key, &iv, &enc_data)
}
};
debug!("sony_bdp: decrypted to {} bytes", dec_data.len());
let mut data_reader = Cursor::new(dec_data);
let header = common::read_exact(&mut data_reader, 300)?;
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
debug!("sony_bdp: firmware={}, version={}, date={}, file_size={}", hdr.firmware_name(), hdr.firmware_version(), hdr.date(), hdr.file_size);
info!("File info:\nFirmware: {}\nVersion: {}\nDate: {}\nFile size: {}",
hdr.firmware_name(), hdr.firmware_version(), hdr.date(), hdr.file_size);
@@ -86,6 +93,7 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
}
let entry: Entry = hdr_reader.read_le()?;
debug!("sony_bdp: entry[{}] offset={} size={}", i, entry.offset, entry.size);
if entry.size == 0 {
continue
}
@@ -132,4 +140,4 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
}
Ok(())
}
}
+7 -2
View File
@@ -13,7 +13,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use tsb_des::decrypt;
use log::info;
use log::{debug, info};
struct TsbBinCtx {
key: Option<[u8; 8]>
@@ -24,12 +24,14 @@ pub fn is_tsb_bin_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
let header = common::read_file(&file, 0, 256)?;
if is_valid_header_checksum(&header) {
debug!("tsb_bin: valid header checksum, no decryption needed");
return Ok(Some(Box::new(TsbBinCtx {key: None})));
}
// -- failed, try with decrypt
//derive key from FILE SIZE (yes)
let file_size = file.metadata()?.len() as u32;
debug!("tsb_bin: header checksum failed, trying size-derived key (file_size={})", file_size);
let mut key = [0u8; 8];
key[..4].copy_from_slice(&file_size.to_le_bytes());
let inv = !file_size;
@@ -46,6 +48,7 @@ pub fn is_tsb_bin_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let ctx = ctx.downcast::<TsbBinCtx>().map_err(|_| "Invalid context type")?;
debug!("tsb_bin: extracted {} key", if ctx.key.is_some() {"size-derived"} else {"none (plain)"});
let mut header = common::read_file(&mut file, 0, 0x400)?;
if let Some(key) = ctx.key { //decrypt header
@@ -61,6 +64,8 @@ pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
,hdr.length, hdr.entry_count, hdr.build_no(), hdr.entry_addr);
for (i, entry) in hdr.entries.iter().enumerate() {
debug!("tsb_bin: entry[{}] name={} offset={} size={} load_addr=0x{:02x} comp={}",
i+1, entry.name(), entry.offset, entry.size, entry.load_addr, entry.is_compressed());
info!("\n({}/{}) - {}, Size: {}, Offset: {}, Load address: 0x{:02x}",
i+1, hdr.entry_count, entry.name(), entry.size, entry.offset, entry.load_addr);
@@ -90,4 +95,4 @@ pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
}
Ok(())
}
}
+1 -1
View File
@@ -216,4 +216,4 @@ fn extract_mb230(
}
Ok(())
}
}
+8 -11
View File
@@ -99,16 +99,15 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logger based on verbosity level
// Default (no flags): show info-level output (normal progress)
// -q: errors only
// -v: info (same as default, explicit)
// -vv: debug level (more details)
// -v: debug level (more details)
// -vv: trace level (everything)
// -vvv: trace level (everything)
let log_level = if args.quiet {
"error"
} else {
match args.verbose {
0 => "info",
1 => "info",
2 => "debug",
1 => "debug",
_ => "trace",
}
};
@@ -118,14 +117,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.format_target(false)
.format_module_path(false)
.format(|buf, record| {
// Clean format: just the message for info/debug/trace
// Add level prefix for warn/error so they stand out
match record.level() {
log::Level::Error => writeln!(buf, "[ERROR] {}", record.args()),
log::Level::Warn => writeln!(buf, "[WARN] {}", record.args()),
log::Level::Info => writeln!(buf, "{}", record.args()),
log::Level::Debug => writeln!(buf, "[DBG] {}", record.args()),
log::Level::Trace => writeln!(buf, "[TRC] {}", record.args()),
log::Level::Error => writeln!(buf, "\x1b[31m[ERROR]\x1b[0m {}", record.args()),
log::Level::Warn => writeln!(buf, "\x1b[33m[WARN]\x1b[0m {}", record.args()),
log::Level::Info => writeln!(buf, "\x1b[36m{}\x1b[0m", record.args()),
log::Level::Debug => writeln!(buf, "\x1b[32m[DBG]\x1b[0m {}", record.args()),
log::Level::Trace => writeln!(buf, "\x1b[90m[TRC]\x1b[0m {}", record.args()),
}
})
.init();
+1
View File
@@ -1,3 +1,4 @@
pub mod sevenz;
pub mod common;
pub mod aes;
pub mod lzop;
+16
View File
@@ -0,0 +1,16 @@
use std::path::PathBuf;
use log::info;
#[allow(dead_code)]
pub fn decompress_7z(data: &[u8], output_dir: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
info!("- Decompressing 7z archive...");
sevenz_rust2::decompress(std::io::Cursor::new(data.to_vec()), &output_dir)?;
Ok(())
}
#[allow(dead_code)]
pub fn decompress_7z_file(file_path: &std::path::Path, output_dir: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
info!("- Decompressing 7z file...");
sevenz_rust2::decompress_file(file_path, &output_dir)?;
Ok(())
}
+1 -1
View File
@@ -68,4 +68,4 @@ pub fn unsparse_to_file(data: &[u8], file_path: PathBuf) -> Result<(), Box<dyn s
Ok(())
}
}