+ cleanup

This commit is contained in:
Pari Malam
2026-06-05 21:02:40 +08:00
parent 612ba74bc8
commit c1f5b7961d
65 changed files with 670 additions and 641 deletions
-40
View File
@@ -1,40 +0,0 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-latest
label: linux
- os: windows-latest
label: windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Build
run: cargo build --release
- uses: actions/upload-artifact@v4
with:
name: unixtract-${{ matrix.label }}-auto
path: |
target/release/unixtract
target/release/unixtract.exe
+2 -1
View File
@@ -1,3 +1,4 @@
/target
__pycache__/
.vscode
.vscode
venv/
+6 -2
View File
@@ -4,7 +4,11 @@ version = "0.2.0"
edition = "2024"
description = "Firmware extractor for various file formats from TVs and AV devices"
license = "GPL-3.0"
repository = "https://github.com/theubusu/unixtract"
repository = "https://github.com/Ap0dexMe0/unixtract"
[features]
default = ["zstd"]
zstd = ["dep:zstd"]
[dependencies]
clap = { version = "4.5", features = ["derive"] }
@@ -27,7 +31,7 @@ prost-types = "0.14.1"
bzip2 = "0.6.1"
liblzma = "0.4.5"
des = "0.8.1"
zstd = "0.13.3"
zstd = { version = "0.13.3", optional = true }
thiserror = "2.0"
log = "0.4"
env_logger = "0.11"
+11 -13
View File
@@ -1,11 +1,15 @@
# unixtract
Extractor for various file formats.
This project is a fork of [theubusu/unixtract](https://github.com/theubusu/unixtract), adding extra features and improvements.
This is a tool that is able to extract the contents of various firmware package formats, mostly from TVs and AV devices.
Built in Rust, and made to not depend on any external dependancies, only Rust crates. This way it can work on Windows, Linux and MacOS and even Android.
Please note that this project is still under active development and errors may occur. Feel free to make an issue in that case, or if you have any feature request.
**PLEASE NOTE** - this program is NOT, and will never be designed for re-packing the extracted files!
## About this fork
This fork extends the upstream project with additional features and bug fixes. For the original README and format support details, see the [upstream repository](https://github.com/theubusu/unixtract).
# Installation
You can download the latest auto build for Windows and Linux x86-64 from [here](https://nightly.link/theubusu/unixtract/workflows/rust/main).
Or, build from source, by downloading the code or cloning the respository and running `cargo build --release`. The binary will be saved in `target/release`.
@@ -15,9 +19,10 @@ Or, build from source, by downloading the code or cloning the respository and ru
Arguments:
`<INPUT_TARGET>` - The target to analyze/extract.
`[OUTPUT_FOLDER]` - Folder to save extracted files to.
If an output folder is not provided, extracted files will be saved in folder `_<INPUT_TARGET>`.
If an output folder is not provided, extracted files will be saved in folder `_<INPUT_TARGET>`.
Options:
`-o, --options <OPTIONS>` - Format specific or global(for all formats that implement it) options, see the list below for format specific options. You can use this multiple times to activate multiple options.
## Global options
`dump_dec_hdrs` - For formats with an encrypted header - dump the decrypted header(s).
@@ -121,6 +126,7 @@ Tip: if you have split ROM (.ROM-00 and .ROM-01), extract both into the same fol
## MediaTek PKG (New)
**Used in:** Newer MediaTek-based TVs (TCL, Hisense, Sony, Philips, CVT...)
**Notes:** **Depends on keys** - see keys.rs (Keys for Philips and Sony included)
**Thanks to:** https://github.com/theubusu/mtkdec
**Options:**
`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing.
※ Support `dump_dec_hdrs` option
@@ -132,14 +138,6 @@ Tip: if you have split ROM (.ROM-00 and .ROM-01), extract both into the same fol
`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing.
※ Support `dump_dec_hdrs` option
## MediaTek PKG
**Used in:** MediaTek-based TVs (Sony, Philips, Panasonic, Sharp...)
**Notes:** All files should be supported, decryption + decompression, however some Philips files use custom keys - most are included some could be missing
**Thanks to:** https://github.com/openlgtv/epk2extract
**Options:**
`mtk_pkg:no_del_comp` - Don't delete LZHS compressed partition file after decompressing.
※ Support `dump_dec_hdrs` option
## Novatek PKG (NFWB)
**Used in:** Some older Novatek-based TVs (LG, Philips)
**Notes:** None, all files should be supported.
@@ -204,16 +202,16 @@ Tip: if you have split ROM (.ROM-00 and .ROM-01), extract both into the same fol
## SDDL.SEC
**Used in:** Panasonic TVs
**Notes:** None, all files should be supported.
**Notes:** None, all files should be supported.
**Options:**
`sddl_sec:save_extra` - Save SDIT.FDI and .TXT files that are not extracted by default.
`sddl_sec:split_peaks` - Split PEAKS module into partitions (only on older files). This will also automatically decompress compressed partitions.
`sddl_sec:no_decomp_peaks` - Do not automatically decompress partitions when splitting PEAKS with above option.
`sddl_sec:no_decomp_peaks` - Do not automatically decompress partitions when splitting PEAKS with above option.
**Base:** https://github.com/theubusu/sddl_dec
## SDImage (SDImage.bin)
**Used in:** Some 2010 USA Panasonic TVs
**Notes:** Decryption is not yet supported.
**Notes:** Decryption is not yet supported.
## SLP
**Used in:** Samsung Tizen-based NX series cameras
+6 -6
View File
@@ -52,11 +52,11 @@ pub mod gx_dvb;
pub mod onkyo;
pub mod philips_bdp;
pub mod tsb_bin;
pub mod pup;
pub mod vestel;
pub mod novatek_raw;
pub mod pup;
pub mod vestel;
pub mod novatek_raw;
pub mod msd;
pub mod msd;
pub mod msd10;
pub mod msd11;
@@ -77,7 +77,7 @@ pub mod mtk_bdp;
/// means that container formats (which embed other formats) must appear
/// before their inner formats to avoid misdetection.
pub fn get_registry() -> Vec<Format> {
return vec![
vec![
Format {
name: "mstar",
detector_func: crate::formats::mstar::is_mstar_file,
@@ -289,4 +289,4 @@ pub fn get_registry() -> Vec<Format> {
extractor_func: crate::formats::novatek_raw::extract_novatek_raw,
},
]
}
}
+7 -6
View File
@@ -10,6 +10,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::sparse::unsparse_to_file;
use include::*;
use log::info;
pub fn is_amlogic_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,7 +27,7 @@ pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: ImageHeader = file.read_le()?;
println!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}",
info!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}",
header.image_size, header.item_align_size, header.item_count, header.version);
if header.version != 2 {
@@ -41,13 +42,13 @@ pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
}
for (i, item) in items.iter().enumerate() {
println!("\n({}/{}) - {}, Type: {}, Offset: {}, Size: {} {}",
info!("\n({}/{}) - {}, Type: {}, Offset: {}, Size: {} {}",
i+1, header.item_count, item.name(), item.item_type(), item.offset_in_image, item.item_size, if item.is_sparse() {"[SPARSE]"} else {""});
if item.item_type() == "VERIFY" { //verify item is SHA1 of partition item
let sum_bytes = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
let sum = common::string_from_bytes(&sum_bytes);
println!("- Checksum for {}: {}", item.name(), sum);
info!("- Checksum for {}: {}", item.name(), sum);
} else {
let data = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
@@ -57,15 +58,15 @@ pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
fs::create_dir_all(&app_ctx.output_dir)?;
if item.is_sparse() {
println!("- Unsparsing...");
info!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
info!("-- Saved file!");
continue
} else {
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
}
+7 -6
View File
@@ -1,3 +1,5 @@
use log::info;
mod include;
mod android_ota_update_metadata;
use std::any::Any;
@@ -29,7 +31,7 @@ pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) ->
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: Header = file.read_be()?;
println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size);
info!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size);
if header.file_format_version != 2 {
return Err("Unsupported format version! (Only 2 is supported right now)".into());
@@ -42,7 +44,7 @@ pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) ->
for (i, partition) in manifest.partitions.into_iter().enumerate() {
let operation_count = partition.operations.len();
println!("\n#{} - {}, Size: {}, Operations: {}",
info!("\n#{} - {}, Size: {}, Operations: {}",
i + 1, partition.partition_name, partition.new_partition_info.unwrap().size.unwrap(), operation_count);
fs::create_dir_all(&app_ctx.output_dir)?;
@@ -60,9 +62,8 @@ pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) ->
//because the amount of operations can reach up to the thousands, i think its best to update the current line
//to not clog up the terminal and so you know what the program is actually doing
print!("\r- ({}/{}) - {}({}), Offset: {}, Size: {}",
info!("- ({}/{}) - {}({}), Offset: {}, Size: {}",
i + 1, operation_count, operation_name_str, operation.r#type, offset, size);
std::io::stdout().flush()?;
let data = common::read_file(&file, offset, size as usize)?;
@@ -76,13 +77,13 @@ pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) ->
else if operation.r#type == 8 { //REPLACE_XZ - decompress with xz and write
out_data = decompress_xz(&data)?;
} else {
println!("-- Unsupported operation!");
info!("-- Unsupported operation!");
break
}
out_file.write_all(&out_data)?;
}
println!("\n-- Saved!");
info!("\n-- Saved!");
}
Ok(())
+5 -4
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub fn is_bdl_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,7 +27,7 @@ pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let header: BdlHeader = file.read_le()?;
println!("File info:\nPackage count: {}\nDate: {}\nManufacturer: {}\nModel: {}\nVersion: {}\nInfo: {}",
info!("File info:\nPackage count: {}\nDate: {}\nManufacturer: {}\nModel: {}\nVersion: {}\nInfo: {}",
header.pkg_count, header.date(), header.manufacturer(), header.model(), header.version(), header.info());
let mut pkgs: Vec<PkgListEntry> = Vec::new();
@@ -39,7 +40,7 @@ pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
for (i, pkg) in pkgs.iter().enumerate() {
file.seek(SeekFrom::Start(pkg.offset))?;
let pkg_header: PkgHeader = file.read_le()?;
println!("\nPackage ({}/{}) - Name: {}, Version: {}, Entry Count: {}, Manufacturer: {}, Offset: {}, Size: {}",
info!("\nPackage ({}/{}) - Name: {}, Version: {}, Entry Count: {}, Manufacturer: {}, Offset: {}, Size: {}",
i + 1, header.pkg_count, pkg_header.name(), pkg_header.version(), pkg_header.entry_count, pkg_header.manufacturer(), pkg.offset, pkg.size);
let mut pkg_entries: Vec<PkgEntry> = Vec::new();
@@ -53,7 +54,7 @@ pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
fs::create_dir_all(&pkg_folder)?;
for (i, pkg_entry) in pkg_entries.iter().enumerate() {
println!("- Entry {}/{} - Name: {}, Offset: {}, Size: {}",
info!("- Entry {}/{} - Name: {}, Offset: {}, Size: {}",
i + 1, pkg_header.entry_count, pkg_entry.name(), pkg_entry.offset, pkg_entry.size);
let calc_offset = pkg.offset + pkg_entry.offset;
@@ -64,7 +65,7 @@ pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
}
+7 -6
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub fn is_cd5_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)};
@@ -32,7 +33,7 @@ pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let dwld_hdr: DownloadHeader = dwld_hdr_reader.read_be()?;
// like Loader Data screen
println!("File info -\nManufacturer code: {}\nHardware Version: {}\nVersion(DSN): {}(0x{:02x})\nVariant/Sub-variant: 0x{:02x}/0x{:02x}\nModule count: {}",
info!("File info -\nManufacturer code: {}\nHardware Version: {}\nVersion(DSN): {}(0x{:02x})\nVariant/Sub-variant: 0x{:02x}/0x{:02x}\nModule count: {}",
dwld_hdr.manufacturer_code, dwld_hdr.hardware_version, dwld_hdr.version, dwld_hdr.version, dwld_hdr.variant, dwld_hdr.sub_variant, dwld_hdr.module_count);
for (i, module) in dwld_hdr.module_entries.iter().enumerate() {
@@ -46,7 +47,7 @@ pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
return Err("Module id mismatch in download header and module header!".into());
}
println!("\n({}/{}) Module {}(0x{:02x}) - Version(DSN): {}(0x{:02x}), Size: {}, Segment size: {}, Segment count: {} {}",
info!("\n({}/{}) Module {}(0x{:02x}) - Version(DSN): {}(0x{:02x}), Size: {}, Segment size: {}, Segment count: {} {}",
i+1, dwld_hdr.module_count, mod_hdr.module_id, mod_hdr.module_id, module.version, module.version, mod_hdr.out_size, mod_hdr.segment_size, mod_hdr.segment_count,
if mod_hdr.is_encrypted() {"[ENCRYPTED]"} else {""});
@@ -61,19 +62,19 @@ pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
return Err("Module id mismatch in segment and module header!".into());
}
println!(" Segment {}/{} - Size: {}", s_i+1, mod_hdr.segment_count, segment.data_size);
info!(" Segment {}/{} - Size: {}", s_i+1, mod_hdr.segment_count, segment.data_size);
module_data.append(&mut segment.data);
}
let out_data;
if mod_hdr.is_encrypted() {
println!("- Warning: data is encrypted, so cannot read inner header - saving ENCRYPTED data!");
info!("- Warning: data is encrypted, so cannot read inner header - saving ENCRYPTED data!");
out_data = module_data;
}
else {
let mut mod_data_rdr = Cursor::new(module_data);
let inner_mod_hdr: InnerModuleHeader = mod_data_rdr.read_be()?;
println!("- Inner header size: {}, Data size: {}", inner_mod_hdr.header_size, inner_mod_hdr.data_size);
info!("- Inner header size: {}, Data size: {}", inner_mod_hdr.header_size, inner_mod_hdr.data_size);
mod_data_rdr.seek(SeekFrom::Start(inner_mod_hdr.header_size as u64))?;
out_data = common::read_exact(&mut mod_data_rdr, inner_mod_hdr.data_size as usize)?;
}
@@ -83,7 +84,7 @@ pub fn extract_cd5(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
+5 -4
View File
@@ -5,6 +5,7 @@ use crate::AppContext;
use crate::utils::common;
use crate::utils::aes::decrypt_aes_ecb_auto;
use crate::formats;
use log::info;
pub struct EpkContext {
epk_version: u8,
@@ -55,21 +56,21 @@ 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>().expect("Missing context");
let ctx = ctx.downcast::<EpkContext>().map_err(|_| "Invalid context type")?;
let versions = common::read_file(&file, 1712, 36)?;
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);
info!("Platform version: {}\nSDK version: {}", platform_version, sdk_version);
file.seek(std::io::SeekFrom::Start(0))?;
if ctx.epk_version == 2 {
println!("EPK2 detected!\n");
info!("EPK2 detected!\n");
formats::epk2::extract_epk2(app_ctx, Box::new(()))?;
} else if ctx.epk_version == 3 {
println!("EPK3 detected!\n");
info!("EPK3 detected!\n");
formats::epk3::extract_epk3(app_ctx, Box::new(()))?;
}
+7 -6
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub fn is_epk1_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)};
@@ -30,10 +31,10 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let init_pak_count = u32::from_le_bytes(init_pak_count_bytes.try_into().unwrap());
if init_pak_count > 256 {
println!("\nBig endian EPK1 detected.");
info!("\nBig endian EPK1 detected.");
epk1_type = Epk1Type::BigEndian;
} else if init_pak_count < 33 {
println!("\nLittle endian EPK1 detected.");
info!("\nLittle endian EPK1 detected.");
epk1_type = Epk1Type::LittleEndian;
} else {
return Err("Unknown EPK1 variant!".into());
@@ -60,7 +61,7 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let version = common::read_exact(&mut file, 4)?;
println!("EPK info -\nData size: {}\nPak count: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
info!("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 == Epk1Type::LittleEndian {
@@ -91,7 +92,7 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let ota_id_bytes = common::read_exact(&mut file, 32)?;
let ota_id = common::string_from_bytes(&ota_id_bytes);
println!("EPK info -\nData size: {}\nHeader size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
info!("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]);
}
@@ -99,7 +100,7 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
file.seek(SeekFrom::Start(pak.offset as u64))?;
let pak_header: PakHeader = if epk1_type == Epk1Type::BigEndian {file.read_be()?} else {file.read_le()?};
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Platform: {}",
info!("\n({}/{}) - {}, Offset: {}, Size: {}, Platform: {}",
i + 1, paks.len(), pak_header.pak_name(), pak.offset, pak_header.image_size, pak_header.platform_id());
let data = common::read_exact(&mut file, pak_header.image_size as usize)?;
@@ -109,7 +110,7 @@ pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+12 -11
View File
@@ -13,6 +13,7 @@ use crate::keys;
use crate::formats::epk::find_key;
use crate::utils::aes::decrypt_aes_ecb_auto;
use include::*;
use log::info;
pub fn is_epk2_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)};
@@ -37,14 +38,14 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
//check if header is encrypted
let epak = &stored_header[0..4]; // epak magic
if epak == b"epak" {
println!("Header is not encrypted.");
info!("Header is not encrypted.");
header = stored_header;
} else {
println!("Header is encrypted...");
println!("\nFinding key...");
info!("Header is encrypted...");
info!("\nFinding key...");
//find the key, knowing that the header should start with "epak"
if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header, b"epak")? {
println!("Found valid key: {}", key_name);
info!("Found valid key: {}", key_name);
matching_key = Some(key_bytes);
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
opt_dump_dec_hdr(app_ctx, &header, "header")?;
@@ -57,7 +58,7 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("\nEPK info -\nData size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}.{:02x?}\n",
info!("\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();
@@ -65,7 +66,7 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
for i in 0..hdr.pak_count {
let pak: PakEntry = hdr_reader.read_le()?;
//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);
info!("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() });
}
@@ -82,10 +83,10 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
//the file's header was not encrypted so we dont have the key yet
if matching_key.is_none() {
println!("\nFinding key...");
info!("\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::EPK, &encrypted_header, pak.name.as_bytes())? {
println!("Found correct key: {}", key_name);
info!("Found correct key: {}", key_name);
matching_key = Some(key_bytes);
} else {
return Err("No valid key found!".into());
@@ -96,7 +97,7 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
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()?;
println!("\n({}/{}) - {}, Size: {}, Segment count: {}, Platform: {}",
info!("\n({}/{}) - {}, Size: {}, Segment count: {}, Platform: {}",
pak_n + 1, paks.len(), pak.name, pak_header.image_size, pak_header.segment_count, pak_header.platform_id());
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", pak.name));
@@ -137,14 +138,14 @@ pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
pak_header.segment_size
};
println!("- Segment {}/{} - Size: {}", i + 1, pak_header.segment_count, actual_segment_size);
info!("- 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)?;
out_file.write_all(&out_data)?;
println!("-- Saved to file!");
info!("-- Saved to file!");
}
}
+5 -4
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub fn is_epk2b_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,7 +27,7 @@ pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: EpkHeader = file.read_le()?;
println!("EPK info -\nData size: {}\nPak count: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}",
info!("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();
@@ -49,7 +50,7 @@ pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut all_segment_size = 0;
println!("\n({}/{}) - {}, Size: {}, Segment count: {}, Platform: {}",
info!("\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());
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", pak_header.pak_name()));
@@ -66,7 +67,7 @@ pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
return Err(format!("Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index).into());
}
println!("- Segment {}/{} - Size: {}", i + 1, pak_header.segment_count, pak_header.segment_size);
info!("- 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;
@@ -80,7 +81,7 @@ pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
out_file.write_all(&out_data[..segment_limit as usize])?;
println!("-- Saved to file!");
info!("-- Saved to file!");
}
}
+11 -10
View File
@@ -13,6 +13,7 @@ use crate::keys;
use crate::formats::epk::find_key;
use crate::utils::aes::decrypt_aes_ecb_auto;
use include::*;
use log::info;
pub fn is_epk3_file(_app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
Ok(None)
@@ -27,11 +28,11 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut new_type = false;
let matching_key: Option<Vec<u8>>;
println!("Finding key...");
info!("Finding key...");
// find the key, knowing that the header should start with "EPK3" (old type 128 byte signature)
if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header[128..], b"EPK3")? {
println!("Found valid key: {}", key_name);
info!("Found valid key: {}", key_name);
matching_key = Some(key_bytes);
_header_signature = &stored_header[..128];
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header[128..])?;
@@ -39,7 +40,7 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
//try for new format epk3 (new type 256 byte signature)
} else if let Some((key_name, key_bytes)) = find_key(&keys::EPK, &stored_header[256..], b"EPK3")? {
println!("Found valid key: {}", key_name);
info!("Found valid key: {}", key_name);
matching_key = Some(key_bytes);
_header_signature = &stored_header[..256];
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header[256..])?;
@@ -59,16 +60,16 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("\nEPK info -\nEPK3 type: {}\nOTA ID: {}\nVersion: {:02x?}.{:02x?}.{:02x?}.{:02x?}\nPackage Info size: {}",
info!("\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: {}",
info!("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!();
info!("");
let _platform_versions = common::read_exact(&mut file, 36)?;
let _pkg_info_signature = common::read_exact(&mut file, signature_size)?;
@@ -81,7 +82,7 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut pkg_info_reader = Cursor::new(pkg_info);
let pkg_info_hdr: PkgInfoHeader = pkg_info_reader.read_le()?;
println!("Package info list size: {}\nPackage info count: {}",
info!("Package info list size: {}\nPackage info count: {}",
pkg_info_hdr.package_info_list_size, pkg_info_hdr.package_info_count);
if new_type {let _unknown = common::read_exact(&mut pkg_info_reader, 4)?;}; //new type has additional value
@@ -90,7 +91,7 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
while (pkg_info_reader.position() as usize) < pkg_info_reader.get_ref().len() {
let mut entry: PkgInfoEntry = pkg_info_reader.read_le()?;
println!("\n({}) - {}, Size: {}, Segments: {}",
info!("\n({}) - {}, Size: {}, Segments: {}",
pak_i, entry.package_name(), entry.package_size, entry.segment_count);
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", entry.package_name()));
@@ -102,7 +103,7 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
entry = pkg_info_reader.read_le()?;
}
println!("- Segment {}/{}, Size: {}", entry.segment_index + 1, entry.segment_count, entry.segment_size);
info!("- Segment {}/{}, Size: {}", entry.segment_index + 1, entry.segment_count, entry.segment_size);
let _segment_signature = common::read_exact(&mut file, signature_size)?;
@@ -111,7 +112,7 @@ pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
out_file.write_all(&out_data[extra_segment_size..])?;
println!("-- Saved to file!");
info!("-- Saved to file!");
}
pak_i += 1;
}
+1 -1
View File
@@ -40,7 +40,7 @@ pub fn is_cmpr(data: &[u8], size: u32) -> bool {
if count > size || data_size > size {return false};
return (size - 0x20 - (count*12) - 12) == data_size;
(size - 0x20 - (count*12) - 12) == data_size
}
//image rom
+12 -11
View File
@@ -12,6 +12,7 @@ use crate::formats::funai_upg::funai_des::funai_des_decrypt;
use include::*;
use crate::keys;
use crate::utils::compression::decompress_zlib;
use log::info;
pub struct FunaiBdpContext {
key: u32,
@@ -36,12 +37,12 @@ pub fn is_funai_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, B
pub fn extract_funai_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::<FunaiBdpContext>().expect("Missing context");
let ctx = ctx.downcast::<FunaiBdpContext>().map_err(|_| "Invalid context type")?;
let mut data = Vec::new(); //to decrypt entire file
file.read_to_end(&mut data)?;
println!("Decrypting file...");
info!("Decrypting file...");
data = funai_des_decrypt(&data, ctx.key);
let mut file_reader = Cursor::new(data);
@@ -56,17 +57,17 @@ pub fn extract_funai_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(),
}
for (i, entry) in entries.iter().enumerate() {
println!("\n({}/{}) - {}, Offset: {}, Size: {}", i +1, index_entry_count, entry.name(), entry.offset, entry.size);
info!("\n({}/{}) - {}, Offset: {}, Size: {}", i +1, index_entry_count, entry.name(), entry.offset, entry.size);
file_reader.seek(SeekFrom::Start(entry.offset as u64))?;
let mut data = common::read_exact(&mut file_reader, entry.size as usize)?;
if is_cmpr(&data, entry.size) {
println!("- cmpr detected!, 'uncompressing' data...");
info!("- cmpr detected!, 'uncompressing' data...");
data = uncmpr_data(&data)?;
} else if entry.name().ends_with("_image_rom") {
println!("- Decompressing image ROM...");
info!("- Decompressing image ROM...");
data = uncomp_image_rom(&data)?;
} else {//strip partition name at start
@@ -80,7 +81,7 @@ pub fn extract_funai_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(),
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
@@ -90,13 +91,13 @@ pub fn uncmpr_data(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data_reader = Cursor::new(data);
let _part_name = read_exact(&mut data_reader, 0x20)?;
let cmpr_header: CmprHeader = data_reader.read_le()?;
println!("[cmpr] out chk: {:02x}, count: {}, data size: {}",
info!("[cmpr] out chk: {:02x}, count: {}, data size: {}",
cmpr_header.out_checksum, cmpr_header.count, cmpr_header.data_size);
let mut out_data: Vec<u8> = Vec::new();
for (i, entry) in cmpr_header.entries.iter().enumerate() {
println!("[cmpr] ({}/{}) size: {}, mode: {}, fill: {:02x}",
info!("[cmpr] ({}/{}) size: {}, mode: {}, fill: {:02x}",
i+1, cmpr_header.count, entry.size, entry.mode, entry.fill);
let mut data;
@@ -120,20 +121,20 @@ pub fn uncomp_image_rom(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Erro
let mut data_reader = Cursor::new(data);
let _part_name = read_exact(&mut data_reader, 0x20)?;
let header: ImageRomHeader = data_reader.read_le()?;
println!("[rom] count: {}", header.count);
info!("[rom] count: {}", header.count);
let mut out_data: Vec<u8> = Vec::new();
for (i, entry) in header.entries.iter().enumerate() {
let offset = header.start_offset + entry.offset;
println!("[rom] ({}/{}) offset: {}, size: {}",
info!("[rom] ({}/{}) offset: {}, size: {}",
i+1, header.count, offset, entry.size);
data_reader.seek(SeekFrom::Start(offset as u64))?;
let compr_data = read_exact(&mut data_reader, entry.size as usize)?;
println!("[rom] - Decompressing...");
info!("[rom] - Decompressing...");
let mut decomp_data = decompress_zlib(&compr_data)?;
out_data.append(&mut decomp_data);
+8 -6
View File
@@ -9,6 +9,7 @@ use std::io::{Write, Seek, SeekFrom};
use crate::utils::common;
use crate::formats::mstar_secure_old::{is_mstar_secure_old_file, extract_mstar_secure_old};
use include::*;
use log::info;
struct FunaiMstarCtx {
data_offset: u64,
@@ -38,10 +39,10 @@ pub fn is_funai_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_funai_mstar(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::<FunaiMstarCtx>().expect("Missing context");
let ctx = ctx.downcast::<FunaiMstarCtx>().map_err(|_| "Invalid context type")?;
let info = InfoStruct::from_str(ctx.info_str).unwrap();
println!("File info -\nFile code: {}\nBrand name: {}\nModel name: {}\nSoC Version: {}\nFRC Version: {}",
info!("File info -\nFile code: {}\nBrand name: {}\nModel name: {}\nSoC Version: {}\nFRC Version: {}",
info.file_code, info.brand_name, info.model_name, info.soc_version, info.frc_version);
let payloads: Vec<(&str, usize)> = vec![("SoC", info.soc_size), ("FRC60", info.frc60_size), ("FRC120", info.frc120_size)];
@@ -53,7 +54,7 @@ pub fn extract_funai_mstar(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
if size == 0 {
continue
}
println!("\n#{} - {}, Size: {}", p_i+1, name, size);
info!("\n#{} - {}, Size: {}", p_i+1, name, size);
let data = common::read_exact(&mut file, size)?;
@@ -62,7 +63,7 @@ pub fn extract_funai_mstar(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
//extract SoC which (should be) mstar_secure_old, this is just a simple container for that format ( so we will go funai_mstar -> mstar_secure_old -> mstar (DUMB?) )
if name == "SoC" {
@@ -73,11 +74,12 @@ pub fn extract_funai_mstar(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
options: app_ctx.options.clone(),
dry_run: app_ctx.dry_run,
quiet: app_ctx.quiet,
verbose: app_ctx.verbose,
};
//do check and extarct
//do check and extract
if let Some(result) = is_mstar_secure_old_file(&in_ctx)? {
println!("- Extracting mstar_secure_old...");
info!("- Extracting mstar_secure_old...");
extract_mstar_secure_old(&in_ctx, result)?;
};
+7 -6
View File
@@ -12,6 +12,7 @@ use crate::utils::common;
use include::*;
use crate::keys;
use funai_des::funai_des_decrypt;
use log::info;
pub fn is_funai_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)};
@@ -30,7 +31,7 @@ pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
let header: Header = file.read_le()?;
let mut key: Option<u32> = None;
println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count);
info!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count);
for i in 0..header.entry_count {
let entry: Entry = file.read_le()?;
@@ -47,7 +48,7 @@ pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
let decrypted = funai_des_decrypt(&data, key_u32);
if is_valid_ver_string(&decrypted) {
println!("Matched key: {}\nFirmware info: {}",
info!("Matched key: {}\nFirmware info: {}",
key_hex, common::string_from_bytes(&decrypted));
key = Some(key_u32);
break
@@ -55,14 +56,14 @@ pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
}
}
println!("\n({}/{}) - Type: {}, Size: {}", i + 1, header.entry_count, entry.entry_type, entry.entry_size);
info!("\n({}/{}) - Type: {}, Size: {}", i + 1, header.entry_count, entry.entry_type, entry.entry_size);
if entry.encryption_flag == 1 {
if let Some(key_u32) = key {
println!("- Decrypting...");
info!("- Decrypting...");
data = funai_des_decrypt(&data, key_u32);
} else {
println!("- Warning! Failed to find decryption key, saving encrypted data")
info!("- Warning! Failed to find decryption key, saving encrypted data")
}
}
@@ -72,7 +73,7 @@ pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+6 -5
View File
@@ -12,6 +12,7 @@ use crate::keys;
use crate::formats::funai_upg::funai_des::funai_des_decrypt;
use crate::formats::funai_upg::include::is_valid_ver_string;
use include::*;
use log::info;
pub fn is_funai_upg_phl_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)};
@@ -46,7 +47,7 @@ pub fn extract_funai_upg_phl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result
let decrypted = funai_des_decrypt(&data, key_u32);
if is_valid_ver_string(&decrypted[..16]) {
println!("Matched key: {}\nFirmware info: {}\nFirmware date: {}",
info!("Matched key: {}\nFirmware info: {}\nFirmware date: {}",
key_hex, common::string_from_bytes(&decrypted[..16]), common::string_from_bytes(&decrypted[16..]));
key = Some(key_u32);
break
@@ -54,14 +55,14 @@ pub fn extract_funai_upg_phl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result
}
}
println!("\n#{} - Type: {}, Size: {}", i + 1, entry.body_type, entry.size);
info!("\n#{} - Type: {}, Size: {}", i + 1, entry.body_type, entry.size);
if let Some(key_u32) = key {
println!("- Decrypting...");
info!("- Decrypting...");
data = funai_des_decrypt(&data, key_u32);
} else {
println!("- Warning! Failed to find decryption key, saving encrypted data")
info!("- Warning! Failed to find decryption key, saving encrypted data")
}
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", entry.body_type));
@@ -70,7 +71,7 @@ pub fn extract_funai_upg_phl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
+4 -3
View File
@@ -8,6 +8,7 @@ use crate::AppContext;
use crate::utils::common;
use binrw::BinReaderExt;
use include::*;
use log::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,10 +27,10 @@ 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()?;
println!("Part count: {}", table.part_count);
info!("Part count: {}", table.part_count);
for (i, part) in table.part_entries.iter().enumerate() {
println!("\n({}/{}) - {}, Offset: {}, Total size: {}, Used size: {}",
info!("\n({}/{}) - {}, Offset: {}, Total size: {}, Used size: {}",
i+1, table.part_count, part.name(), part.start, part.total_size, part.used_size);
let data = common::read_file(&file, part.start as u64, part.total_size as usize)?;
@@ -39,7 +40,7 @@ pub fn extract_gx_dvb(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+6 -5
View File
@@ -10,6 +10,7 @@ use binrw::BinReaderExt;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use crate::utils::common;
use include::*;
use log::info;
pub fn is_invincible_image_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,14 +27,14 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Res
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: Header = file.read_le()?;
println!("File info -\nKey ID: {}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nChunk count: {}\nChunk size: {}\n\nPayload Count: {}",
info!("File info -\nKey ID: {}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nChunk count: {}\nChunk size: {}\n\nPayload Count: {}",
header.file_infos[0], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.chunk_count, header.chunk_size, header.payload_count);
let mut entries: Vec<Entry> = Vec::new();
for i in 0..header.payload_count {
let entry: Entry = file.read_le()?;
println!("{}. {} - Start offset: {}, Size: {}",
info!("{}. {} - Start offset: {}, Size: {}",
i + 1, entry.name(), entry.start_offset, entry.size);
entries.push(entry);
}
@@ -65,13 +66,13 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Res
}
}
println!("\nDecrypting data...");
info!("\nDecrypting data...");
let decrypted_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &aes_key, &aes_iv)?;
let mut data_reader = Cursor::new(decrypted_data);
for (i , entry) in entries.iter().enumerate() {
println!("\n({}/{}) - {}, Size: {}, Start offset: {}", i+1, header.payload_count, entry.name(), entry.size, entry.start_offset);
info!("\n({}/{}) - {}, Size: {}, Start offset: {}", i+1, header.payload_count, entry.name(), entry.size, entry.start_offset);
let data = common::read_exact(&mut data_reader, entry.size as usize)?;
@@ -82,7 +83,7 @@ pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Res
out_file.seek(SeekFrom::Start(entry.start_offset.into()))?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+44 -42
View File
@@ -3,6 +3,8 @@
use std::io::{Seek, SeekFrom, Cursor};
use binrw::{BinRead, BinReaderExt};
use log::info;
use crate::utils::common;
#[derive(BinRead)]
@@ -100,44 +102,44 @@ pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>,
while reader.stream_position()? < blob.len() as u64 {
chunk_n += 1;
let chunk: ChunkHeader = reader.read_be()?;
if print_tree { println!("\nChunk {} - Size: {}, Value: {}", chunk_n, chunk.size, chunk.value); };
if print_tree { info!("\nChunk {} - Size: {}, Value: {}", chunk_n, chunk.size, chunk.value); };
let chunk_end = reader.stream_position()? + chunk.size as u64;
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc)
let top_descriptor: DescriptorHeader = reader.read_be()?;
if top_descriptor.tag == 0x01 {
if print_tree { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let item_id: u32 = reader.read_be()?;
if print_tree { println!(" Item ID: {}", item_id); };
if print_tree { info!(" Item ID: {}", item_id); };
//REQUIRED are items in order: OUDestinationDesc(0x03), OUDataProcessingDesc(0x07), OUGroupInfoDesc(0x13). OPTIONAL items: OUDependenciesDesc(0x04), OUDataPostProcessingDesc(0x08)
//In MSD files, no others seem to be used than the required ones. We will ignore all data after required descriptors.
let destination_descriptor: DescriptorHeader = reader.read_be()?;
if destination_descriptor.tag != 0x03 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x03, Got: 0x{:02x}!", destination_descriptor.tag).into())}
if print_tree { println!(" OUDestinationDesc(0x03) - Size: {}", destination_descriptor.size); };
if print_tree { info!(" OUDestinationDesc(0x03) - Size: {}", destination_descriptor.size); };
let out_size: u32 = reader.read_be()?;
if print_tree { println!(" Out data size: {}", out_size); };
if print_tree { info!(" Out data size: {}", out_size); };
//OUDestinationDesc needs one of OUSWFileVersionDesc(0x0B), OUPartitionVersionDesc(0x0A), OUCMACDataDesc(0x11). Their structure is the same. so we can store the type
let type_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x0B, 0xA, 0x11].contains(&type_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDestinationDesc, Expected: one of 0x0B, 0x0A, 0x11, Got: 0x{:02x}!", type_descriptor.tag).into())}
if print_tree { println!(" Type descriptor(0x{:02x}) - Size: {}", type_descriptor.tag, type_descriptor.size); };
if print_tree { info!(" Type descriptor(0x{:02x}) - Size: {}", type_descriptor.tag, type_descriptor.size); };
let destination_info: CommonDestinationInfo = reader.read_be()?;
if print_tree {
println!(" Name length: {}", destination_info.name_len);
println!(" Name: {}", destination_info.name());
println!(" Version: {}", destination_info.version);
info!(" Name length: {}", destination_info.name_len);
info!(" Name: {}", destination_info.name());
info!(" Version: {}", destination_info.version);
};
//OUDataProcessingDesc can have OUXOREncryptionDesc(0x0D), OUAESEncryptionDesc(0x0E), OUCompressionDesc(0x0F), OUSecureHashValidationDesc(0x18), OURSAValidationDesc(0x10), OUDataCopyDesc(0x16), OUKeepCurrentDataDesc(0x1E), OUCRC32ValidationDesc(0x12)
let data_processing_descriptor: DescriptorHeader = reader.read_be()?;
if data_processing_descriptor.tag != 0x07 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x07, Got: 0x{:02x}!", data_processing_descriptor.tag).into())}
if print_tree { println!(" OUDataProcessingDesc(0x07) - Size: {}", data_processing_descriptor.size); };
if print_tree { info!(" OUDataProcessingDesc(0x07) - Size: {}", data_processing_descriptor.size); };
let heading_size: u32 = reader.read_be()?;
if print_tree { println!(" Heading size: {}", heading_size); };
if print_tree { info!(" Heading size: {}", heading_size); };
let data_size: u32 = reader.read_be()?;
if print_tree { println!(" Data size: {}", data_size); };
if print_tree { info!(" Data size: {}", data_size); };
let mut aes_encryption = false;
//let mut crc32_hash: Option<u32> = None;
@@ -149,46 +151,46 @@ pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>,
if ![0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12].contains(&descriptor.tag) {return Err(format!("Unexpected descriptor type in OUDataProcessingDesc, Expected: one of 0x0D, 0x0E, 0x0F, 0x18, 0x10, 0x16, 0x1E, 0x12, Got: 0x{:02x}!", descriptor.tag).into())}
if descriptor.tag == 0x0E {
//OUAESEncryptionDesc
if print_tree { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", descriptor.size); };
if print_tree { info!(" OUAESEncryptionDesc(0x0E) - Size: {}", descriptor.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_be()?;
if print_tree {
println!(" Mode: {}", aes_encryption_desc.mode);
println!(" Key size: {}", aes_encryption_desc.key_size);
println!(" Salt size: {}", aes_encryption_desc.salt_size);
info!(" Mode: {}", aes_encryption_desc.mode);
info!(" Key size: {}", aes_encryption_desc.key_size);
info!(" Salt size: {}", aes_encryption_desc.salt_size);
};
aes_encryption = true;
}
else if descriptor.tag == 0x10 {
//OURSAValidationDesc
if print_tree { println!(" OURSAValidationDesc(0x10) - Size: {}", descriptor.size); };
if print_tree { info!(" OURSAValidationDesc(0x10) - Size: {}", descriptor.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_be()?;
if print_tree {
println!(" Mode: {}", rsa_validation_desc.mode);
println!(" Field 2: {}", rsa_validation_desc._unknown);
println!(" Signature size: {}", rsa_validation_desc.signature_size);
info!(" Mode: {}", rsa_validation_desc.mode);
info!(" Field 2: {}", rsa_validation_desc._unknown);
info!(" Signature size: {}", rsa_validation_desc.signature_size);
};
}
else if descriptor.tag == 0x12 {
//OUCRC32ValidationDesc
if print_tree { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", descriptor.size); };
if print_tree { info!(" OUCRC32ValidationDesc(0x12) - Size: {}", descriptor.size); };
let crc32: u32 = reader.read_be()?;
if print_tree { println!(" CRC32: {:02x}", crc32); };
if print_tree { info!(" CRC32: {:02x}", crc32); };
//crc32_hash = Some(crc32);
}
else if descriptor.tag == 0x18 {
//OUSecureHashValidationDesc
if print_tree { println!(" OUSecureHashValidationDesc(0x18) - Size: {}", descriptor.size); };
if print_tree { info!(" OUSecureHashValidationDesc(0x18) - Size: {}", descriptor.size); };
let secure_hash_validation_desc: OUSecureHashValidationDesc = reader.read_be()?;
if print_tree {
println!(" Mode: {}", secure_hash_validation_desc.mode);
println!(" Hash size: {}", secure_hash_validation_desc.hash_size);
println!(" Hash: {}", hex::encode(&secure_hash_validation_desc.hash));
info!(" Mode: {}", secure_hash_validation_desc.mode);
info!(" Hash size: {}", secure_hash_validation_desc.hash_size);
info!(" Hash: {}", hex::encode(&secure_hash_validation_desc.hash));
};
//secure_hash = Some(secure_hash_validation_desc.hash);
}
else {
//type not implemented, ignore the data
if print_tree { println!(" Unimplemented descriptor(0x{:02x}) - Size: {}", descriptor.tag, descriptor.size); };
if print_tree { info!(" Unimplemented descriptor(0x{:02x}) - Size: {}", descriptor.tag, descriptor.size); };
let _descriptor_data = common::read_exact(&mut reader, descriptor.size as usize);
}
}
@@ -196,9 +198,9 @@ pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>,
//OUGroupInfoDesc
let group_info_descriptor: DescriptorHeader = reader.read_be()?;
if group_info_descriptor.tag != 0x13 {return Err(format!("Unexpected descriptor type in OUUpgradeItemDesc, Expected: 0x13, Got: 0x{:02x}!", group_info_descriptor.tag).into())}
if print_tree { println!(" OUGroupInfoDesc(0x13) - Size: {}", group_info_descriptor.size); };
if print_tree { info!(" OUGroupInfoDesc(0x13) - Size: {}", group_info_descriptor.size); };
let group_id: u32 = reader.read_be()?;
if print_tree { println!(" Group ID: {}", group_id); };
if print_tree { info!(" Group ID: {}", group_id); };
//create the msd item with all infos
let msd_item = MSDItem {
@@ -220,12 +222,12 @@ pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>,
}
else if top_descriptor.tag == 0x02 {
if print_tree { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_be()?;
if print_tree {
println!(" Group ID: {}", group_desc.group_id);
println!(" Field 2: {}", group_desc.field_2);
println!(" Field 3: {}", group_desc.field_3);
info!(" Group ID: {}", group_desc.group_id);
info!(" Field 2: {}", group_desc.field_2);
info!(" Field 3: {}", group_desc.field_3);
};
//OUGroupDesc REQUIRES one of: OUSWImageVersionDesc(0x09), OUSWImageVersionExDesc(0x19), OUOptionalDataVersionDesc(0x14), OUFirmwareVersionDesc(0x15). OPTIONALLY it can also have OUDependenciesDesc
@@ -233,23 +235,23 @@ pub fn parse_ouith_blob(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>,
let version_descriptor: DescriptorHeader = reader.read_be()?;
if ![0x09, 0x19, 0x14, 0x15].contains(&version_descriptor.tag) {return Err(format!("Unexpected descriptor type in OUGroupDesc, Expected: one of 0x09, 0x19, 0x14, 0x15, Got: 0x{:02x}!", version_descriptor.tag).into())}
if version_descriptor.tag == 0x19 {
if print_tree { println!(" OUSWImageVersionExDesc(0x12) - Size: {}", version_descriptor.size); };
if print_tree { info!(" OUSWImageVersionExDesc(0x12) - Size: {}", version_descriptor.size); };
let sw_image_version_ex_desc: OUSWImageVersionExDesc = reader.read_be()?;
if print_tree {
println!(" Name length: {}", sw_image_version_ex_desc.name_len);
println!(" Name: {}", sw_image_version_ex_desc.name());
println!(" Major version: {}", sw_image_version_ex_desc.major_ver);
println!(" Minor version: {}", sw_image_version_ex_desc.minor_ver);
println!(" Date year: {}", sw_image_version_ex_desc.date_year);
println!(" Date month: {}", sw_image_version_ex_desc.date_month);
println!(" Date day: {}", sw_image_version_ex_desc.date_day);
info!(" Name length: {}", sw_image_version_ex_desc.name_len);
info!(" Name: {}", sw_image_version_ex_desc.name());
info!(" Major version: {}", sw_image_version_ex_desc.major_ver);
info!(" Minor version: {}", sw_image_version_ex_desc.minor_ver);
info!(" Date year: {}", sw_image_version_ex_desc.date_year);
info!(" Date month: {}", sw_image_version_ex_desc.date_month);
info!(" Date day: {}", sw_image_version_ex_desc.date_day);
};
info = Some(sw_image_version_ex_desc);
}
else {
//type not implemented, ignore the data
if print_tree { println!(" Unimplemented descriptor(0x{:02x}) - Size: {}", version_descriptor.tag, version_descriptor.size); };
if print_tree { info!(" Unimplemented descriptor(0x{:02x}) - Size: {}", version_descriptor.tag, version_descriptor.size); };
let _descriptor_data = common::read_exact(&mut reader, version_descriptor.size as usize);
}
+44 -42
View File
@@ -3,6 +3,8 @@
use std::io::{Cursor};
use binrw::{BinRead, BinReaderExt};
use log::info;
use crate::utils::common;
#[derive(BinRead)]
@@ -116,24 +118,24 @@ pub fn parse_blob_1_8(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
}
let top_level_descriptor_count: u32 = reader.read_be()?; //BIG ENDIAN
if print_tree { println!("\nTop level descriptor count: {}", top_level_descriptor_count); };
if print_tree { info!("\nTop level descriptor count: {}", top_level_descriptor_count); };
for _i in 0..top_level_descriptor_count {
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc)
let top_descriptor: DescriptorHeader = reader.read_be()?;
if top_descriptor.tag == 0x01 {
if print_tree { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let upgrade_item_desc: OUUpgradeItemDesc = reader.read_be()?;
if print_tree {
println!(" Item ID: {}", upgrade_item_desc.item_id);
println!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
println!(" Original size: {}", upgrade_item_desc.original_size);
println!(" Processed size: {}", upgrade_item_desc.processed_size);
println!(" Unknown: {}", upgrade_item_desc.unk);
info!(" Item ID: {}", upgrade_item_desc.item_id);
info!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
info!(" Original size: {}", upgrade_item_desc.original_size);
info!(" Processed size: {}", upgrade_item_desc.processed_size);
info!(" Unknown: {}", upgrade_item_desc.unk);
};
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if print_tree { println!(" Subdescriptor count: {}", subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", subdesc_count); };
let mut name: Option<String> = None;
//let mut crc32_hash: Option<u32> = None;
@@ -143,66 +145,66 @@ pub fn parse_blob_1_8(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_be()?;
if sub_descriptor.tag == 0x0A {
if print_tree { println!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
let partition_version_desc: OUPartitionVersionDesc = reader.read_be()?;
if print_tree {
println!(" Name length: {}", partition_version_desc.name_len);
println!(" Name: {}", partition_version_desc.name());
println!(" Version: {}", partition_version_desc.version);
info!(" Name length: {}", partition_version_desc.name_len);
info!(" Name: {}", partition_version_desc.name());
info!(" Version: {}", partition_version_desc.version);
};
name = Some(partition_version_desc.name());
}
else if sub_descriptor.tag == 0x07 {
if print_tree { println!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
let data_processing_desc: OUDataProcessingDesc = reader.read_le()?; //LITTLE ENDIAN??
if print_tree { println!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
for _i in 0..data_processing_desc.subdesc_count {
let data_processing_sub_desc: DescriptorHeader = reader.read_be()?;
if data_processing_sub_desc.tag == 0x12 {
if print_tree { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
let crc32_validation_desc: OUCRC32ValidationDesc = reader.read_be()?;
if print_tree { println!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
if print_tree { info!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
//crc32_hash = Some(crc32_validation_desc.crc32);
}
else if data_processing_sub_desc.tag == 0x10 {
if print_tree { println!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_be()?;
if print_tree {
println!(" Signature size: {}", rsa_validation_desc.signature_size);
println!(" Public key ID: {}", rsa_validation_desc.public_key_id);
println!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
info!(" Signature size: {}", rsa_validation_desc.signature_size);
info!(" Public key ID: {}", rsa_validation_desc.public_key_id);
info!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
};
}
else if data_processing_sub_desc.tag == 0x0E {
if print_tree { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_be()?;
if print_tree {
println!(" Private key ID: {}", aes_encryption_desc.private_key_id);
println!(" Salt size: {}", aes_encryption_desc.salt_size);
println!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
println!(" Processed size: {}", aes_encryption_desc.processed_size);
info!(" Private key ID: {}", aes_encryption_desc.private_key_id);
info!(" Salt size: {}", aes_encryption_desc.salt_size);
info!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
info!(" Processed size: {}", aes_encryption_desc.processed_size);
};
aes_encryption = true;
aes_salt = Some(aes_encryption_desc.salt);
}
else {
if print_tree { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
if print_tree { info!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
let _ = common::read_exact(&mut reader, data_processing_sub_desc.size as usize - 4)?;
}
}
}
else if sub_descriptor.tag == 0x13 {
if print_tree { println!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
let group_info_desc: OUGroupInfoDesc = reader.read_be()?;
if print_tree { println!(" Group ID: {}", group_info_desc.group_id); };
if print_tree { info!(" Group ID: {}", group_info_desc.group_id); };
}
else {
if print_tree { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
if print_tree { info!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
@@ -224,34 +226,34 @@ pub fn parse_blob_1_8(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
}
}
else if top_descriptor.tag == 0x02 {
if print_tree { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_be()?;
if print_tree {
println!(" Group ID: {}", group_desc.group_id);
println!(" Unknown: {}", group_desc.unknown);
info!(" Group ID: {}", group_desc.group_id);
info!(" Unknown: {}", group_desc.unknown);
};
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if print_tree { println!(" Subdescriptor count: {}", subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", subdesc_count); };
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_be()?;
if sub_descriptor.tag == 0x19 {
if print_tree { println!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
let sw_image_version_desc: OUSWImageVersionDesc = reader.read_be()?;
if print_tree {
println!(" Name length: {}", sw_image_version_desc.name_len);
println!(" Name: {}", sw_image_version_desc.name());
println!(" Major ver: {}", sw_image_version_desc.major_ver);
println!(" Minor ver: {}", sw_image_version_desc.minor_ver);
println!(" Year: {}", sw_image_version_desc.date_year);
println!(" Month: {}", sw_image_version_desc.date_month);
println!(" Day: {}", sw_image_version_desc.date_day);
info!(" Name length: {}", sw_image_version_desc.name_len);
info!(" Name: {}", sw_image_version_desc.name());
info!(" Major ver: {}", sw_image_version_desc.major_ver);
info!(" Minor ver: {}", sw_image_version_desc.minor_ver);
info!(" Year: {}", sw_image_version_desc.date_year);
info!(" Month: {}", sw_image_version_desc.date_month);
info!(" Day: {}", sw_image_version_desc.date_day);
};
info = Some(sw_image_version_desc);
} else {
if print_tree { println!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
if print_tree { info!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
+40 -38
View File
@@ -3,6 +3,8 @@
use std::io::{Cursor};
use binrw::{BinRead, BinReaderExt};
use log::info;
use crate::utils::common;
#[derive(BinRead)]
@@ -121,24 +123,24 @@ pub fn parse_blob_1_9(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
}
let top_level_descriptor_count: u32 = reader.read_le()?;
if print_tree { println!("\nTop level descriptor count: {}", top_level_descriptor_count); };
if print_tree { info!("\nTop level descriptor count: {}", top_level_descriptor_count); };
for _i in 0..top_level_descriptor_count {
//parse top level descriptor. it can only be ID 1(OUUpgradeItemDesc) or 2(OUGroupDesc) or 0x37(OUSecureDowngradeDesc)
let top_descriptor: DescriptorHeader = reader.read_le()?;
if top_descriptor.tag == 0x01 {
if print_tree { println!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUUpgradeItemDesc(0x01) - Size: {}", top_descriptor.size); };
let upgrade_item_desc: OUUpgradeItemDesc = reader.read_le()?;
if print_tree {
println!(" Item ID: {}", upgrade_item_desc.item_id);
println!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
println!(" Original size: {}", upgrade_item_desc.original_size);
println!(" Processed size: {}", upgrade_item_desc.processed_size);
println!(" Unknown: {}", upgrade_item_desc.unk);
info!(" Item ID: {}", upgrade_item_desc.item_id);
info!(" Unknown flag: {}", upgrade_item_desc.unk_flag);
info!(" Original size: {}", upgrade_item_desc.original_size);
info!(" Processed size: {}", upgrade_item_desc.processed_size);
info!(" Unknown: {}", upgrade_item_desc.unk);
};
let subdesc_count: u32 = reader.read_le()?;
if print_tree { println!(" Subdescriptor count: {}", subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", subdesc_count); };
let mut name: Option<String> = None;
//let mut crc32_hash: Option<u32> = None;
@@ -148,64 +150,64 @@ pub fn parse_blob_1_9(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_le()?;
if sub_descriptor.tag == 0x0A {
if print_tree { println!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUPartitionVersionDesc(0x0A) - Size: {}", sub_descriptor.size); };
let partition_version_desc: OUPartitionVersionDesc = reader.read_le()?;
if print_tree {
println!(" Name length: {}", partition_version_desc.name_len);
println!(" Name: {}", partition_version_desc.name());
info!(" Name length: {}", partition_version_desc.name_len);
info!(" Name: {}", partition_version_desc.name());
};
name = Some(partition_version_desc.name());
}
else if sub_descriptor.tag == 0x07 {
if print_tree { println!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUDataProcessingDesc(0x07) - Size: {}", sub_descriptor.size); };
let data_processing_desc: OUDataProcessingDesc = reader.read_le()?;
if print_tree { println!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", data_processing_desc.subdesc_count); };
for _i in 0..data_processing_desc.subdesc_count {
let data_processing_sub_desc: DescriptorHeader = reader.read_le()?;
if data_processing_sub_desc.tag == 0x12 {
if print_tree { println!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OUCRC32ValidationDesc(0x12) - Size: {}", data_processing_sub_desc.size); };
let crc32_validation_desc: OUCRC32ValidationDesc = reader.read_le()?;
if print_tree { println!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
if print_tree { info!(" CRC32: {:02x}", crc32_validation_desc.crc32); };
//crc32_hash = Some(crc32_validation_desc.crc32);
}
else if data_processing_sub_desc.tag == 0x10 {
if print_tree { println!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OURSAValidationDesc(0x10) - Size: {}", data_processing_sub_desc.size); };
let rsa_validation_desc: OURSAValidationDesc = reader.read_le()?;
if print_tree {
println!(" Signature size: {}", rsa_validation_desc.signature_size);
println!(" Public key ID: {}", rsa_validation_desc.public_key_id);
println!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
info!(" Signature size: {}", rsa_validation_desc.signature_size);
info!(" Public key ID: {}", rsa_validation_desc.public_key_id);
info!(" Signature: {}", hex::encode(&rsa_validation_desc.signature));
};
}
else if data_processing_sub_desc.tag == 0x0E {
if print_tree { println!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
if print_tree { info!(" OUAESEncryptionDesc(0x0E) - Size: {}", data_processing_sub_desc.size); };
let aes_encryption_desc: OUAESEncryptionDesc = reader.read_le()?;
if print_tree {
println!(" Salt size: {}", aes_encryption_desc.salt_size);
println!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
println!(" Processed size: {}", aes_encryption_desc.processed_size);
info!(" Salt size: {}", aes_encryption_desc.salt_size);
info!(" Salt: {}", hex::encode(&aes_encryption_desc.salt));
info!(" Processed size: {}", aes_encryption_desc.processed_size);
};
aes_encryption = true;
aes_salt = Some(aes_encryption_desc.salt);
}
else {
if print_tree { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
if print_tree { info!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", data_processing_sub_desc.tag, data_processing_sub_desc.size); };
let _ = common::read_exact(&mut reader, data_processing_sub_desc.size as usize - 4)?;
}
}
}
else if sub_descriptor.tag == 0x13 {
if print_tree { println!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUGroupInfoDesc(0x13) - Size: {}", sub_descriptor.size); };
let group_info_desc: OUGroupInfoDesc = reader.read_le()?;
if print_tree { println!(" Group ID: {}", group_info_desc.group_id); };
if print_tree { info!(" Group ID: {}", group_info_desc.group_id); };
}
else {
if print_tree { println!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
if print_tree { info!(" Unimplemented Descriptor(0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
@@ -227,36 +229,36 @@ pub fn parse_blob_1_9(blob: &[u8], print_tree: bool) -> Result<(Vec<MSDItem>, Op
}
}
else if top_descriptor.tag == 0x02 {
if print_tree { println!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUGroupDesc(0x02) - Size: {}", top_descriptor.size); };
let group_desc: OUGroupDesc = reader.read_le()?;
if print_tree { println!(" Group ID: {}", group_desc.group_id); };
if print_tree { info!(" Group ID: {}", group_desc.group_id); };
let subdesc_count: u32 = reader.read_le()?; //LITTLE ENDIAN??
if print_tree { println!(" Subdescriptor count: {}", subdesc_count); };
if print_tree { info!(" Subdescriptor count: {}", subdesc_count); };
for _i in 0..subdesc_count {
let sub_descriptor: DescriptorHeader = reader.read_le()?;
if sub_descriptor.tag == 0x19 {
if print_tree { println!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
if print_tree { info!(" OUSWImageVersionDesc(0x19) - Size: {}", sub_descriptor.size); };
let sw_image_version_desc: OUSWImageVersionDesc = reader.read_le()?;
if print_tree {
println!(" Name length: {}", sw_image_version_desc.name_len);
println!(" Name: {}", sw_image_version_desc.name());
println!(" Major ver: {}", sw_image_version_desc.major_ver);
println!(" Minor ver: {}", sw_image_version_desc.minor_ver);
info!(" Name length: {}", sw_image_version_desc.name_len);
info!(" Name: {}", sw_image_version_desc.name());
info!(" Major ver: {}", sw_image_version_desc.major_ver);
info!(" Minor ver: {}", sw_image_version_desc.minor_ver);
};
info = Some(sw_image_version_desc);
} else {
if print_tree { println!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
if print_tree { info!(" Unimplemented Descriptor (0x{:02x}) - Size: {}", sub_descriptor.tag, sub_descriptor.size); };
let _ = common::read_exact(&mut reader, sub_descriptor.size as usize - 4)?;
}
}
}
else if top_descriptor.tag == 0x37 {
if print_tree { println!("OUSecureDowngradeDesc(0x37) - Size: {}", top_descriptor.size); };
if print_tree { info!("OUSecureDowngradeDesc(0x37) - Size: {}", top_descriptor.size); };
let secure_downgrade_desc: OUSecureDowngradeDesc = reader.read_le()?;
if print_tree { println!(" Image generation timestamp: {}", secure_downgrade_desc.image_generation_date); };
if print_tree { info!(" Image generation timestamp: {}", secure_downgrade_desc.image_generation_date); };
}
else {
return Err(format!("Unexpected top level descriptor type 0x{:02x}!", top_descriptor.tag).into());
+16 -15
View File
@@ -14,6 +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;
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)};
@@ -30,29 +31,29 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: FileHeader = file.read_le()?;
println!("\nNumber of sections: {}", header.section_count);
info!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<SectionEntry> = Vec::new();
for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
info!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(section);
}
let _zero_padding = common::read_exact(&mut file, 4)?;
let header_count: u32 = file.read_le()?;
println!("\nNumber of headers: {}", header_count);
info!("\nNumber of headers: {}", header_count);
let mut headers: Vec<HeaderEntry> = Vec::new();
for i in 0..header_count {
let header: HeaderEntry = file.read_le()?;
println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
info!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
headers.push(header);
}
//use first header
let firmware_name = &headers[0].name();
println!("\nFirmware name: {}", firmware_name);
info!("\nFirmware name: {}", firmware_name);
let toc_offset = headers[0].offset;
let toc_size = headers[0].size;
@@ -89,7 +90,7 @@ 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) {
println!("Using passphrase: {}", passphrase_name);
info!("Using passphrase: {}", passphrase_name);
(p, t)
} else {
return Err("No matching key found!".into());
@@ -103,7 +104,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let (items, info) = parse_blob_1_8(&toc, app_ctx.has_option("msd:print_ouith"))?;
if let Some(info) = info {
println!("\nImage info:\n{} {}.{} {}/{}/{}",
info!("\nImage info:\n{} {}.{} {}/{}/{}",
info.name(), info.major_ver, info.minor_ver, info.date_day, info.date_month, info.date_year);
}
@@ -111,7 +112,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let size = sections[i as usize].size;
let offset = sections[i as usize].offset;
println!("\n({}/{}) - {}, Size: {}",
info!("\n({}/{}) - {}, Size: {}",
i + 1, items.len(), item.name, size);
if sections[i as usize].index != item.item_id {
@@ -122,7 +123,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let out_data;
if item.aes_encryption {
println!("- Decrypting...");
info!("- Decrypting...");
let salt = item.aes_salt.as_ref().ok_or("AES salt missing!")?;
out_data = decrypt_aes_tizen(&stored_data, &passphrase_bytes, salt)?;
} else {
@@ -134,7 +135,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
@@ -145,14 +146,14 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let (items, info) = parse_ouith_blob(&toc, app_ctx.has_option("msd:print_ouith"))?;
if let Some(info) = info {
println!("\nImage info:\n{} {}.{} {}/{}/20{}",
info!("\nImage info:\n{} {}.{} {}/{}/20{}",
info.name(), info.major_ver, info.minor_ver, info.date_day, info.date_month, info.date_year);
}
for (i, item) in items.iter().enumerate() {
let offset = sections[i as usize].offset;
let type_str = if item.item_type == 0x0A {"Partition"} else if item.item_type == 0x0B {"File"} else if item.item_type == 0x11 {"CMAC Data"} else {"Unknown"};
println!("\n({}/{}) - {}, Type: {}, Size: {}",
info!("\n({}/{}) - {}, Type: {}, Size: {}",
item.item_id, items.len(), item.name, type_str, item.all_size);
if sections[i as usize].index != item.item_id {
@@ -164,7 +165,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
if app_ctx.has_option("msd10:save_cmac") {
out_filename = format!("{}.cmac", item.name); //add an additional extension, because the CMAC data has the same name as its item
} else {
println!("- Skipping CMAC Data...");
info!("- Skipping CMAC Data...");
continue
}
}
@@ -177,7 +178,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let stored_data = common::read_exact(&mut file, item.data_size as usize)?;
let out_data;
if item.aes_encryption {
println!("- Decrypting...");
info!("- Decrypting...");
out_data = decrypt_aes_salted_old(&stored_data, &passphrase_bytes)?;
} else {
out_data = stored_data;
@@ -188,7 +189,7 @@ pub fn extract_msd10(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
}
+11 -10
View File
@@ -13,6 +13,7 @@ use crate::keys;
use crate::formats::msd::{decrypt_aes_salted_tizen, decrypt_aes_tizen};
use crate::formats::msd::msd_ouith_parser_tizen_1_9::{parse_blob_1_9};
use include::*;
use log::info;
pub fn is_msd11_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)};
@@ -29,28 +30,28 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: FileHeader = file.read_le()?;
println!("\nNumber of sections: {}", header.section_count);
info!("\nNumber of sections: {}", header.section_count);
let mut sections: Vec<SectionEntry> = Vec::new();
for _i in 0..header.section_count {
let section: SectionEntry = file.read_le()?;
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
info!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
sections.push(section);
}
let header_count: u32 = file.read_le()?;
println!("\nNumber of headers: {}", header_count);
info!("\nNumber of headers: {}", header_count);
let mut headers: Vec<HeaderEntry> = Vec::new();
for i in 0..header_count {
let header: HeaderEntry = file.read_le()?;
println!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
info!("Header {}: {}, offset: {}, size: {}", i + 1, header.name(), header.offset, header.size);
headers.push(header);
}
//use first header
let firmware_name = &headers[0].name();
println!("\nFirmware name: {}", firmware_name);
info!("\nFirmware name: {}", firmware_name);
let toc_offset = headers[0].offset + 8;
let toc_size = headers[0].size - 8;
@@ -72,7 +73,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
let passphrase_bytes = if let Some(p) = passphrase_bytes {
println!("Using passphrase: {}", passphrase_name);
info!("Using passphrase: {}", passphrase_name);
p
} else {
return Err("No matching key found!".into());
@@ -84,7 +85,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let (items, info) = parse_blob_1_9(&toc, app_ctx.has_option("msd:print_ouith"))?;
if let Some(info) = info {
println!("\nImage info:\n{} {}.{}",
info!("\nImage info:\n{} {}.{}",
info.name(), info.major_ver, info.minor_ver);
}
@@ -92,7 +93,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let size = sections[i as usize].size;
let offset = sections[i as usize].offset;
println!("\n({}/{}) - {}, Size: {}",
info!("\n({}/{}) - {}, Size: {}",
i + 1, items.len(), item.name, size);
if sections[i as usize].index != item.item_id {
@@ -103,7 +104,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let out_data;
if item.aes_encryption {
println!("- Decrypting...");
info!("- Decrypting...");
let salt = item.aes_salt.as_ref().ok_or("AES salt missing!")?;
out_data = decrypt_aes_tizen(&stored_data, &passphrase_bytes, salt)?;
} else {
@@ -115,7 +116,7 @@ pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+19 -24
View File
@@ -12,6 +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;
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)};
@@ -35,9 +36,9 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
let mut script_string = String::from_utf8_lossy(&script);
if script_string == "" {
if script_string.is_empty() {
//try for hisense
println!("Failed to get script at 0x0, trying 0x1000...");
info!("Failed to get script at 0x0, trying 0x1000...");
script = common::read_file(&file, 4096, 32768)?;
if let Some(pos) = script.iter().position(|x| [0x00, 0xFF].contains(x)) {
@@ -46,16 +47,15 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
script_string = String::from_utf8_lossy(&script);
if script_string == "" {
if script_string.is_empty() {
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();
let mut i = 0;
for line in &lines {
for (i, line) in lines.iter().enumerate() {
if line.starts_with("filepartload") {
let parts: Vec<&str> = line.split_whitespace().collect();
let offset = parse_number(parts[3]).unwrap_or(0);
@@ -113,7 +113,7 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
}
// try to get partname from nand/mmc/ubi writes
if lines[j].starts_with("mmc write") | lines[j].starts_with("nand write") | lines[j].starts_with("ubi write"){
if lines[j].starts_with("mmc write") || lines[j].starts_with("nand write") || lines[j].starts_with("ubi write"){
let parts: Vec<&str> = lines[j].split_whitespace().collect();
if partname == "unknown" {
partname = parts[3]
@@ -123,17 +123,16 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
j += 1;
}
println!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname);
info!("\nPart - Offset: {}, Size: {} --> {}", offset, size, partname);
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
let data = common::read_file(&file, offset, size.try_into().map_err(|_| "Size conversion failed")?)?;
let out_data;
let output_path = if partname == "unknown" {
if app_ctx.has_option("mstar:keep_unknown") {
println!("- Warning, unknown destination - saving to _unknown_{}.bin", offset);
info!("- Warning, unknown destination - saving to _unknown_{}.bin", offset);
Path::new(&app_ctx.output_dir).join(format!("_unknown_{}.bin", offset))
} else {
println!("- Warning, unknown destination - skipping...");
i += 1;
info!("- Warning, unknown destination - skipping...");
continue;
}
} else {
@@ -141,27 +140,25 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
};
if compression == CompressionType::Lzma {
println!("- Decompressing LZMA...");
info!("- Decompressing LZMA...");
out_data = decompress_lzma(&data)?;
} else if compression == CompressionType::DoubleLzma {
println!("- Decompressing LZMA (Pass 1)...");
info!("- Decompressing LZMA (Pass 1)...");
let pass_1 = decompress_lzma(&data)?;
println!("- Decompressing LZMA (Pass 2)...");
info!("- Decompressing LZMA (Pass 2)...");
out_data = decompress_lzma(&pass_1)?;
} else if compression == CompressionType::Lz4 {
println!("- Decompressing lz4, expected size: {}", lz4_expect_size);
info!("- Decompressing lz4, expected size: {}", lz4_expect_size);
out_data = decompress_lz4(&data, lz4_expect_size.try_into().unwrap())?;
} else if compression == CompressionType::Lzo {
println!("- Decompressing LZO..");
info!("- Decompressing LZO..");
unlzop_to_file(&data, output_path)?;
println!("-- Saved file!");
i += 1;
info!("-- Saved file!");
continue
} else if compression == CompressionType::Sparse {
println!("- Unsparsing...");
info!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
i += 1;
info!("-- Saved file!");
continue
} else {
out_data = data;
@@ -170,10 +167,8 @@ pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box
fs::create_dir_all(&app_ctx.output_dir)?;
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
i += 1;
}
Ok(())
+5 -3
View File
@@ -11,6 +11,7 @@ use crate::utils::common;
use crate::utils::aes::decrypt_aes128_ecb;
use crate::formats::mstar::{extract_mstar, is_mstar_file};
use include::*;
use log::info;
pub struct MstarSecureCtx {
dec_footer: Vec<u8>,
@@ -34,14 +35,14 @@ pub fn is_mstar_secure_old_file(app_ctx: &AppContext) -> Result<Option<Box<dyn A
pub fn extract_mstar_secure_old(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::<MstarSecureCtx>().expect("Missing context");
let ctx = ctx.downcast::<MstarSecureCtx>().map_err(|_| "Invalid context type")?;
let hdr: ChunkFileFooter = Cursor::new(ctx.dec_footer).read_le()?;
println!("Info -\nSegment size: {}\nFile data offset: {}\nFile data len: {}", hdr.segment_size, hdr.file_data_offset, hdr.file_data_len);
info!("Info -\nSegment size: {}\nFile data offset: {}\nFile data len: {}", hdr.segment_size, hdr.file_data_offset, hdr.file_data_len);
let enc_data = common::read_file(&mut file, hdr.file_data_offset as u64, hdr.file_data_len as usize)?;
println!("- Decrypting...");
info!("- Decrypting...");
let dec_data = decrypt_aes128_ecb(&MSTAR_DEFAULT_UPGRADE_KEY, &enc_data)?;
let output_path = Path::new(&app_ctx.output_dir).join("_decrypted.bin");
@@ -57,6 +58,7 @@ pub fn extract_mstar_secure_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Resu
options: app_ctx.options.clone(),
dry_run: app_ctx.dry_run,
quiet: app_ctx.quiet,
verbose: app_ctx.verbose,
};
//do check just in case and extract
+2 -2
View File
@@ -10,7 +10,7 @@ pub static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
#[derive(BinRead)]
pub struct PITITPITEntry {
pub nand_size: u32,
pub nand_size: u32,
pub pit_offset: u32,
pub pit_size: u32,
_table_id: u32,
@@ -18,7 +18,7 @@ pub struct PITITPITEntry {
#[derive(BinRead)]
pub struct PITITBITEntry {
pub bit_offset: u32,
pub bit_offset: u32,
pub bit_size: u32,
_private_data_1: u32,
_private_data_2: u32,
+12 -11
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub struct MtkBdpContext {
pitit_offset: u64,
@@ -34,10 +35,10 @@ pub fn is_mtk_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
pub fn extract_mtk_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::<MtkBdpContext>().expect("Missing context");
let ctx = ctx.downcast::<MtkBdpContext>().map_err(|_| "Invalid context type")?;
let offset = ctx.pitit_offset;
println!("\nReading PITIT at: {}", offset);
info!("\nReading PITIT at: {}", offset);
file.seek(SeekFrom::Start(offset + 8))?;
@@ -53,11 +54,11 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
if pitit_ver == 1 {
//old PITIT does not have BIT entry, because BIT appears directly after PITIT
let pitit_bit_entry: PITITBITEntry = file.read_le()?;
println!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}, BIT Offset: {}, BIT Size: {}",
info!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}, BIT Offset: {}, BIT Size: {}",
pitit_pit_entry.nand_size, pitit_pit_entry.pit_offset, pitit_pit_entry.pit_size, pitit_bit_entry.bit_offset, pitit_bit_entry.bit_size);
if bit_offset == 0 { bit_offset = pitit_bit_entry.bit_offset as u64 } //use the first entry in PITIT
} else {
println!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}",
info!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}",
pitit_pit_entry.nand_size, pitit_pit_entry.pit_offset, pitit_pit_entry.pit_size);
}
if pit_offset == 0 { pit_offset = pitit_pit_entry.pit_offset as u64 } //use the first entry in PITIT
@@ -67,24 +68,24 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
bit_offset = file.stream_position()?;
}
println!("\nReading PIT at: {}", pit_offset); //PIT is the NAND partition table.
info!("\nReading PIT at: {}", pit_offset); //PIT is the NAND partition table.
file.seek(SeekFrom::Start(pit_offset))?;
let mut pit_entries: Vec<PITEntry> = Vec::new();
let pit_header: PITHeader = file.read_le()?;
if pit_header.pit_magic != PIT_MAGIC {
return Err("Invalid PIT magic!".into());
}
println!("PIT Info - First entry offs: {}, Entry size: {}, Entry count: {}", pit_header.first_entry_offset, pit_header.entry_size, pit_header.entry_count);
info!("PIT Info - First entry offs: {}, Entry size: {}, Entry count: {}", pit_header.first_entry_offset, pit_header.entry_size, pit_header.entry_count);
file.seek(SeekFrom::Start(pit_offset + pit_header.first_entry_offset as u64))?;
for i in 0..pit_header.entry_count {
let pit_entry: PITEntry = file.read_le()?;
println!("{}. ID: {:02x}, Name: {}, NAND Offset: {}, NAND Size: {}",
info!("{}. ID: {:02x}, Name: {}, NAND Offset: {}, NAND Size: {}",
i + 1, pit_entry.partition_id, pit_entry.name(), pit_entry.offset_on_nand, pit_entry.size_on_nand);
pit_entries.push(pit_entry);
}
println!("\nReading BIT at: {}", bit_offset); //BIT is the table of objects present in the update file.
info!("\nReading BIT at: {}", bit_offset); //BIT is the table of objects present in the update file.
file.seek(SeekFrom::Start(bit_offset))?;
let mut bit_entries: Vec<BITEntry> = Vec::new();
let bit_magic = common::read_exact(&mut file, 20)?;
@@ -96,7 +97,7 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
loop {
let bit_entry: BITEntry = file.read_le()?;
if bit_entry.partition_id == BIT_END_MARKER {break};
println!("{}. ID: {:02x}, Offset: {}, Size: {}, Offset in part: {}",
info!("{}. ID: {:02x}, Offset: {}, Size: {}, Offset in part: {}",
bit_i + 1, bit_entry.partition_id, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
bit_entries.push(bit_entry);
bit_i += 1;
@@ -112,7 +113,7 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
}
}
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Offset in partition: {}",
info!("\n({}/{}) - {}, Offset: {}, Size: {}, Offset in partition: {}",
i + 1, bit_entries.len(), name, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
let data = common::read_file(&file, bit_entry.offset as u64, bit_entry.size as usize)?;
@@ -123,7 +124,7 @@ pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
out_file.seek(SeekFrom::Start(bit_entry.offset_in_target_part as u64))?;
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+4 -4
View File
@@ -2,8 +2,8 @@ use crate::utils::common;
use binrw::BinRead;
pub static HEADER_KEY: [u8; 16] = [
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
];
pub static HEADER_IV: [u8; 16] = [0x00; 16];
@@ -24,7 +24,7 @@ pub struct Header {
pub vendor_magic_bytes: [u8; 4],
_mtk_magic: [u8; 8], //#DH@FiRm
version_bytes: [u8; 60],
pub file_size: u32,
pub file_size: u32,
_flags: [u8; 4], // 3rd is possibly the compression type in new mtk pkg
product_name_bytes: [u8; 32],
}
@@ -44,7 +44,7 @@ impl Header {
#[derive(BinRead)]
pub struct PartEntry {
name_bytes: [u8; 4],
pub flags: u32,
pub flags: u32,
pub size: u32,
}
impl PartEntry {
+10 -9
View File
@@ -1,3 +1,4 @@
use log::info;
use std::fs::OpenOptions;
use std::io::{Write, Cursor, Seek, SeekFrom, Read};
use binrw::{BinRead, BinReaderExt};
@@ -39,7 +40,7 @@ pub fn decompress_mtk_to_file(data: &[u8], output_file: &PathBuf) -> Result<(),
let segment_header: LzhsHeader = data_reader.read_le()?;
let comp_header: LzhsHeader = data_reader.read_le()?;
println!("[cmp] Segment {} - Compressed size: {}, Decompressed size: {}",
info!("[cmp] Segment {} - Compressed size: {}, Decompressed size: {}",
segment_header.checksum_or_seg_idx, comp_header.compressed_size, comp_header.uncompressed_size);
let compressed_data = common::read_exact(&mut data_reader, comp_header.compressed_size as usize)?;
@@ -71,7 +72,7 @@ pub fn decompress_mtk_to_file(data: &[u8], output_file: &PathBuf) -> Result<(),
} else {
//normal variant
println!("- Decompressing {:?}...", compression_type);
info!("- Decompressing {:?}...", compression_type);
if compression_type == CompressionType::LZ4 {
out_data = decompress_lz4(&compressed_data, comp_header.uncompressed_size as i32)?;
}
@@ -84,12 +85,12 @@ pub fn decompress_mtk_to_file(data: &[u8], output_file: &PathBuf) -> Result<(),
arm_thumb_convert(&mut out_data, 0, false);
let checksum = calc_checksum(&out_data);
println!("-- Calculated checksum: 0x{:02x?}", checksum);
info!("-- Calculated checksum: 0x{:02x?}", checksum);
if u32::from(checksum) != comp_header.checksum_or_seg_idx {
println!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", comp_header.checksum_or_seg_idx, checksum);
info!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", comp_header.checksum_or_seg_idx, checksum);
return Err("LZHS checksum mismatch".into());
} else {
println!("--- Checksum OK!")
info!("--- Checksum OK!")
}
}
else {
@@ -119,7 +120,7 @@ pub fn decompress_mtk_to_file_old(data: &[u8], output_file: &PathBuf) -> Result<
let _segment_header: LzhsOldSegmentHdr = data_reader.read_le()?;
let lzhs_header: LzhsHeader = data_reader.read_le()?;
println!("[LZHS] Segment - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
info!("[LZHS] Segment - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx);
let compressed_data = common::read_exact(&mut data_reader, lzhs_header.compressed_size as usize)?;
@@ -129,12 +130,12 @@ pub fn decompress_mtk_to_file_old(data: &[u8], output_file: &PathBuf) -> Result<
arm_thumb_convert(&mut out_data, 0, false);
let checksum = calc_checksum(&out_data);
println!("-- Calculated checksum: 0x{:02x?}", checksum);
info!("-- Calculated checksum: 0x{:02x?}", checksum);
if u32::from(checksum) != lzhs_header.checksum_or_seg_idx {
println!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", lzhs_header.checksum_or_seg_idx, checksum);
info!("--- Checksum mismatch! Expected: 0x{:02x?}, Got: 0x{:02x?}!", lzhs_header.checksum_or_seg_idx, checksum);
return Err("LZHS checksum mismatch".into());
} else {
println!("--- Checksum OK!")
info!("--- Checksum OK!")
}
out_file.write_all(&out_data)?;
+12 -11
View File
@@ -15,6 +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};
pub struct MtkPkgContext {
is_philips_variant: bool,
@@ -43,7 +44,7 @@ pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box
pub fn extract_mtk_pkg(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::<MtkPkgContext>().expect("Missing context");
let ctx = ctx.downcast::<MtkPkgContext>().map_err(|_| "Invalid context type")?;
let file_size = file.metadata()?.len();
let header = ctx.decrypted_header;
@@ -52,7 +53,7 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
info!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
if ctx.is_philips_variant {
@@ -67,13 +68,13 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
let part_entry: PartEntry = file.read_le()?;
if !part_entry.is_valid() {break};
println!("\n#{} - {}, Size: {}{} {}",
info!("\n#{} - {}, Size: {}{} {}",
part_n, part_entry.name(), part_entry.size, if part_entry.is_compressed() {" [COMPRESSED]"} else {""}, if part_entry.is_encrypted() {"[ENCRYPTED]"} else {""} );
let data = common::read_exact(&mut file, part_entry.size as usize + CRYPTED_HEADER_SIZE)?;
if part_entry.size == 0 {
println!("- Empty entry, skipping!");
info!("- Empty entry, skipping!");
continue
}
@@ -91,7 +92,7 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
}
let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key, &HEADER_IV)?;
if try_decrypt.starts_with(MTK_RESERVED_MAGIC) {
println!("- Decrypting with 4xVendor magic...");
info!("- Decrypting with 4xVendor magic...");
matching_key = Some(key);
matching_iv = Some(HEADER_IV);
@@ -103,7 +104,7 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
let try_decrypt = decrypt_aes128_cbc_nopad(&crypted_header, &key_array, &iv_array)?;
if try_decrypt.starts_with(MTK_RESERVED_MAGIC) {
println!("- Decrypting with key {}...", name);
info!("- Decrypting with key {}...", name);
matching_key = Some(key_array);
matching_iv = Some(iv_array);
break
@@ -121,7 +122,7 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
out_data.push(b ^ key_array[i % key_array.len()]);
}
} else {
println!("- Failed to decrypt data!");
info!("- Failed to decrypt data!");
continue
}
} else {
@@ -134,7 +135,7 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
if imtk_len != 0 && &out_data[56..60] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap());
let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
println!("- Version: {}", version);
info!("- Version: {}", version);
}
imtk_len + 8
} else {
@@ -148,18 +149,18 @@ pub fn extract_mtk_pkg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
if part_entry.is_compressed() {
match decompress_mtk_to_file(&fin_data, &output_path) {
Ok(()) => {
println!("-- Decompressed Successfully, Saved file!");
info!("-- Decompressed Successfully, Saved file!");
continue
},
Err(e) => {
eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
warn!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
}
}
}
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&fin_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
+11 -10
View File
@@ -14,6 +14,7 @@ use crate::keys;
use crate::formats::mtk_pkg::lzhs::{decompress_mtk_to_file};
use crate::formats::mtk_pkg::include::{Header, PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
use include::*;
use log::{info, warn};
pub struct MtkPkgNewContext {
matching_key_name: String,
@@ -46,12 +47,12 @@ pub fn is_mtk_pkg_new_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_mtk_pkg_new(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::<MtkPkgNewContext>().expect("Missing context");
let ctx = ctx.downcast::<MtkPkgNewContext>().map_err(|_| "Invalid context type")?;
let file_size = file.metadata()?.len();
//the key was founf, and header was decrypted at detection stage so we can reuse
println!("Using key {}", ctx.matching_key_name);
info!("Using key {}", ctx.matching_key_name);
let key_array = ctx.matching_key_key;
let iv_array = ctx.matching_key_iv;
let header = ctx.decrypted_header;
@@ -60,7 +61,7 @@ pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
info!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
file.seek(SeekFrom::Start(HEADER_SIZE as u64))?;
@@ -73,19 +74,19 @@ pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
break
}
println!("\n#{} - {}, Size: {}{} {}",
info!("\n#{} - {}, Size: {}{} {}",
part_n, part_entry.name(), part_entry.size, if part_entry.is_compressed() {" [COMPRESSED]"} else {""}, if part_entry.is_encrypted() {"[ENCRYPTED]"} else {""} );
let data = common::read_exact(&mut file, part_entry.size as usize + 48)?;
if part_entry.size == 0 {
println!("- Empty entry, skipping!");
info!("- Empty entry, skipping!");
continue
}
let mut out_data;
if part_entry.is_encrypted() {
println!("- Decrypting...");
info!("- Decrypting...");
//data aligned to 16 bytes is AES encrypted. the remaining unaligned data is XORed with the key
let align_len = data.len() & !15;
let (aes_enc, xor_tail) = data.split_at(align_len);
@@ -103,7 +104,7 @@ pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
if imtk_len != 0 && &out_data[56..60] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[56..60].try_into().unwrap());
let version = common::string_from_bytes(&out_data[60..60 + version_len as usize]);
println!("- Version: {}", version);
info!("- Version: {}", version);
}
imtk_len + 8
} else {
@@ -118,18 +119,18 @@ pub fn extract_mtk_pkg_new(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
if part_entry.is_compressed() {
match decompress_mtk_to_file(&fin_data, &output_path) {
Ok(()) => {
println!("-- Decompressed Successfully, Saved file!");
info!("-- Decompressed Successfully, Saved file!");
continue
},
Err(e) => {
eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
warn!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
}
}
}
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&fin_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+9 -8
View File
@@ -14,6 +14,7 @@ use crate::formats::mtk_pkg::lzhs::{decompress_mtk_to_file_old};
use crate::formats::mtk_pkg::include::{PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
use mtk_crypto::{decrypt};
use include::*;
use log::{info, warn};
pub struct MtkPkgOldContext {
header_offset: u64,
@@ -39,7 +40,7 @@ pub fn is_mtk_pkg_old_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_mtk_pkg_old(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::<MtkPkgOldContext>().expect("Missing context");
let ctx = ctx.downcast::<MtkPkgOldContext>().map_err(|_| "Invalid context type")?;
let file_size = file.metadata()?.len();
@@ -51,7 +52,7 @@ pub fn extract_mtk_pkg_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
info!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
let mut part_n = 0;
@@ -59,14 +60,14 @@ pub fn extract_mtk_pkg_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
part_n += 1;
let part_entry: PartEntry = file.read_le()?;
println!("\n#{} - {}, Size: {}{} {}",
info!("\n#{} - {}, Size: {}{} {}",
part_n, part_entry.name(), part_entry.size, if part_entry.is_compressed() {" [COMPRESSED]"} else {""}, if part_entry.is_encrypted() {"[ENCRYPTED]"} else {""} );
let data = common::read_exact(&mut file, part_entry.size as usize)?;
let out_data;
if part_entry.is_encrypted() {
//decrypt with the vendor magic
println!("- Decrypting...");
info!("- Decrypting...");
let vendor_magic_u32 = u32::from_le_bytes(hdr.vendor_magic_bytes.clone().try_into().unwrap());
out_data = decrypt(&data, vendor_magic_u32, Some(CONTENT_XOR_MASK));
} else {
@@ -79,7 +80,7 @@ pub fn extract_mtk_pkg_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
if imtk_len != 0 && &out_data[8..12] != MTK_META_PAD_MAGIC {
let version_len = u32::from_le_bytes(out_data[8..12].try_into().unwrap());
let version = common::string_from_bytes(&out_data[12..12 + version_len as usize]);
println!("- Version: {}", version);
info!("- Version: {}", version);
}
imtk_len + 8
} else {
@@ -93,18 +94,18 @@ pub fn extract_mtk_pkg_old(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
if part_entry.is_compressed() {
match decompress_mtk_to_file_old(&fin_data, &output_path) {
Ok(()) => {
println!("-- Decompressed Successfully, Saved file!");
info!("-- Decompressed Successfully, Saved file!");
continue
},
Err(e) => {
eprintln!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
warn!("Failed to decompress partition!, Error: {}. Saving compressed data...", e);
}
}
}
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&fin_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+4 -3
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub fn is_novatek_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)};
@@ -25,7 +26,7 @@ pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: Header = file.read_le()?;
println!("File info:\nFirmware name: {}\nVersion: {}.{}\nData size: {}\nPart count: {}",
info!("File info:\nFirmware name: {}\nVersion: {}.{}\nData size: {}\nPart count: {}",
header.firmware_name(), header.version_major, header.version_minor, header.data_size, header.part_count);
let mut entries: Vec<PartEntry> = Vec::new();
@@ -37,7 +38,7 @@ pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut e_i = 0;
for entry in &entries {
e_i += 1;
println!("\n({}/{}) - ID: {}, Offset: {}, Size: {}", e_i, entries.len(), entry.id, entry.offset, entry.size);
info!("\n({}/{}) - ID: {}, Offset: {}, Size: {}", e_i, entries.len(), entry.id, entry.offset, entry.size);
let data = common::read_file(&file, entry.offset as u64, entry.size as usize)?;
@@ -47,7 +48,7 @@ pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+7 -10
View File
@@ -8,6 +8,7 @@ use std::io::Write;
use crate::utils::common;
use crate::utils::aes::decrypt_aes_ecb_auto;
use crate::keys;
use log::info;
const NVT_PARTITIONS: [(&str, u32, u32); 16] = [
("spi_boot", 0x0000020, 0x00020000),
@@ -41,10 +42,6 @@ pub fn is_novatek_raw_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
return Ok(Some(Box::new(())));
}
for _ in keys::NOVATEK_RAW {
return Ok(Some(Box::new(())));
}
Ok(None)
}
@@ -66,28 +63,28 @@ pub fn extract_novatek_raw(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
let file_size = file.metadata()?.len() as usize;
let enc_data = common::read_file(&mut file, 0, file_size)?;
println!("- File size: {} bytes", file_size);
println!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" });
info!("- File size: {} bytes", file_size);
info!("- Decrypting with AES-{}...", if key.len() == 16 { "128" } else { "256" });
let dec_data = decrypt_aes_ecb_auto(&key, &enc_data)?;
println!("- Decryption complete");
info!("- Decryption complete");
fs::create_dir_all(&app_ctx.output_dir)?;
let output_path = Path::new(&app_ctx.output_dir).join("_decrypted.bin");
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&dec_data)?;
println!("- Saved decrypted image to {}", output_path.display());
info!("- Saved decrypted image to {}", output_path.display());
println!("\nExtracting partitions:");
info!("\nExtracting partitions:");
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];
let part_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", name));
let mut part_file = OpenOptions::new().write(true).create(true).open(&part_path)?;
part_file.write_all(part_data)?;
println!(" {}: offset=0x{:x}, size={}", name, offset, size);
info!(" {}: offset=0x{:x}, size={}", name, offset, size);
}
}
+9 -8
View File
@@ -11,6 +11,7 @@ use crate::utils::common;
use crate::utils::compression::{decompress_gzip};
use crate::utils::sparse::{unsparse_to_file};
use include::*;
use log::info;
pub struct TimgContext {
variant: TimgVariant,
@@ -42,14 +43,14 @@ pub fn is_nvt_timg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
pub fn extract_nvt_timg(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::<TimgContext>().expect("Missing context");
let ctx = ctx.downcast::<TimgContext>().map_err(|_| "Invalid context type")?;
let timg: Box<dyn TIMG> = match ctx.variant {
TimgVariant::New => Box::new(file.read_le::<TIMG64>()?),
TimgVariant::Old => Box::new(file.read_le::<TIMG32>()?),
TimgVariant::Old2 => Box::new(file.read_le::<TIMGOld2>()?),
};
println!("File info:\nVariant: {:?}\nData size: {}", ctx.variant, timg.data_size());
info!("File info:\nVariant: {:?}\nData size: {}", ctx.variant, timg.data_size());
//position after header + data size
let end = file.stream_position()? + timg.data_size() as u64;
@@ -70,27 +71,27 @@ pub fn extract_nvt_timg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
let data = common::read_exact(&mut file, pimg.size())?;
println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}, Comment: {}",
info!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}, Comment: {}",
pimg_i, pimg.name(), pimg.size(), pimg.dest_dev(), pimg.comp_type(), pimg.comment());
let out_data;
let output_path = Path::new(&app_ctx.output_dir).join(pimg.name() + ".bin");
if pimg.comp_type() == "gzip" && data.starts_with(b"\x1F\x8B") { //additionally check for gzip header, because sometimes its deceptive
println!("- Decompressing gzip...");
info!("- Decompressing gzip...");
out_data = decompress_gzip(&data)?;
} else if pimg.comp_type() == "none" || pimg.comp_type() == "" {
out_data = data;
} else if pimg.comp_type() == "sparse" {
println!("- Unsparsing...");
info!("- Unsparsing...");
unsparse_to_file(&data, output_path)?;
println!("-- Saved file!");
info!("-- Saved file!");
continue
} else {
println!("- Warning: unsupported compression type, saving stored data!");
info!("- Warning: unsupported compression type, saving stored data!");
out_data = data;
}
@@ -98,7 +99,7 @@ pub fn extract_nvt_timg(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+1 -1
View File
@@ -1,6 +1,6 @@
use super::include::ONKYO_MAGIC;
pub fn ub_encrypte_block(input: &[u8], key: &[u8; 8]) -> Vec<u8> {
pub fn ub_encrypted_block(input: &[u8], key: &[u8; 8]) -> Vec<u8> {
let mut output = Vec::with_capacity(input.len());
let state_counter = 0;
let state_byte = 0;
+15 -14
View File
@@ -12,6 +12,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use crypto::*;
use log::info;
struct OnkyoCtx {
header_size: u32,
@@ -21,7 +22,7 @@ pub fn is_onkyo_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<d
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
let enc_inihdr = common::read_file(&file, 0, 20)?;
let dec_inihdr= ub_encrypte_block(&enc_inihdr, &HEADER_KEY);
let dec_inihdr= ub_encrypted_block(&enc_inihdr, &HEADER_KEY);
if dec_inihdr.starts_with(ONKYO_MAGIC) {
let header_size = u32::from_le_bytes(dec_inihdr[16..20].try_into().unwrap());
@@ -33,12 +34,12 @@ pub fn is_onkyo_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<d
pub fn extract_onkyo(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::<OnkyoCtx>().expect("Missing context");
let ctx = ctx.downcast::<OnkyoCtx>().map_err(|_| "Invalid context type")?;
println!("Header size: {}", ctx.header_size);
info!("Header size: {}", ctx.header_size);
let enc_hdr = common::read_exact(&mut file, ctx.header_size as usize)?;
let dec_hdr = ub_encrypte_block(&enc_hdr, &HEADER_KEY);
let dec_hdr = ub_encrypted_block(&enc_hdr, &HEADER_KEY);
opt_dump_dec_hdr(app_ctx, &dec_hdr, "header")?;
let mut hdr_rdr = Cursor::new(dec_hdr);
@@ -47,7 +48,7 @@ pub fn extract_onkyo(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<
//read info section
hdr_rdr.seek(SeekFrom::Start(hdr.pack_info_offset as u64))?;
let info: PackInfo = hdr_rdr.read_le()?;
println!("Info -\nPackage ID: {}\nVersion: {}\nEntry count: {}\nEntries in file: {}\nPack: {}/{}",
info!("Info -\nPackage ID: {}\nVersion: {}\nEntry count: {}\nEntries in file: {}\nPack: {}/{}",
info.package_id(), info.package_version(), info.entry_count, info.entries_in_file, info.pack_id, info.pack_count,);
if info.entries_in_file == 0 {
@@ -80,7 +81,7 @@ pub fn extract_onkyo(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<
}
act_ei += 1;
println!("\n({}/{}) - {}, Size: {}, Offset: {}, Pack location: {}",
info!("\n({}/{}) - {}, Size: {}, Offset: {}, Pack location: {}",
act_ei, info.entries_in_file, entry.id(), data_entry.size, data_entry.offset, entry.pack_location);
let data = common::read_file(&mut file, data_entry.offset as u64, data_entry.size as usize)?;
@@ -91,33 +92,33 @@ pub fn extract_onkyo(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<
let mut is_pack = false;
//try using standard data key
if ub_encrypte_block(&data[..16], &DATA_KEY).starts_with(ONKYO_MAGIC) {
if ub_encrypted_block(&data[..16], &DATA_KEY).starts_with(ONKYO_MAGIC) {
dec_key = Some(DATA_KEY);
//try header key, if success it means this is a package inside a package, and it should not be decrypted
} else if ub_encrypte_block(&data[..16], &HEADER_KEY).starts_with(ONKYO_MAGIC) {
} else if ub_encrypted_block(&data[..16], &HEADER_KEY).starts_with(ONKYO_MAGIC) {
is_pack = true;
//if not matched with data key, try to calc key
} else {
let calced_key = calc_key(&data[..8]);
if ub_encrypte_block(&data[..16], &calced_key).starts_with(ONKYO_MAGIC) {
if ub_encrypted_block(&data[..16], &calced_key).starts_with(ONKYO_MAGIC) {
dec_key = Some(calced_key);
}
}
if let Some(key) = dec_key {
println!("- Detected encrypted data, decrypting...");
out_data = ub_encrypte_block(&data, &key);
info!("- Detected encrypted data, decrypting...");
out_data = ub_encrypted_block(&data, &key);
out_data.drain(0..16); //remove ONKYO Encryption heading
} else if is_pack {
println!("- Inner pack detected!"); //maybe handle this...
info!("- Inner pack detected!"); //maybe handle this...
out_data = data;
} else {
println!("- Failed to decrypt data or entry is not encrypted, saving raw data...");
info!("- Failed to decrypt data or entry is not encrypted, saving raw data...");
out_data = data;
}
@@ -126,7 +127,7 @@ pub fn extract_onkyo(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
+26 -24
View File
@@ -1,3 +1,5 @@
use log::info;
mod include;
mod pana_dvd_crypto;
mod lzss;
@@ -62,7 +64,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>().expect("Missing context");
let context = ctx.downcast::<PanaDvdContext>().map_err(|_| "Invalid context type")?;
let matching_key = context.matching_key;
let mut file_entries: Vec<FileEntry> = Vec::new();
@@ -70,7 +72,7 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
//AES files can contain multiple firmwares inside of itself
if context.is_aes {
let (aes_key, aes_iv) = (context.aes_key.unwrap(), context.aes_iv.unwrap());
println!("Using key: {} + AES key: {}, IV: {}", hex::encode_upper(matching_key), hex::encode_upper(aes_key), hex::encode_upper(aes_iv));
info!("Using key: {} + AES key: {}, IV: {}", hex::encode_upper(matching_key), hex::encode_upper(aes_key), hex::encode_upper(aes_iv));
//read inner file table
let file_table = common::read_exact(&mut file, 48)?;
@@ -89,11 +91,11 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
}
} else {
println!("Using key: {}", hex::encode_upper(matching_key));
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 });
}
println!("File contains {} sub-files...", file_entries.len());
info!("File contains {} sub-files...", file_entries.len());
for (i, file_entry ) in file_entries.iter().enumerate() {
let data = common::read_file(&mut file, file_entry.offset as u64, file_entry.size as usize)?;
let dec_data = if context.is_aes {
@@ -111,7 +113,7 @@ pub fn extract_pana_dvd(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
&app_ctx.output_dir.join(format!("file_{}", i + 1))
};
println!("\nExtracting file {}/{} - Offset: {}, Size: {}, Header size: {}",
info!("\nExtracting file {}/{} - Offset: {}, Size: {}, Header size: {}",
i + 1, file_entries.len(), file_entry.offset, file_entry.size, file_entry.header_size);
extract_file(app_ctx, &mut file_reader, file_entry.header_size as u64, matching_key, output_dir)?;
@@ -133,10 +135,10 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
for i in 0..100 {
let entry: ModuleEntry = list_reader.read_le()?;
if !entry.is_valid() {break};
println!("Module {} - Name: {}, Version: {}, Model ID: {}, ID: {}, Offset: {}, Size: {}",
info!("Module {} - Name: {}, Version: {}, Model ID: {}, ID: {}, Offset: {}, Size: {}",
i + 1, entry.name(), entry.version(), entry.model_id(), entry.id(), entry.offset, entry.size);
if modules.iter().any(|m| m.offset == entry.offset ){
println!("- Duplicate module, skipping!");
info!("- Duplicate module, skipping!");
continue
}
@@ -146,7 +148,7 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
let mut mod_i = 0;
for module in &modules {
mod_i += 1;
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Checksum: {:#010x}",
info!("\n({}/{}) - {}, Offset: {}, Size: {}, Checksum: {:#010x}",
mod_i, modules.len(), module.name(), module.offset, module.size, module.data_checksum);
//if there is multiple modules with the same name, add the module ID to the outptut file to prevent collision
@@ -160,10 +162,10 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
//special treatment of MAIN
if module.name() == "MAIN" {
println!("- Extracting MAIN...");
info!("- Extracting MAIN...");
extract_main(file_reader, key, &output_path)?;
if app_ctx.has_option("pana_dvd:split_main") {
println!("\n- Splitting MAIN...");
info!("\n- Splitting MAIN...");
split_main_file(&output_path, output_folder)?;
}
continue
@@ -171,12 +173,12 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
let data = common::read_exact(file_reader, (module.size as usize + 7) & !7)?; //read to the nearest multiple of 8 (needed for unalinged data decryption)
println!("- Decrypting...");
info!("- Decrypting...");
let mut dec_data = decrypt_data(&data, &key);
dec_data.truncate(module.size as usize); //discard padding
if module.name().starts_with("DRV") {
println!("- Extracting DRIVE firmware...");
info!("- Extracting DRIVE firmware...");
dec_data = extract_drv(dec_data, &key)?;
}
@@ -184,7 +186,7 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&dec_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
@@ -193,15 +195,15 @@ fn extract_file(app_ctx: &AppContext, file_reader: &mut Cursor<Vec<u8>>, header_
fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
let main_list_hdr: MainListHeader = file_reader.read_le()?;
if main_list_hdr.entry_count() > 200 {
println!("Unsupported MAIN data, skipping!");
info!("Unsupported MAIN data, skipping!");
return Ok(())
}
println!("MAIN - Entry count: {}, Decompressed part size: {}", main_list_hdr.entry_count(), main_list_hdr.decompressed_part_size);
info!("MAIN - Entry count: {}, Decompressed part size: {}", main_list_hdr.entry_count(), main_list_hdr.decompressed_part_size);
let mut main_entries: Vec<MainListEntry> = Vec::new();
for i in 0..main_list_hdr.entry_count() {
let main_entry: MainListEntry = file_reader.read_le()?;
println!("- Entry {}/{} - Size: {}, Checksum: {:#010x}",
info!("- Entry {}/{} - Size: {}, Checksum: {:#010x}",
i + 1, main_list_hdr.entry_count(), main_entry.size, main_entry.checksum);
main_entries.push(main_entry);
}
@@ -225,12 +227,12 @@ fn extract_main(file_reader: &mut Cursor<Vec<u8>>, key: [u8; 8], output_path: &P
data.copy_from_slice(&decrypted);
}
print!("\nMAIN ({}/{}) - ", maine_i, main_entries.len());
info!("MAIN ({}/{}) - ", maine_i, main_entries.len());
let decompressed_data = decompress_data(&data)?;
main_out_file.write_all(&decompressed_data)?;
println!("-- Saved to MAIN!");
info!("-- Saved to MAIN!");
}
Ok(())
@@ -241,22 +243,22 @@ fn decompress_data(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let header: CompressedFileHeader = data_reader.read_le()?;
let compression_type = CompressionType::from(header.compression_type);
println!("Compressed size: {}, Decompressed size: {}, Compression type: {:?}({})",
info!("Compressed size: {}, Decompressed size: {}, Compression type: {:?}({})",
header.src_size, header.dest_size, compression_type, header.compression_type);
let compressed_data = common::read_exact(&mut data_reader, header.src_size as usize)?;
let mut decompressed_data;
if compression_type == CompressionType::Gzip {
println!("- Decompressing GZIP...");
info!("- Decompressing GZIP...");
let (decompressed_gzip, gzip_filename) = decompress_gzip_get_filename(&compressed_data)?;
if let Some(gzip_filename) = gzip_filename {
println!("- GZIP filename: {}", gzip_filename);
info!("- GZIP filename: {}", gzip_filename);
}
decompressed_data = decompressed_gzip;
} else if compression_type == CompressionType::Lzss {
println!("- Decompressing LZSS...");
info!("- Decompressing LZSS...");
decompressed_data = decompress_lzss(&compressed_data);
if decompressed_data.len() != header.dest_size as usize {
return Err("Decompressed size does not match size in header, decompression failed!".into());
@@ -267,7 +269,7 @@ fn decompress_data(data: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
//GzipAndLzss is not used in this context.
} else {
println!("- Unknown compression method!");
info!("- Unknown compression method!");
decompressed_data = compressed_data;
}
@@ -293,7 +295,7 @@ fn extract_drv(mut data: Vec<u8>, key: &[u8; 8]) -> Result<Vec<u8>, Box<dyn std:
let mut reader = Cursor::new(&data);
let header: DriveHeader = reader.read_le()?;
println!("- DRIVE info:\n-- Manufacturer ID: {}\n-- Model: {}\n-- Version: {}", header.manufacturer(), header.model(), header.version());
info!("- DRIVE info:\n-- Manufacturer ID: {}\n-- Model: {}\n-- Version: {}", header.manufacturer(), header.model(), header.version());
//can be compressed
let out_data = if data[header_size..].starts_with(COMPRESSED_FILE_MAGIC) {
+5 -3
View File
@@ -1,3 +1,5 @@
use log::info;
use crate::utils::common;
use std::{fs::{self, File, OpenOptions}, io::{Read, Write}, path::{Path, PathBuf}};
@@ -48,7 +50,7 @@ pub fn split_main_file(path: &PathBuf, out_path: &PathBuf) -> Result<(), Box<dyn
}
let root = root.ok_or("Failed to get root partition!")?;
println!("Root - {}", root);
info!("Root - {}", root);
let root_index = parts.iter().position(|(n, _s)| n == &root).ok_or("Root partition not found in partition list!")?;
let mut tsize: u64 = 0;
@@ -60,7 +62,7 @@ pub fn split_main_file(path: &PathBuf, out_path: &PathBuf) -> Result<(), Box<dyn
break
}
println!("- {} - Size: {}", part_name, part_size);
info!("- {} - Size: {}", part_name, part_size);
tsize += part_size;
let data = common::read_exact(&mut file, *part_size as usize)?;
@@ -76,7 +78,7 @@ pub fn split_main_file(path: &PathBuf, out_path: &PathBuf) -> Result<(), Box<dyn
if has_swup_addon {
let mut data = Vec::new();
file.read_to_end(&mut data)?;
println!("- SWUP_ADDON - Size: {}", data.len());
info!("- SWUP_ADDON - Size: {}", data.len());
let output_path = Path::new(&output_folder).join("SWUP_ADDON.bin");
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
+7 -7
View File
@@ -39,11 +39,11 @@ pub struct Header {
_magic_bytes: [u8; 8],
pub header_size: u32, //data start
pub data_size: u32,
_crc32: u32,
pub mask: u32,
_data_size_decompressed: u32,
_padding2: u32,
description_bytes: [u8; 512],
_crc32: u32,
pub mask: u32,
_data_size_decompressed: u32,
_padding2: u32,
description_bytes: [u8; 512],
}
impl Header {
pub fn description(&self) -> String {
@@ -58,8 +58,8 @@ impl Header {
pub struct FileHeader {
file_name_bytes: [u8; 60],
pub real_size: u32,
pub stored_size: u32,
pub header_size: u32,
pub stored_size: u32,
pub header_size: u32,
pub attributes: [u8; 4],
}
impl FileHeader {
+13 -11
View File
@@ -10,6 +10,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use crate::utils::aes::decrypt_aes256_ecb;
use include::*;
use log::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)};
@@ -32,24 +33,24 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let version_bytes = common::read_exact(&mut file, 28)?;
let version = common::string_from_bytes(&version_bytes);
println!("\nVersion: {}", version);
info!("\nVersion: {}", version);
if header.description() != "" { //look ugly when empty
println!("--- Description --- \n{}", header.description());
println!("-------------------");
info!("--- Description --- \n{}", header.description());
info!("-------------------");
}
println!("Data size: {}", header.data_size);
info!("Data size: {}", header.data_size);
file.seek(SeekFrom::Start(header.header_size as u64))?;
let mut data;
if header.is_encrypted() {
println!("\nFile is encrypted.");
info!("\nFile is encrypted.");
//get some data as test ciphertext for key finding
let ciphertext = common::read_file(&mut file, header.header_size as u64, 64)?;
let aes_key;
if let Some((key_name, key)) = try_find_key(&signature, &ciphertext)? {
println!("Matched pubkey: {}, AES key: {}", key_name, hex::encode(key));
info!("Matched pubkey: {}, AES key: {}", key_name, hex::encode(key));
aes_key = key;
} else {
return Err("Matching key not found, cannot decrypt data".into());
@@ -58,7 +59,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)?;
println!("Decrypting data...");
info!("Decrypting data...");
data = decrypt_aes256_ecb(&aes_key, &encrypted_data)?;
data.truncate(header.data_size as usize); //discard padding
@@ -76,7 +77,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let ex_header_bytes = common::read_exact(&mut data_reader, ex_header_size as usize)?;
if file_header.is_folder() {
println!("\nFolder - {}", file_header.file_name());
info!("\nFolder - {}", file_header.file_name());
let output_path = Path::new(&app_ctx.output_dir).join(file_header.file_name().trim_start_matches('/'));
fs::create_dir_all(output_path)?;
continue
@@ -88,7 +89,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
file_header.file_name()
};
println!("\nFile - {}, Size: {}", file_name, file_header.real_size);
info!("\nFile - {}, Size: {}", file_name, file_header.real_size);
let data = common::read_exact(&mut data_reader, file_header.stored_size as usize)?;
let output_path = Path::new(&app_ctx.output_dir).join(file_name.trim_start_matches('/'));
@@ -100,7 +101,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
//pfl upg inside pfl upg! DUMB code!
if file_header.is_package() && !app_ctx.has_option("pfl_upg:no_extract_inner_upg"){
println!("- Extracting inner UPG...");
info!("- Extracting inner UPG...");
//save this as temp file
let temp_path = Path::new(&app_ctx.output_dir).join("inner_upg_temp");
@@ -115,6 +116,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
options: app_ctx.options.clone(),
dry_run: app_ctx.dry_run,
quiet: app_ctx.quiet,
verbose: app_ctx.verbose,
};
//do check just in case and extract
@@ -132,7 +134,7 @@ pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data[..file_header.real_size as usize])?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+11 -10
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
struct PhilipsBdpCtx {
header_type: HeaderType,
@@ -31,7 +32,7 @@ pub fn is_philips_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>,
pub fn extract_philips_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::<PhilipsBdpCtx>().expect("Missing context");
let ctx = ctx.downcast::<PhilipsBdpCtx>().map_err(|_| "Invalid context type")?;
let header: Box<dyn UpgHeader> = match ctx.header_type {
HeaderType::Old => Box::new(file.read_le::<UpgHeaderOld>()?),
@@ -40,7 +41,7 @@ pub fn extract_philips_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
let header_size = file.stream_position()?;
println!("File info -\nName: {}\nVersion: {}\nTarget size: {}\nEntry count: {}\nHeader type: {:?}\nHeader size: {}",
info!("File info -\nName: {}\nVersion: {}\nTarget size: {}\nEntry count: {}\nHeader type: {:?}\nHeader size: {}",
header.name(), header.version(), header.target_size(), header.target_num(), ctx.header_type, header_size);
for (i, entry) in header.entries().iter().enumerate() {
@@ -48,14 +49,14 @@ pub fn extract_philips_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
break
}
println!("\n#{} - ID: {:x}, IIC: {:x}, Version: {}, Offset: {}, Size: {}",
info!("\n#{} - ID: {:x}, IIC: {:x}, Version: {}, Offset: {}, Size: {}",
i+1, entry.id, entry.iic, entry.version(), entry.offset, entry.size);
let data = common::read_file(&file, entry.offset as u64 + header_size, entry.size as usize)?;
let out_data;
if entry.id == 0 && app_ctx.has_option("philips_bdp:decrypt") {
println!("- Decrypting...");
info!("- Decrypting...");
out_data = bebin_decrypt_aes256cfb(&data, &KEY1, &IV1);
} else {
out_data = data;
@@ -67,23 +68,23 @@ pub fn extract_philips_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<()
let mut out_file = OpenOptions::new().write(true).create(true).open(&output_path)?;
out_file.write_all(&out_data)?;
println!("- Saved file!");
info!("- Saved file!");
//ID 0 should be the main MTK bdp file, since this is just an extra container for that format (like Sony BDP), so we can try to extract it here.
if entry.id == 0 {
println!("Checking if it's also MTK BDP...");
info!("Checking if it's also MTK BDP...");
let new_file = File::open(&output_path)?;
let mtk_ctx: AppContext = AppContext { input: InputTarget::File(new_file), output_dir: app_ctx.output_dir.join("0"), options: app_ctx.options.clone(), dry_run: app_ctx.dry_run, quiet: app_ctx.quiet };
let mtk_ctx: AppContext = AppContext { input: InputTarget::File(new_file), output_dir: app_ctx.output_dir.join("0"), options: app_ctx.options.clone(), dry_run: app_ctx.dry_run, quiet: app_ctx.quiet, verbose: app_ctx.verbose };
if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&mtk_ctx)? {
println!("- MTK BDP file detected!\n");
info!("- MTK BDP file detected!\n");
formats::mtk_bdp::extract_mtk_bdp(&mtk_ctx, result)?;
} else {
if app_ctx.has_option("philips_bdp:decrypt") {
println!("- Not an MTK BDP file");
info!("- Not an MTK BDP file");
} else {
println!("- Not an MTK BDP file (try with decrypt?)");
info!("- Not an MTK BDP file (try with decrypt?)");
}
}
}
+12 -11
View File
@@ -10,6 +10,7 @@ use std::io::{Write, Seek, SeekFrom};
use crate::utils::common;
use crate::utils::compression::{decompress_zlib};
use include::*;
use log::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)};
@@ -26,7 +27,7 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
let header: Header = file.read_le()?;
println!("File info:\nFile size: {}\nEntry count: {}",
info!("File info:\nFile size: {}\nEntry count: {}",
header.file_size, header.entry_count);
let mut entries: Vec<Entry> = Vec::new();
@@ -42,7 +43,7 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let mut e_i = 0;
for entry in &entries {
println!("\n({}/{}) - ID: {} Offset: {}, Compressed Size: {}, Uncompressed Size: {}\nCompressed: {}, Blocked: {}, 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());
if !entry.is_block_table () {
@@ -51,17 +52,17 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let block_count = (block_size + entry.uncompressed_size as u32 - 1) / block_size;
let last_block_size = entry.uncompressed_size % block_size as u64;
let mut my_block_table: Option<Entry> = None;
println!("Block size: {}, Block count: {}", block_size, block_count);
info!("Block size: {}, Block count: {}", block_size, block_count);
for block_table in &block_tables {
if block_table.id() == e_i {
my_block_table = Some(block_table.clone());
println!("Found block table: Offset: {}, Size: {}", block_table.offset, block_table.compressed_size);
info!("Found block table: Offset: {}, Size: {}", block_table.offset, block_table.compressed_size);
break
}
}
if my_block_table.is_none() {
println!("Failed to find block table!");
info!("Failed to find block table!");
continue
}
@@ -84,13 +85,13 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let ac_block_size = if i == block_count - 1 {last_block_size as u32} else {block_size};
let compressed = if data_size == ac_block_size {false} else {true};
println!("Block {}/{}: Offset: {}, Data Size: {}, Padding: {}, Compressed: {}", i + 1, block_count, block.offset, data_size, padding, compressed);
info!("Block {}/{}: Offset: {}, Data Size: {}, Padding: {}, Compressed: {}", i + 1, block_count, block.offset, data_size, padding, compressed);
file.seek(std::io::SeekFrom::Start(initial_offset + block.offset as u64))?;
let out_data;
let data = common::read_exact(&mut file, data_size as usize)?;
if compressed {
println!("- Decompressing...");
info!("- Decompressing...");
out_data = decompress_zlib(&data)?;
} else {
out_data = data;
@@ -102,7 +103,7 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved!");
info!("-- Saved!");
file.seek(std::io::SeekFrom::Start(current_pos))?;
}
@@ -112,7 +113,7 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let out_data;
if entry.is_compressed() {
println!("- Decompressing...");
info!("- Decompressing...");
out_data = decompress_zlib(&data)?;
} else {
out_data = data;
@@ -124,10 +125,10 @@ pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&out_data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
} else {
println!("- Skipping block table..")
info!("- Skipping block table..")
}
e_i += 1;
+8 -7
View File
@@ -11,6 +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;
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)};
@@ -31,7 +32,7 @@ pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut encrypted_data = Vec::new();
file.read_to_end(&mut encrypted_data)?;
println!("\nDecrypting...\n");
info!("\nDecrypting...\n");
let tar_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &FILE_KEY, &FILE_IV)?;
let tar_reader = Cursor::new(tar_data);
let mut tar_archive = Archive::new(tar_reader);
@@ -47,20 +48,20 @@ pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
entry.read_to_end(&mut contents)?;
let text = String::from_utf8_lossy(&contents[..size - 256]); //dont display signature
println!("Manifest file:\n{}", text);
info!("Manifest file:\n{}", text);
} else {
let mut contents = Vec::new();
entry.read_to_end(&mut contents)?; //entry cant seek
if contents.starts_with(b"\x00\x00\x00\x00\x00\x00\x00\x00imgARMcC") {
println!("\nImage file: {:?}:", path);
info!("\nImage file: {:?}:", path);
let size = entry.header().size()? as usize;
let mut image_reader = Cursor::new(contents);
let mut i = 1;
while image_reader.stream_position()? < size as u64 {
let image: AImageHeader = image_reader.read_le()?;
println!(" #{} - Type: {}(0x{:x}), Length: {}, encmode: {}",
info!(" #{} - Type: {}(0x{:x}), Length: {}, encmode: {}",
i, image.image_type_str(), image.image_type, image.length, image.encmode_str());
let data =
@@ -80,20 +81,20 @@ pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!(" - Saved file!\n");
info!(" - Saved file!\n");
i += 1;
}
} else {
println!("\nOther/Unknown file: {:?}", path);
info!("\nOther/Unknown file: {:?}", path);
let output_path = Path::new(&app_ctx.output_dir).join(&path);
fs::create_dir_all(&app_ctx.output_dir)?;
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&contents)?;
println!("- Saved file!");
info!("- Saved file!");
}
}
}
+3 -3
View File
@@ -5,7 +5,7 @@ use binrw::BinRead;
pub struct RufHeader {
_magic_bytes: [u8; 6],
_upgrade_type_bytes: [u8; 2],
_unk1: u32,
_unk1: u32,
date_time_bytes: [u8; 32],
buyer_bytes: [u8; 8],
model_bytes: [u8; 32],
@@ -33,7 +33,7 @@ impl RufHeader {
common::string_from_bytes(&self.region_info_bytes)
}
pub fn is_dual_ruf(&self) -> bool {
if self.dual_ruf_flag == 0x44 {true} else {false}
self.dual_ruf_flag == 0x44
}
}
@@ -41,7 +41,7 @@ impl RufHeader {
pub struct RufEntry {
_metadata: [u8; 32],
pub payload_type_bytes: u32,
pub size: u32,
pub size: u32,
_unk1: u32,
_unk2: [u8; 20],
}
+10 -9
View File
@@ -11,6 +11,7 @@ use crate::utils::common;
use crate::keys;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use include::*;
use log::info;
pub fn is_ruf_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,9 +29,9 @@ pub fn extract_ruf(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<d
let header: RufHeader = file.read_be()?;
if header.is_dual_ruf() {
println!("\nDual RUF detected! Extracting 1st RUF...\n");
info!("\nDual RUF detected! Extracting 1st RUF...\n");
actually_extract_ruf(file, &app_ctx.output_dir.join("RUF_1"), 0)?;
println!("\nExtracting 2nd RUF...\n");
info!("\nExtracting 2nd RUF...\n");
actually_extract_ruf(file, &app_ctx.output_dir.join("RUF_2"), 41943088)?;
} else {
actually_extract_ruf(file, &app_ctx.output_dir, 0)?;
@@ -43,10 +44,10 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
file.seek(SeekFrom::Start(start_offset))?;
let header: RufHeader = file.read_be()?;
println!("File info:\nBuyer: {} \nModel: {} \nRegion Info: {} \nDateTime: {}\nVersion:{:02x?} \nData Size: {} \nDual RUF: {}",
info!("File info:\nBuyer: {} \nModel: {} \nRegion Info: {} \nDateTime: {}\nVersion:{:02x?} \nData Size: {} \nDual RUF: {}",
header.buyer(), header.model(), header.region_info(), header.date_time(), header.version_bytes, header.data_size, header.is_dual_ruf());
println!("\nPayload count: {}", header.payload_count);
info!("\nPayload count: {}", header.payload_count);
file.seek(SeekFrom::Start(start_offset + header.payloads_start_offset as u64))?;
let mut entries: Vec<RufEntry> = Vec::new();
@@ -64,7 +65,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
vi += 1
}
println!("{}/{}: Type: {}({}), Size: {}",
info!("{}/{}: Type: {}({}), Size: {}",
vi, header.payload_count, entry.payload_type_bytes, entry.payload_type(), entry.size);
entries.push(entry);
@@ -82,7 +83,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
}
}
if let Some(k) = key {
println!("\nKey: {}", k);
info!("\nKey: {}", k);
key_bytes = hex::decode(k)?.as_slice().try_into()?;
} else {
return Err("This firmware is not supported!".into());
@@ -90,14 +91,14 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
file.seek(SeekFrom::Start(start_offset + 2048))?;
let encrypted_data = common::read_exact(&mut file, header.data_size as usize)?;
println!("Decrypting data...");
info!("Decrypting data...");
let decrypted_data = decrypt_aes128_cbc_pcks7(&encrypted_data, &key_bytes, &iv_bytes)?;
let mut data_reader = Cursor::new(decrypted_data);
let mut ei = 1;
for entry in entries {
println!("\n({}/{}) - {}({}), Size: {}",
info!("\n({}/{}) - {}({}), Size: {}",
ei, header.payload_count, entry.payload_type_bytes, entry.payload_type(), entry.size);
let data = common::read_exact(&mut data_reader, entry.size as usize)?;
@@ -108,7 +109,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset:
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
ei += 1;
}
+12 -11
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub struct RvpContext {
header_type: HeaderType,
@@ -35,11 +36,11 @@ pub fn is_rvp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn
pub fn extract_rvp(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::<RvpContext>().expect("Missing context");
let ctx = ctx.downcast::<RvpContext>().map_err(|_| "Invalid context type")?;
if ctx.header_type == HeaderType::RVP {
let header: RVPHeader = file.read_be()?;
println!("RVP Info -\nVersion: {}\nYear: {:x}\nForce: {}", header.version_info(), header.year, header.force);
info!("RVP Info -\nVersion: {}\nYear: {:x}\nForce: {}", header.version_info(), header.year, header.force);
} else if ctx.header_type == HeaderType::MVP {
file.seek(std::io::SeekFrom::Start(36))?;
@@ -47,14 +48,14 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
let mut obf_data = Vec::new();
file.read_to_end(&mut obf_data)?;
println!("DeXORing data..");
info!("DeXORing data..");
let data = decrypt_xor(&obf_data);
let data_size = data.len();
let mut data_reader = Cursor::new(data);
let module_count: u32 = data_reader.read_le()?; //little endian??
println!("Module count: {}", module_count);
info!("Module count: {}", module_count);
//follows table of sizes of modules, structure is static for given module
let mut module_names: Vec<&str> = Vec::new();
@@ -82,8 +83,8 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
};
let header_size_bytes = common::read_exact(&mut data_reader, 4)?;
let header_size = u32::from_be_bytes(header_size_bytes.try_into().unwrap());
println!("\n({}/{}) - {}, Offset: {}, Header size: {}", i+1, module_count, module_name, data_reader.position() - 4, header_size);
let header_size = u32::from_be_bytes(header_size_bytes.try_into().map_err(|_| "Conversion failed")?);
info!("\n({}/{}) - {}, Offset: {}, Header size: {}", i+1, module_count, module_name, data_reader.position() - 4, header_size);
let hdr = common::read_exact(&mut data_reader, header_size as usize)?;
let size;
@@ -93,7 +94,7 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
let lines: Vec<String> = hdr_string.lines().map(|l| l.trim().to_string()).collect();
//BEAUTIFUL
println!("ModelName: {}\nFileName: {}\nModelID: {}\nNewUpdate: {}\nNewMajorVer: {}\nNewMinorVer: {}\nForcedFlag: {}\nStartAddress: {}\nJumpAddress: {}\nMagicAddress: {}\nTotalSize: {}\nTotalSum: {}\nTotalCrc: {}",
info!("ModelName: {}\nFileName: {}\nModelID: {}\nNewUpdate: {}\nNewMajorVer: {}\nNewMinorVer: {}\nForcedFlag: {}\nStartAddress: {}\nJumpAddress: {}\nMagicAddress: {}\nTotalSize: {}\nTotalSum: {}\nTotalCrc: {}",
lines[0], lines[1], lines[2], lines[3], lines[4], lines[5], lines[6], lines[7], lines[8], lines[9], lines[10], lines[11], lines[12]);
size = lines[10].parse().unwrap();
@@ -116,7 +117,7 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
//5. - unknown - like "00011200" -- this line is not present when size is 40 but were not using it anyway so whatever
name = lines[0].clone();
size = u32::from_str_radix(&lines[1], 16).unwrap();
println!("Name: {}", name);
info!("Name: {}", name);
} else if header_size == 16 {
// 4 bytes CRC32
@@ -126,11 +127,11 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
size = u32::from_be_bytes(hdr[8..12].try_into().unwrap());
} else {
println!("Unsupported header size!");
info!("Unsupported header size!");
break
}
println!("Size: {}", size);
info!("Size: {}", size);
let data = common::read_exact(&mut data_reader, size as usize)?;
let output_path = Path::new(&app_ctx.output_dir).join(if name=="" {format!("{}_{}.bin", i+1, module_name)} else {format!("{}_{}_{}", i+1, module_name, name)});
@@ -138,7 +139,7 @@ pub fn extract_rvp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+9 -8
View File
@@ -5,7 +5,7 @@ use crate::AppContext;
use std::fs;
use std::path::{Path};
use std::fs::{File, OpenOptions};
use std::io::{Write};
use std::io::Write;
use hex::decode;
use sha1::{Digest, Sha1};
use md5;
@@ -14,11 +14,12 @@ use crate::utils::common;
use crate::keys;
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
use include::decrypt_xor;
use log::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)};
if Path::new(&dir).join("image").is_dir() & Path::new(&dir).join("image/info.txt").exists(){
if Path::new(&dir).join("image").is_dir() && Path::new(&dir).join("image/info.txt").exists(){
Ok(Some(Box::new(())))
} else {
Ok(None)
@@ -29,7 +30,7 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
let path = app_ctx.dir().ok_or("Extractor expected directory")?;
let fw_info = fs::read_to_string(Path::new(&path).join("image/info.txt"))?;
println!("Firmware info: {}", fw_info);
info!("Firmware info: {}", fw_info);
let image_path = Path::new(&path).join("image");
@@ -43,7 +44,7 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
}
}
if let Some(p) = secret {
println!("Secret: {}", p);
info!("Secret: {}", p);
} else {
return Err("This firmware is not supported!".into());
}
@@ -58,7 +59,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();
println!("\nFile - {}", filename);
info!("\nFile - {}", filename);
let data = common::read_file(&file, 0, file_size.try_into().unwrap())?;
let salt = &data[8..16];
@@ -103,10 +104,10 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
}
let end = file_size - 260;
println!("- Decrypting file...");
info!("- Decrypting file...");
let decrypted_data = decrypt_aes128_cbc_pcks7(&data[16..end.try_into().unwrap()], &key_md5, &iv_md5)?;
println!("-- DeXORing file...");
info!("-- DeXORing file...");
let xor_key = fw_info.split_whitespace().next().unwrap();
let out_data = decrypt_xor(&decrypted_data, xor_key);
@@ -120,7 +121,7 @@ pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(
out_file.write_all(&out_data)?;
println!("--- Saved file!");
info!("--- Saved file!");
}
}
}
+10 -9
View File
@@ -12,10 +12,11 @@ use crate::utils::common;
use crate::formats::sddl_sec::include::*;
use crate::utils::compression::decompress_zlib;
use include::*;
use log::info;
pub fn is_sdboot_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, 32).expect("Failed to read from file.");
let header = common::read_file(&file, 0, 32)?;
let deciph_header = decipher(&header);
let chk = deciph_header.iter().all(|&b| {
@@ -59,7 +60,7 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
}
let key: KeyEntry = KeyEntry::AES(KEYS[key_id as usize]);
println!("File info -\nKey ID: {}\nFile count: {}", secfile_header.key_id(), secfile_header.num_files());
info!("File info -\nKey ID: {}\nFile count: {}", secfile_header.key_id(), secfile_header.num_files());
//create file list
let mut file_list: Vec<FileEntry> = Vec::new();
@@ -84,7 +85,7 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
let target = s.next().unwrap(); //"nand"
let target_filename = s.next().unwrap(); //"nandall.img"
println!("\nSaving {} to {}...", target, target_filename);
info!("\nSaving {} to {}...", target, target_filename);
let output_path = Path::new(&app_ctx.output_dir).join(&target_filename);
let mut out_file = OpenOptions::new().write(true).create(true).truncate(true).open(&output_path)?;
@@ -95,18 +96,18 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
let info_header: InfoListHeader = infofile_reader.read_le()?;
for i in 0..info_header.part_count {
let part_entry: InfoListEntry = infofile_reader.read_le()?;
println!("- ({}/{}) Size: {}, Compressed?: {}", i+1, info_header.part_count, part_entry.out_size, part_entry.is_compressed());
info!("- ({}/{}) Size: {}, Compressed?: {}", i+1, info_header.part_count, part_entry.out_size, part_entry.is_compressed());
let part_file_name = format!("{}{:02x}", target_filename, i); //not sure what happens if it goes over 255
let mut part_data = get_file(&file, &part_file_name, &file_list, &key)?;
if part_entry.is_ciphered() {
println!("-- Deciphering...");
info!("-- Deciphering...");
part_data = decipher(&part_data);
}
if part_entry.is_compressed() {
println!("-- Decompressing...");
info!("-- Decompressing...");
part_data = decompress_zlib(&part_data)?;
}
@@ -114,7 +115,7 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
processed_image_files.insert(part_file_name);
}
println!("--- Saved file!");
info!("--- Saved file!");
}
//extract the rest of the files
@@ -123,13 +124,13 @@ pub fn extract_sdboot(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Bo
continue;
}
println!("\nFile: {} - Size: {}", entry.name, entry.size);
info!("\nFile: {} - Size: {}", entry.name, entry.size);
let file_data = get_file(&file, &entry.name, &file_list, &key)?;
let output_path = Path::new(&app_ctx.output_dir).join(&entry.name);
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
out_file.write_all(&file_data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+5 -5
View File
@@ -50,7 +50,7 @@ pub fn decipher(s: &[u8]) -> Vec<u8> {
pub fn decrypt_3des(encrypted_data: &[u8], key_entry: &DesKeyEntry) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut data = encrypted_data.to_vec();
let decryptor = Decryptor::<TdesEde3>::new_from_slices(&key_entry.key, &key_entry.iv).unwrap();
let decryptor = Decryptor::<TdesEde3>::new_from_slices(&key_entry.key, &key_entry.iv).map_err(|e| format!("Invalid 3DES key: {}", e))?;
let out_data = decryptor.decrypt_padded_mut::<NoPadding>(&mut data)
.map_err(|e| format!("!!Decryption error!!: {:?}", e))?;
@@ -142,15 +142,15 @@ pub struct SecHeader {
impl SecHeader {
pub fn key_id(&self) -> u32 {
let string = string_from_bytes(&self.key_id_str_bytes);
string.parse().unwrap()
string.parse::<u32>().expect("Invalid key_id in sec header")
}
pub fn grp_num(&self) -> u32 {
let string = string_from_bytes(&self.grp_num_str_bytes);
string.parse().unwrap()
string.parse::<u32>().expect("Invalid grp_num in sec header")
}
pub fn prg_num(&self) -> u32 {
let string = string_from_bytes(&self.prg_num_str_bytes);
string.parse().unwrap()
string.parse::<u32>().expect("Invalid prg_num in sec header")
}
}
@@ -167,7 +167,7 @@ impl FileHeader {
}
pub fn size(&self) -> u64 {
let string = string_from_bytes(&self.size_str_bytes);
string.parse().unwrap()
string.parse::<u64>().expect("Invalid size in file header")
}
}
+22 -21
View File
@@ -15,10 +15,11 @@ use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
use crate::utils::compression::{decompress_zlib};
use include::*;
use util::split_peaks_file;
use log::info;
pub fn is_sddl_sec_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, 32).expect("Failed to read from file.");
let header = common::read_file(&file, 0, 32)?;
let deciph_header = decipher(&header);
if deciph_header.starts_with(b"\x11\x22\x33\x44") {
Ok(Some(Box::new(())))
@@ -43,7 +44,7 @@ fn get_sec_file(mut file: &File, key_entry: &KeyEntry) -> Result<(FileHeader, Ve
} else {
//extra ciphered data before encrypted data, prefixed by size, like "0021XXXXXX PEAKS.T00/12900002"
//this counts into file size but not decrypt size
let extra_size: usize = string_from_bytes(&read_exact(&mut file, 4)?).parse().unwrap();
let extra_size: usize = string_from_bytes(&read_exact(&mut file, 4)?).parse::<usize>().map_err(|e| format!("Parse error: {}", e))?;
let _extra_data = read_exact(&mut file, extra_size)?;
file_header.size() as usize - (extra_size + 4)
@@ -57,10 +58,10 @@ fn get_sec_file(mut file: &File, key_entry: &KeyEntry) -> Result<(FileHeader, Ve
//extra info in enc data, like "0021XXXXXX PEAKS.T00/12900002000000571800"
//part before size looks to be a duplicate of previous extra data, probably for signing purpose, size used for unpad
let extra_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 4)?).parse().unwrap();
let extra_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 4)?).parse::<usize>().map_err(|e| format!("Parse error: {}", e))?;
let _extra_data = read_exact(&mut data_rdr, extra_size + 4)?;
let data_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 12)?).parse().unwrap();
let data_size: usize = string_from_bytes(&read_exact(&mut data_rdr, 12)?).parse::<usize>().map_err(|e| format!("Parse error: {}", e))?;
read_exact(&mut data_rdr, data_size)?
};
@@ -77,16 +78,16 @@ fn parse_tdi_to_modules(tdi_data: Vec<u8>) -> Result<Vec<TdiTgtInf>, Box<dyn std
return Err(format!("Unsupported TDI format version {}! (The supported version is {})", tdi_header.format_version, SUPPORTED_TDI_VERSION).into());
}
println!("[TDI] Group count: {}", tdi_header.num_of_group);
info!("[TDI] Group count: {}", tdi_header.num_of_group);
let mut modules: Vec<TdiTgtInf> = Vec::new();
for _i in 0..tdi_header.num_of_group {
let group_head: TdiGroupHead = tdi_reader.read_be()?;
println!("[TDI] Group ID: {}, Target count: {}", group_head.group_id, group_head.num_of_target);
info!("[TDI] Group ID: {}, Target count: {}", group_head.group_id, group_head.num_of_target);
for _i in 0..group_head.num_of_target {
let tgt_inf: TdiTgtInf = tdi_reader.read_be()?;
println!("[TDI] - {}, Target ID: {}, Segment count: {}, Version: {}",
info!("[TDI] - {}, Target ID: {}, Segment count: {}, Version: {}",
tgt_inf.module_name(), tgt_inf.target_id, tgt_inf.num_of_txx, tgt_inf.version_string());
//push unique modules
@@ -106,7 +107,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
let mut secfile_hdr_reader = Cursor::new(decipher(&read_exact(&mut file, 32)?));
let secfile_header: SecHeader = secfile_hdr_reader.read_be()?;
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
info!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
//by knowing that the first file is always SDIT.FDI, find key(and mode)
let try_hdr = read_exact(&mut file, 0x20)?;
@@ -116,7 +117,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
//for new, key will always be the same
if let Ok(dec) = decrypt_aes128_cbc_pcks7(&try_hdr, &NEW_KEY.key, &NEW_KEY.iv) {
if dec.starts_with(TDI_FILENAME.as_bytes()) {
println!("- New type detected\n");
info!("- New type detected\n");
key = Some(KeyEntry::AESPcks7(NEW_KEY));
}
}
@@ -125,7 +126,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
for key_entry in OLD_KEYS_AES {
let dec = decrypt_aes128_cbc_nopad(&try_hdr, &key_entry.key, &key_entry.iv)?;
if dec.starts_with(TDI_FILENAME.as_bytes()) {
println!("- Old type detected with AES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
info!("- Old type detected with AES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
key = Some(KeyEntry::AES(key_entry));
break
}
@@ -136,7 +137,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
for key_entry in OLD_KEYS_DES {
let dec = decrypt_3des(&try_hdr, &key_entry)?;
if dec.starts_with(TDI_FILENAME.as_bytes()) {
println!("- Old type detected with DES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
info!("- Old type detected with DES key={}, iv={}\n", hex::encode(key_entry.key) ,hex::encode(key_entry.iv));
key = Some(KeyEntry::DES(key_entry));
break
}
@@ -154,7 +155,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
file.seek(SeekFrom::Start(0x20))?;
let (tdi_file, tdi_data) = get_sec_file(&file, &key)?;
println!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size());
info!("[TDI] Name: {}, Size: {}", tdi_file.name(), tdi_file.size());
if save_extra { //Save SDIT
let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&app_ctx.output_dir).join(tdi_file.name()))?;
out_file.write_all(&tdi_data)?;
@@ -168,7 +169,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
//get info files, each info file belongs to its respecitve group in the TDI
for i in 0..secfile_header.grp_num() {
let (info_file, info_data) = get_sec_file(&file, &key)?;
println!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size());
info!("\n[INFO] ID: {}, Name: {}, Size: {}", i, info_file.name(), info_file.size());
if !info_file.name().ends_with(INFO_FILE_EXTENSION) {
return Err(format!("Info file {} does not have the expected extension {}!", info_file.name(), INFO_FILE_EXTENSION).into());
}
@@ -177,12 +178,12 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
out_file.write_all(&info_data)?;
}
//print info file
println!("{}", String::from_utf8_lossy(&info_data));
info!("{}", String::from_utf8_lossy(&info_data));
}
//parse module data
for (i, module) in modules.iter().enumerate(){
println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}",
info!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}",
i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string());
let mut final_out_path: Option<PathBuf> = None;
@@ -192,7 +193,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
if !module_file.name().starts_with(&module.module_name()) {
return Err(format!("Module file {} does not start with the module's name: {}!", module_file.name(), module.module_name()).into());
}
println!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size());
info!(" Segment #{}/{} - Name: {}, Size: {}", i+1, module.num_of_txx, module_file.name(), module_file.size());
let mut module_reader = Cursor::new(module_data);
let _com_header: ModuleComHeader = module_reader.read_be()?;
@@ -203,23 +204,23 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
let module_header: ModuleHeader = module_reader.read_be()?;
let mut module_data = read_exact(&mut module_reader, module_header.cmp_size as usize)?;
if module_header.is_ciphered() {
println!(" - Deciphering...");
info!(" - Deciphering...");
module_data = decipher(&module_data);
}
if module_header.is_compressed() {
println!(" - Decompressing...");
info!(" - Decompressing...");
module_data = decompress_zlib(&module_data)?;
}
let mut content_reader = Cursor::new(module_data);
let content_header: ContentHeader = content_reader.read_be()?;
println!(" --> 0x{:X} @ 0x{:X}", content_header.size, content_header.dest_offset());
info!(" --> 0x{:X} @ 0x{:X}", content_header.size, content_header.dest_offset());
let output_path: PathBuf;
if content_header.has_subfile() {
let sub_filename_bytes = read_exact(&mut content_reader, 0x100)?;
let sub_filename = common::string_from_bytes(&sub_filename_bytes);
println!(" --> {}", sub_filename);
info!(" --> {}", sub_filename);
let sub_folder_path = Path::new(&app_ctx.output_dir).join(module.module_name());
fs::create_dir_all(&sub_folder_path)?;
@@ -238,7 +239,7 @@ pub fn extract_sddl_sec(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(),
}
if app_ctx.has_option("sddl_sec:split_peaks") && module.module_name() == "PEAKS" {
println!("\n- Splitting PEAKS");
info!("\n- Splitting PEAKS");
if let Some(ref path) = final_out_path {
split_peaks_file(path, &app_ctx.output_dir, !app_ctx.has_option("sddl_sec:no_decomp_peaks"))?;
}
+7 -9
View File
@@ -3,6 +3,7 @@ use crate::utils::common;
use std::{fs::{self, File, OpenOptions}, io::{Cursor, Seek, SeekFrom, Write}, path::{Path, PathBuf}};
use crate::utils::compression::decompress_zlib;
use log::info;
pub fn split_peaks_file(path: &PathBuf, out_path: &PathBuf, do_decomp: bool) -> Result<(), Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
@@ -11,7 +12,7 @@ pub fn split_peaks_file(path: &PathBuf, out_path: &PathBuf, do_decomp: bool) ->
let args_bytes = common::read_file(&mut file, 0, 0x210)?;
if !args_bytes.starts_with(b"D50 ") {
println!("- Splitting PEAKS is not supported on this file...");
info!("- Splitting PEAKS is not supported on this file...");
return Ok(());
}
let args = common::string_from_bytes(&args_bytes[16..]);
@@ -57,7 +58,7 @@ pub fn split_peaks_file(path: &PathBuf, out_path: &PathBuf, do_decomp: bool) ->
}
let root = root.ok_or("Failed to get root partition!")?;
println!("Root - {}", root);
info!("Root - {}", root);
let root_index = parts.iter().position(|(n, _s, _f)| n == &root).ok_or("Root partition not found in partition list!")?;
let mut tsize: u64 = 0;
@@ -69,7 +70,7 @@ pub fn split_peaks_file(path: &PathBuf, out_path: &PathBuf, do_decomp: bool) ->
break
}
println!("- {} - Size: {}{}", part_name, part_size, if let Some(part_flag) = part_flag {format!(", Flag: {}", part_flag)} else {format!("")});
info!("- {} - Size: {}{}", part_name, part_size, if let Some(part_flag) = part_flag {format!(", Flag: {}", part_flag)} else {format!("")});
tsize += part_size;
@@ -79,7 +80,7 @@ pub fn split_peaks_file(path: &PathBuf, out_path: &PathBuf, do_decomp: bool) ->
let data = common::read_exact(&mut file, *part_size as usize)?;
if do_decomp && let Some(part_flag) = part_flag && *part_flag == "c" {
println!("-- Decompressing ...");
info!("-- Decompressing ...");
decompress_part_to_file(&data, &output_path)?;
continue
}
@@ -108,7 +109,7 @@ fn decompress_part_to_file(data: &[u8], out_path: &PathBuf) -> Result<(), Box<dy
let first_blk: u32 = reader.read_type(endianness)?;
let count_blks = (first_blk - 4) / 4;
println!("[INFO] Endianness: {}, Uncompressed size: {}, First block loc: {}, Block count: {}",
info!("[INFO] Endianness: {}, Uncompressed size: {}, First block loc: {}, Block count: {}",
endianness, uncompressed_size, first_blk, count_blks);
reader.seek(SeekFrom::Start(4))?;
@@ -123,9 +124,8 @@ fn decompress_part_to_file(data: &[u8], out_path: &PathBuf) -> Result<(), Box<dy
for (n, blk) in block_locs.iter().enumerate() {
reader.seek(SeekFrom::Start(*blk as u64))?;
print!("\rDecompressing... {}/{}",
info!("Decompressing... {}/{}",
n + 1, count_blks);
std::io::stdout().flush()?;
//special handling for last block since there is no next block to check, just assume 16k
let blk_size = if n == count_blks as usize - 1 {16384} else {block_locs[n + 1] - blk};
@@ -136,7 +136,5 @@ fn decompress_part_to_file(data: &[u8], out_path: &PathBuf) -> Result<(), Box<dy
out_file.write_all(&decompressed)?;
}
println!();
Ok(())
}
+4 -3
View File
@@ -8,6 +8,7 @@ use binrw::BinReaderExt;
use include::*;
use crate::utils::common;
use log::info;
pub fn is_sdimage_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,14 +29,14 @@ pub fn extract_sdimage(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut e_i = 0;
while file.stream_position()? < file.metadata()?.len() as u64 {
let entry: EntryHeader = file.read_le()?;
println!("\n#{} - {} ({}), Size: {}, Version: {}.{}.{}.{}, Model ID: {}, Info: {}{} {}",
info!("\n#{} - {} ({}), Size: {}, Version: {}.{}.{}.{}, Model ID: {}, Info: {}{} {}",
e_i+1, entry.target_name(), entry.target_id, entry.size1, entry.version[3], entry.version[2], entry.version[1], entry.version[0], entry.model_id, entry.info(),
if entry.is_encrypted() {" [ENCRYPTED]"} else {""}, if entry.is_empty() {"[EMPTY]"} else {""});
if !entry.is_empty() {
let info = entry.info();
let filename = info.split("FN=\"").nth(1).and_then(|s| s.split('"').next()).unwrap();
println!("- Filename: {}", filename);
info!("- Filename: {}", filename);
let data = common::read_exact(&mut file, entry.size1 as usize)?;
@@ -47,7 +48,7 @@ pub fn extract_sdimage(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), B
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
e_i += 1;
+11 -10
View File
@@ -9,6 +9,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use log::info;
pub struct SlpContext {
variant: SlpVariant,
@@ -39,10 +40,10 @@ pub fn is_slp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn
pub fn extract_slp(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::<SlpContext>().expect("Missing context");
let ctx = ctx.downcast::<SlpContext>().map_err(|_| "Invalid context type")?;
let meta_header: CommonMetaHeader = file.read_le()?;
println!("File info:\nType: {:?}\nProject name: {}\nFirmware Version: {}\nFirmware Version(USER): {}",
info!("File info:\nType: {:?}\nProject name: {}\nFirmware Version: {}\nFirmware Version(USER): {}",
ctx.variant, meta_header.project_name(), meta_header.firmware_version(), meta_header.user_version());
let num_image;
@@ -50,21 +51,21 @@ pub fn extract_slp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
if ctx.variant == SlpVariant::New {
let meta_header_ext: MetaHeaderExtNew = file.read_le()?;
if meta_header_ext.snapshot_included == 0x01 {
println!("Snapshot Image: Included");
println!("S/S Img. Board Version: {}", meta_header_ext.snapshot_board_version())
info!("Snapshot Image: Included");
info!("S/S Img. Board Version: {}", meta_header_ext.snapshot_board_version())
} else {
println!("Snapshot Image: Excluded");
info!("Snapshot Image: Excluded");
}
num_image = meta_header_ext.num_image;
}
else if ctx.variant == SlpVariant::Old {
let meta_header_ext: MetaHeaderExtOld = file.read_le()?;
if meta_header_ext.snapshot_included == 0x01 {
println!("Snapshot Image: Included");
println!("Snapshot Image offset: {}", meta_header_ext.snapshot_entry_offset);
info!("Snapshot Image: Included");
info!("Snapshot Image offset: {}", meta_header_ext.snapshot_entry_offset);
snapshot_entry_offset = Some(meta_header_ext.snapshot_entry_offset);
} else {
println!("Snapshot Image: Excluded");
info!("Snapshot Image: Excluded");
}
num_image = 5; //hardcoded for old variant
}
@@ -95,7 +96,7 @@ pub fn extract_slp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
}
for (i, entry) in entries.iter().enumerate() {
println!("\n({}/{}) - Offset: {}, Size: {}, Magic: 0x{:02X}", i+1, &entries.len(), entry.offset, entry.size, entry.magic);
info!("\n({}/{}) - Offset: {}, Size: {}, Magic: 0x{:02X}", i+1, &entries.len(), entry.offset, entry.size, entry.magic);
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", i+1));
@@ -106,7 +107,7 @@ pub fn extract_slp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dy
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
}
Ok(())
+10 -8
View File
@@ -12,6 +12,7 @@ use crate::utils::common;
use crate::formats;
use crate::keys;
use include::*;
use log::info;
struct SonyBdpCtx {
encryption_type: EncryptionType,
@@ -50,7 +51,7 @@ pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Bo
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>().expect("Missing context");
let ctx = ctx.downcast::<SonyBdpCtx>().map_err(|_| "Invalid context type")?;
//need to decrypt entire file of new aes enc
let mut enc_data = Vec::new();
@@ -58,11 +59,11 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
let dec_data = match ctx.encryption_type {
EncryptionType::HexSubst => {
println!("Decrypting with hex substitution...");
info!("Decrypting with hex substitution...");
hex_substitute(&enc_data)
},
EncryptionType::AesOfb((key, iv, key_name)) => {
println!("Decrypting with AES key: {}...", key_name);
info!("Decrypting with AES key: {}...", key_name);
ver_up_decrypt_aes128ofb(&key, &iv, &enc_data)
}
};
@@ -72,7 +73,7 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
let mut hdr_reader = Cursor::new(header);
let hdr: Header = hdr_reader.read_le()?;
println!("File info:\nFirmware: {}\nVersion: {}\nDate: {}\nFile size: {}",
info!("File info:\nFirmware: {}\nVersion: {}\nDate: {}\nFile size: {}",
hdr.firmware_name(), hdr.firmware_version(), hdr.date(), hdr.file_size);
let mut last_file_path: Option<PathBuf> = None;
@@ -89,7 +90,7 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
continue
}
println!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size);
info!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size);
if i == 0 {
first_entry_offset = entry.offset as u64;
}
@@ -104,7 +105,7 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("- Saved file!");
info!("- Saved file!");
i += 1;
}
@@ -119,13 +120,14 @@ pub fn extract_sony_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), B
options: app_ctx.options.clone(),
dry_run: app_ctx.dry_run,
quiet: app_ctx.quiet,
verbose: app_ctx.verbose,
};
if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&ctx)? {
println!("- MTK BDP file detected!\n");
info!("- MTK BDP file detected!\n");
formats::mtk_bdp::extract_mtk_bdp(&ctx, result)?;
} else {
println!("- Not an MTK BDP file.");
info!("- Not an MTK BDP file.");
}
}
+8 -7
View File
@@ -13,6 +13,7 @@ use binrw::BinReaderExt;
use crate::utils::common;
use include::*;
use tsb_des::decrypt;
use log::info;
struct TsbBinCtx {
key: Option<[u8; 8]>
@@ -44,11 +45,11 @@ 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>().expect("Missing context");
let ctx = ctx.downcast::<TsbBinCtx>().map_err(|_| "Invalid context type")?;
let mut header = common::read_file(&mut file, 0, 0x400)?;
if let Some(key) = ctx.key { //decrypt header
println!("File is encrypted, using key: {}", hex::encode(&key));
info!("File is encrypted, using key: {}", hex::encode(&key));
header = decrypt(&header, &key);
opt_dump_dec_hdr(app_ctx, &header, "header")?;
}
@@ -56,17 +57,17 @@ pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
let mut hdr_rdr = Cursor::new(header);
let hdr: Header = hdr_rdr.read_be()?;
println!("File info -\nSize: {}\nEntry count: {}\nBuild no.: {}\nEntry address: 0x{:02x}"
info!("File info -\nSize: {}\nEntry count: {}\nBuild no.: {}\nEntry address: 0x{:02x}"
,hdr.length, hdr.entry_count, hdr.build_no(), hdr.entry_addr);
for (i, entry) in hdr.entries.iter().enumerate() {
println!("\n({}/{}) - {}, Size: {}, Offset: {}, Load address: 0x{:02x}",
info!("\n({}/{}) - {}, Size: {}, Offset: {}, Load address: 0x{:02x}",
i+1, hdr.entry_count, entry.name(), entry.size, entry.offset, entry.load_addr);
let mut data;
if let Some(key) = ctx.key {
let enc_data = common::read_file(&mut file, entry.offset as u64, (entry.size as usize + 7) & !7)?; //read aligned to 8b blocks for decryption
println!("- Decrypting...");
info!("- Decrypting...");
data = decrypt(&enc_data, &key);
data.truncate(entry.size as usize); //discard alignment
@@ -75,7 +76,7 @@ pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
}
if entry.is_compressed() {
println!("- Decompressing...");
info!("- Decompressing...");
data = decompress_zlib(&data)?;
}
@@ -85,7 +86,7 @@ pub fn extract_tsb_bin(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Bo
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("-- Saved file!");
info!("-- Saved file!");
}
Ok(())
+15 -14
View File
@@ -8,6 +8,7 @@ use crate::utils::aes::decrypt_aes128_ecb;
use crate::keys;
use include::*;
use log::info;
pub struct VestelCtx {
pub is_encrypted: bool,
@@ -91,7 +92,7 @@ pub fn extract_vestel(
app_ctx: &AppContext,
ctx: Box<dyn Any>,
) -> Result<(), Box<dyn std::error::Error>> {
let ctx = ctx.downcast::<VestelCtx>().expect("Missing context");
let ctx = ctx.downcast::<VestelCtx>().map_err(|_| "Invalid context type")?;
match ctx.variant {
VestelVariant::Mb230 => extract_mb230(app_ctx, &ctx),
@@ -116,8 +117,8 @@ fn extract_standard(
let key: [u8; 16] = key_vec.try_into().map_err(|_| "Invalid AES key length")?;
let (final_data, decrypted_path) = if ctx.is_encrypted {
println!("Encrypted Vestel firmware detected");
println!("Using key: {} ({})", "VESTEL", key_hex);
info!("Encrypted Vestel firmware detected");
info!("Using key: {} ({})", "VESTEL", key_hex);
let dec = decrypt_aes128_ecb(&key, &data)?;
@@ -129,12 +130,12 @@ fn extract_standard(
(dec, Some(output_path))
} else {
println!("Detected Vestel firmware (unencrypted)");
info!("Detected Vestel firmware (unencrypted)");
(data, None)
};
let partitions = VESTEL_PARTITIONS;
println!("\nPartition count: {}", partitions.len());
info!("\nPartition count: {}", partitions.len());
fs::create_dir_all(&app_ctx.output_dir)?;
@@ -143,14 +144,14 @@ fn extract_standard(
let end = start.saturating_add(*size);
if start >= final_data.len() {
println!("Skipping {} (out of range)", name);
info!("Skipping {} (out of range)", name);
continue;
}
let end = end.min(final_data.len());
let segment = &final_data[start..end];
println!(
info!(
"\n{} - Offset: 0x{:X}, Size: 0x{:X}",
name, offset, size
);
@@ -160,7 +161,7 @@ fn extract_standard(
fs::write(&output_path, segment)?;
println!("- Saved {}.bin", name);
info!("- Saved {}.bin", name);
}
if let Some(path) = decrypted_path {
@@ -181,11 +182,11 @@ fn extract_mb230(
let file_size = file.metadata()?.len() as usize;
let data = common::read_exact(&mut file, file_size)?;
println!("Detected Vestel MB230 firmware (Novatek NT72673)");
println!("File size: {} bytes ({:.2} MB)", file_size, file_size as f64 / 1024.0 / 1024.0);
info!("Detected Vestel MB230 firmware (Novatek NT72673)");
info!("File size: {} bytes ({:.2} MB)", file_size, file_size as f64 / 1024.0 / 1024.0);
let partitions = MB230_PARTITIONS;
println!("\nPartition count: {}", partitions.len());
info!("\nPartition count: {}", partitions.len());
fs::create_dir_all(&app_ctx.output_dir)?;
@@ -194,14 +195,14 @@ fn extract_mb230(
let end = start.saturating_add(*size);
if start >= file_size {
println!("Skipping {} (out of range)", name);
info!("Skipping {} (out of range)", name);
continue;
}
let end = end.min(file_size);
let segment = &data[start..end];
println!(
info!(
"\n{} - Offset: 0x{:X}, Size: 0x{:X} ({} bytes)",
name, offset, size, segment.len()
);
@@ -211,7 +212,7 @@ fn extract_mb230(
fs::write(&output_path, segment)?;
println!("- Saved {}.bin", name);
info!("- Saved {}.bin", name);
}
Ok(())
+42 -23
View File
@@ -5,7 +5,7 @@ mod utils;
use clap::Parser;
use std::path::PathBuf;
use std::io::{self, Seek, SeekFrom};
use std::io::{self, Seek, SeekFrom, Write};
use std::fs::{self, File};
use crate::formats::{Format, get_registry};
use crate::error::UnixtractError;
@@ -43,7 +43,7 @@ struct Args {
#[arg(short, long)]
quiet: bool,
/// Verbose mode — show detailed progress information
/// Verbose mode — show detailed progress information (use -vv for trace)
#[arg(short, long, action = clap::ArgAction::Count)]
verbose: u8,
}
@@ -59,6 +59,7 @@ pub struct AppContext {
pub options: Vec<String>,
pub dry_run: bool,
pub quiet: bool,
pub verbose: u8,
}
impl AppContext {
@@ -81,15 +82,31 @@ impl AppContext {
}
}
/// Clear the terminal screen
fn clear_terminal() {
// ANSI escape sequence to clear screen and move cursor to top-left
eprint!("\x1B[2J\x1B[H");
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
// Clear the terminal on execution
if !args.quiet {
clear_terminal();
}
// 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)
// -vvv: trace level (everything)
let log_level = if args.quiet {
"error"
} else {
match args.verbose {
0 => "warn",
0 => "info",
1 => "info",
2 => "debug",
_ => "trace",
@@ -98,22 +115,34 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(log_level))
.format_timestamp(None)
.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()),
}
})
.init();
// Handle --list-formats
if args.list_formats {
let formats = get_registry();
println!("Supported formats ({} total):", formats.len());
eprintln!("Supported formats ({} total):", formats.len());
for (i, fmt) in formats.iter().enumerate() {
println!(" {:2}. {}", i + 1, fmt.name);
eprintln!(" {:2}. {}", i + 1, fmt.name);
}
return Ok(());
}
log::info!("unixtract Firmware extractor v{}", env!("CARGO_PKG_VERSION"));
log::info!("unixtract v{}", env!("CARGO_PKG_VERSION"));
let target_path_str = args.input_target.as_deref().unwrap_or("");
log::info!("Input target: {}", target_path_str);
let target_path = PathBuf::from(target_path_str);
let output_path_str = if let Some(ref out) = args.output_directory {
@@ -123,7 +152,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.and_then(|s| s.to_str())
.ok_or_else(|| UnixtractError::Other("Invalid input file name".to_string()))?)
};
log::info!("Output directory: {}", output_path_str);
let output_directory_path = PathBuf::from(&output_path_str);
if output_directory_path.exists() {
@@ -131,7 +159,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let is_empty = fs::read_dir(&output_directory_path)?.next().is_none();
if !is_empty && !args.quiet {
log::warn!("Output folder already exists and is NOT empty! Files may be overwritten.");
eprintln!("Warning: Output folder already exists and is NOT empty! Files may be overwritten!");
eprintln!("Press Enter if you want to continue...");
io::stdin().read_line(&mut String::new())?;
}
@@ -149,6 +176,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
options: args.options,
dry_run: args.dry_run,
quiet: args.quiet,
verbose: args.verbose,
};
} else if target_path.is_dir() {
app_ctx = AppContext {
@@ -157,25 +185,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
options: args.options,
dry_run: args.dry_run,
quiet: args.quiet,
verbose: args.verbose,
};
} else {
return Err("Invalid input path!".into());
}
let formats: Vec<Format> = get_registry();
log::info!("Loaded {} formats", formats.len());
for format in formats {
if let Some(ctx) = (format.detector_func)(&app_ctx)? {
log::info!("{} detected!", format.name);
if !app_ctx.quiet {
println!("\n{} detected!", format.name);
}
log::info!("\n{} detected!", format.name);
if app_ctx.dry_run {
if !app_ctx.quiet {
println!("Dry run — skipping extraction.");
}
log::info!("Dry run — skipping extraction.");
return Ok(());
}
@@ -187,15 +210,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
(format.extractor_func)(&app_ctx, ctx)?;
// Extractor returned with no error
if !app_ctx.quiet {
println!("\nExtraction finished! Saved extracted files to {}", output_path_str);
}
log::info!("\nExtraction finished! Saved extracted files to {}", output_path_str);
return Ok(());
}
}
if !app_ctx.quiet {
println!("\nInput format not recognized!");
}
log::warn!("\nInput format not recognized!");
Err("Unrecognized input format".into())
}
+5 -4
View File
@@ -1,12 +1,12 @@
use std::fs::{File};
use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};
pub fn read_file(mut file: &File, offset: u64, size: usize) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
file.seek(SeekFrom::Start(offset))?;
let mut buffer = vec![0u8; size];
let _bytes_read = file.read(&mut buffer)?;
file.read_exact(&mut buffer)?;
// reset seek (!
// Reset seek position back to the read offset
file.seek(SeekFrom::Start(offset))?;
Ok(buffer)
}
@@ -20,4 +20,5 @@ pub fn read_exact<R: Read>(reader: &mut R, size: usize) -> io::Result<Vec<u8>> {
pub fn string_from_bytes(buf: &[u8]) -> String {
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..end]).to_string()
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ pub fn decompress_gzip_get_filename(compressed_data: &[u8]) -> Result<(Vec<u8>,
let mut decoder = GzDecoder::new(compressed_data);
let mut filename: Option<String> = None;
if let Some(filename_bytes) = decoder.header().unwrap().filename() {
if let Some(filename_bytes) = decoder.header().and_then(|h| h.filename()) {
filename = Some(common::string_from_bytes(filename_bytes));
}
+2 -1
View File
@@ -1,6 +1,7 @@
use std::{fs::{self, OpenOptions}, io::Write, path::Path};
use crate::AppContext;
use log::info;
pub fn opt_dump_dec_hdr(app_ctx: &AppContext, data: &[u8], name: &str) -> Result<(), Box<dyn std::error::Error>> {
if !app_ctx.has_option("dump_dec_hdrs") {
@@ -14,7 +15,7 @@ pub fn opt_dump_dec_hdr(app_ctx: &AppContext, data: &[u8], name: &str) -> Result
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
out_file.write_all(&data)?;
println!("[i] Saved {} to {}", name, filename);
info!("[i] Saved {} to {}", name, filename);
Ok(())
}
+2 -1
View File
@@ -5,6 +5,7 @@ use std::io::{Cursor, Write};
use simd_adler32::adler32;
use crate::utils::common;
use log::info;
#[derive(BinRead)]
struct LzopHeader {
@@ -48,7 +49,7 @@ pub fn unlzop_to_file(data: &[u8], file_path: PathBuf) -> Result<(), Box<dyn std
}
let segment_header: SegmentHeader = data_reader.read_be()?;
if segment_header.compressed_size > segment_header.uncompressed_size {
println!("{:?}", segment_header);
info!("{:?}", segment_header);
}
if segment_header.uncompressed_size == 0 {
break