mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
+ cleanup
This commit is contained in:
@@ -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
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
__pycache__/
|
||||
.vscode
|
||||
.vscode
|
||||
venv/
|
||||
+6
-2
@@ -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"
|
||||
@@ -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
@@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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!");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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(()))?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
|
||||
@@ -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)?;
|
||||
};
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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!");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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!");
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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
@@ -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"))?;
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user