mirror of
https://github.com/Ap0dexMe0/unixtract.git
synced 2026-07-15 21:00:03 +02:00
MERGE NEWREG
Newreg
This commit is contained in:
+161
@@ -1,3 +1,12 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
pub struct Format {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub detector_func: fn(&AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>>,
|
||||||
|
pub extractor_func: fn(&AppContext, Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub mod mstar;
|
pub mod mstar;
|
||||||
pub mod samsung_old;
|
pub mod samsung_old;
|
||||||
pub mod nvt_timg;
|
pub mod nvt_timg;
|
||||||
@@ -32,3 +41,155 @@ pub mod mtk_pkg;
|
|||||||
pub mod mtk_pkg_old;
|
pub mod mtk_pkg_old;
|
||||||
pub mod mtk_pkg_new;
|
pub mod mtk_pkg_new;
|
||||||
pub mod mtk_bdp;
|
pub mod mtk_bdp;
|
||||||
|
|
||||||
|
//define all formats here
|
||||||
|
pub fn get_registry() -> Vec<Format> {
|
||||||
|
return vec![
|
||||||
|
Format {
|
||||||
|
name: "mstar",
|
||||||
|
detector_func: crate::formats::mstar::is_mstar_file,
|
||||||
|
extractor_func: crate::formats::mstar::extract_mstar,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "samsung_old",
|
||||||
|
detector_func: crate::formats::samsung_old::is_samsung_old_dir,
|
||||||
|
extractor_func: crate::formats::samsung_old::extract_samsung_old,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "nvt_timg",
|
||||||
|
detector_func: crate::formats::nvt_timg::is_nvt_timg_file,
|
||||||
|
extractor_func: crate::formats::nvt_timg::extract_nvt_timg,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "pfl_upg",
|
||||||
|
detector_func: crate::formats::pfl_upg::is_pfl_upg_file,
|
||||||
|
extractor_func: crate::formats::pfl_upg::extract_pfl_upg,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "sddl_sec",
|
||||||
|
detector_func: crate::formats::sddl_sec::is_sddl_sec_file,
|
||||||
|
extractor_func: crate::formats::sddl_sec::extract_sddl_sec,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "novatek",
|
||||||
|
detector_func: crate::formats::novatek::is_novatek_file,
|
||||||
|
extractor_func: crate::formats::novatek::extract_novatek,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "ruf",
|
||||||
|
detector_func: crate::formats::ruf::is_ruf_file,
|
||||||
|
extractor_func: crate::formats::ruf::extract_ruf,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "invincible_image",
|
||||||
|
detector_func: crate::formats::invincible_image::is_invincible_image_file,
|
||||||
|
extractor_func: crate::formats::invincible_image::extract_invincible_image,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "slp",
|
||||||
|
detector_func: crate::formats::slp::is_slp_file,
|
||||||
|
extractor_func: crate::formats::slp::extract_slp,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "roku",
|
||||||
|
detector_func: crate::formats::roku::is_roku_file,
|
||||||
|
extractor_func: crate::formats::roku::extract_roku,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "sony_bdp",
|
||||||
|
detector_func: crate::formats::sony_bdp::is_sony_bdp_file,
|
||||||
|
extractor_func: crate::formats::sony_bdp::extract_sony_bdp,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "rvp",
|
||||||
|
detector_func: crate::formats::rvp::is_rvp_file,
|
||||||
|
extractor_func: crate::formats::rvp::extract_rvp,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "funai_upg",
|
||||||
|
detector_func: crate::formats::funai_upg::is_funai_upg_file,
|
||||||
|
extractor_func: crate::formats::funai_upg::extract_funai_upg,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "pana_dvd",
|
||||||
|
detector_func: crate::formats::pana_dvd::is_pana_dvd_file,
|
||||||
|
extractor_func: crate::formats::pana_dvd::extract_pana_dvd,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "android_ota_payload",
|
||||||
|
detector_func: crate::formats::android_ota_payload::is_android_ota_payload_file,
|
||||||
|
extractor_func: crate::formats::android_ota_payload::extract_android_ota_payload,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "bdl",
|
||||||
|
detector_func: crate::formats::bdl::is_bdl_file,
|
||||||
|
extractor_func: crate::formats::bdl::extract_bdl,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "amlogic",
|
||||||
|
detector_func: crate::formats::amlogic::is_amlogic_file,
|
||||||
|
extractor_func: crate::formats::amlogic::extract_amlogic,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "pup",
|
||||||
|
detector_func: crate::formats::pup::is_pup_file,
|
||||||
|
extractor_func: crate::formats::pup::extract_pup,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "msd10",
|
||||||
|
detector_func: crate::formats::msd10::is_msd10_file,
|
||||||
|
extractor_func: crate::formats::msd10::extract_msd10,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "msd11",
|
||||||
|
detector_func: crate::formats::msd11::is_msd11_file,
|
||||||
|
extractor_func: crate::formats::msd11::extract_msd11,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "epk",
|
||||||
|
detector_func: crate::formats::epk::is_epk_file,
|
||||||
|
extractor_func: crate::formats::epk::extract_epk,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "epk1",
|
||||||
|
detector_func: crate::formats::epk1::is_epk1_file,
|
||||||
|
extractor_func: crate::formats::epk1::extract_epk1,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "epk2",
|
||||||
|
detector_func: crate::formats::epk2::is_epk2_file,
|
||||||
|
extractor_func: crate::formats::epk2::extract_epk2,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "epk2b",
|
||||||
|
detector_func: crate::formats::epk2b::is_epk2b_file,
|
||||||
|
extractor_func: crate::formats::epk2b::extract_epk2b,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "epk3",
|
||||||
|
detector_func: crate::formats::epk3::is_epk3_file,
|
||||||
|
extractor_func: crate::formats::epk3::extract_epk3,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "mtk_pkg",
|
||||||
|
detector_func: crate::formats::mtk_pkg::is_mtk_pkg_file,
|
||||||
|
extractor_func: crate::formats::mtk_pkg::extract_mtk_pkg,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "mtk_pkg_old",
|
||||||
|
detector_func: crate::formats::mtk_pkg_old::is_mtk_pkg_old_file,
|
||||||
|
extractor_func: crate::formats::mtk_pkg_old::extract_mtk_pkg_old,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "mtk_pkg_new",
|
||||||
|
detector_func: crate::formats::mtk_pkg_new::is_mtk_pkg_new_file,
|
||||||
|
extractor_func: crate::formats::mtk_pkg_new::extract_mtk_pkg_new,
|
||||||
|
},
|
||||||
|
Format {
|
||||||
|
name: "mtk_bdp",
|
||||||
|
detector_func: crate::formats::mtk_bdp::is_mtk_bdp_file,
|
||||||
|
extractor_func: crate::formats::mtk_bdp::extract_mtk_bdp,
|
||||||
|
},
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::{BinRead};
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct ImageHeader {
|
||||||
|
_crc32: u32,
|
||||||
|
pub version: u32,
|
||||||
|
_magic_bytes: [u8; 4], //56 19 B5 27
|
||||||
|
pub image_size: u64,
|
||||||
|
pub item_align_size: u32,
|
||||||
|
pub item_count: u32,
|
||||||
|
_reserved: [u8; 36],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct ItemEntry {
|
||||||
|
_item_id: u32,
|
||||||
|
pub file_type: u32,
|
||||||
|
_current_offset_in_item: u64,
|
||||||
|
pub offset_in_image: u64,
|
||||||
|
pub item_size: u64,
|
||||||
|
item_type_bytes: [u8; 256],
|
||||||
|
name_bytes: [u8; 256],
|
||||||
|
_verify: u32,
|
||||||
|
_is_backup_item: u16,
|
||||||
|
_backup_item_id: u16,
|
||||||
|
_reserved: [u8; 24],
|
||||||
|
}
|
||||||
|
impl ItemEntry {
|
||||||
|
pub fn item_type(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.item_type_bytes)
|
||||||
|
}
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
pub fn is_sparse(&self) -> bool {
|
||||||
|
self.file_type == 254
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +1,36 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
use std::io::Write;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::sparse::{unsparse_to_file};
|
use crate::utils::sparse::unsparse_to_file;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_amlogic_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct ImageHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
_crc32: u32,
|
|
||||||
version: u32,
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //56 19 B5 27
|
|
||||||
image_size: u64,
|
|
||||||
item_align_size: u32,
|
|
||||||
item_count: u32,
|
|
||||||
#[br(count = 36)] _reserved: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 8, 4)?;
|
||||||
struct ItemEntry {
|
|
||||||
_item_id: u32,
|
|
||||||
file_type: u32,
|
|
||||||
_current_offset_in_item: u64,
|
|
||||||
offset_in_image: u64,
|
|
||||||
item_size: u64,
|
|
||||||
#[br(count = 256)] item_type_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] name_bytes: Vec<u8>,
|
|
||||||
_verify: u32,
|
|
||||||
_is_backup_item: u16,
|
|
||||||
_backup_item_id: u16,
|
|
||||||
#[br(count = 24)] _reserved: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl ItemEntry {
|
|
||||||
fn item_type(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.item_type_bytes)
|
|
||||||
}
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn is_sparse(&self) -> bool {
|
|
||||||
self.file_type == 254
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_amlogic_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 8, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"\x56\x19\xB5\x27" {
|
if header == b"\x56\x19\xB5\x27" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_amlogic(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
file.seek(SeekFrom::Start(0))?;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
let header: ImageHeader = file.read_le()?;
|
|
||||||
|
|
||||||
|
let header: ImageHeader = file.read_le()?;
|
||||||
println!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}",
|
println!("File info -\nImage size: {}\nItem align size: {}\nItem count: {}\nFormat version: {}",
|
||||||
header.image_size, header.item_align_size, header.item_count, header.version);
|
header.image_size, header.item_align_size, header.item_count, header.version);
|
||||||
|
|
||||||
if header.version != 2 {
|
if header.version != 2 {
|
||||||
println!("\nSorry, this format version is not yet supported!");
|
return Err("Unsupported format version! (Only 2 is supported right now)".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut items: Vec<ItemEntry> = Vec::new();
|
let mut items: Vec<ItemEntry> = Vec::new();
|
||||||
@@ -85,8 +53,8 @@ pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
let data = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
|
let data = common::read_file(&file, item.offset_in_image, item.item_size as usize)?;
|
||||||
|
|
||||||
let extension = if item.item_type() == "PARTITION" {"img"} else {&item.item_type()};
|
let extension = if item.item_type() == "PARTITION" {"img"} else {&item.item_type()};
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.{}", item.name(), extension));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.{}", item.name(), extension));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
|
|
||||||
if item.is_sparse() {
|
if item.is_sparse() {
|
||||||
println!("- Unsparsing...");
|
println!("- Unsparsing...");
|
||||||
@@ -103,6 +71,5 @@ pub fn extract_amlogic(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use binrw::{BinRead};
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4], //CrAU
|
||||||
|
pub file_format_version: u64,
|
||||||
|
pub manifest_size: u64,
|
||||||
|
pub metadata_signature_size: u32,
|
||||||
|
}
|
||||||
@@ -1,37 +1,38 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
mod android_ota_update_metadata;
|
||||||
use std::io::{Write};
|
use std::any::Any;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::Write;
|
||||||
|
use binrw::BinReaderExt;
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::android_ota_update_metadata::{DeltaArchiveManifest, install_operation};
|
use android_ota_update_metadata::{DeltaArchiveManifest, install_operation};
|
||||||
use crate::utils::compression::{decompress_bzip, decompress_xz};
|
use crate::utils::compression::{decompress_bzip, decompress_xz};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_android_ota_payload_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //CrAU
|
|
||||||
file_format_version: u64,
|
|
||||||
manifest_size: u64,
|
|
||||||
metadata_signature_size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_android_ota_payload_file(file: &File) -> bool {
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"CrAU" {
|
if header == b"CrAU" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_android_ota_payload(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_android_ota_payload(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
let header: Header = file.read_be()?;
|
let header: Header = file.read_be()?;
|
||||||
println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size);
|
println!("File info:\nFormat version: {}\nManifest size: {}", header.file_format_version, header.manifest_size);
|
||||||
|
|
||||||
if header.file_format_version != 2 {
|
if header.file_format_version != 2 {
|
||||||
println!("\nSorry, this version of the file is not supported!");
|
return Err("Unsupported format version! (Only 2 is supported right now)".into());
|
||||||
return Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_offset = 24 /* size of header */ + header.manifest_size + header.metadata_signature_size as u64;
|
let base_offset = 24 /* size of header */ + header.manifest_size + header.metadata_signature_size as u64;
|
||||||
@@ -75,15 +76,13 @@ pub fn extract_android_ota_payload(mut file: &File, output_folder: &str) -> Resu
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", partition.partition_name));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", partition.partition_name));
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
}
|
}
|
||||||
println!("\n-- Saved!");
|
println!("\n-- Saved!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::path::{Path};
|
|
||||||
use std::fs::{self, OpenOptions};
|
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct BdlHeader {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //ibdl
|
|
||||||
#[br(count = 8)] _file_version: Vec<u8>,
|
|
||||||
_unk1: u32,
|
|
||||||
pkg_count: u32,
|
|
||||||
#[br(count = 12)] _unk2: Vec<u8>,
|
|
||||||
#[br(count = 256)] date_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] manufacturer_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] model_bytes: Vec<u8>,
|
|
||||||
#[br(count = 9)] _unk3: Vec<u8>,
|
|
||||||
#[br(count = 256)] version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 1280)] info_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl BdlHeader {
|
|
||||||
fn date(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.date_bytes)
|
|
||||||
}
|
|
||||||
fn manufacturer(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.manufacturer_bytes)
|
|
||||||
}
|
|
||||||
fn model(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.model_bytes)
|
|
||||||
}
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn info(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.info_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PkgListEntry {
|
|
||||||
offset: u64,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PkgHeader {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //ipkg
|
|
||||||
#[br(count = 12)] _unk1: Vec<u8>,
|
|
||||||
entry_count: u32,
|
|
||||||
#[br(count = 12)] _unk2: Vec<u8>,
|
|
||||||
#[br(count = 256)] version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] manufacturer_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 285)] _unk3: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl PkgHeader {
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn manufacturer(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.manufacturer_bytes)
|
|
||||||
}
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PkgEntry {
|
|
||||||
#[br(count = 256)] name_bytes: Vec<u8>,
|
|
||||||
offset: u64,
|
|
||||||
size: u64,
|
|
||||||
_crc32: u32,
|
|
||||||
}
|
|
||||||
impl PkgEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_bdl_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"ibdl" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_bdl(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: BdlHeader = file.read_le()?;
|
|
||||||
|
|
||||||
println!("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();
|
|
||||||
|
|
||||||
for _i in 0..header.pkg_count {
|
|
||||||
let pkg_entry: PkgListEntry = file.read_le()?;
|
|
||||||
//println!("Package {} - Offset: {}, Size: {}", i + 1, pkg_entry.offset, pkg_entry.size);
|
|
||||||
pkgs.push(pkg_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
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: {}",
|
|
||||||
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();
|
|
||||||
|
|
||||||
for _i in 0..pkg_header.entry_count {
|
|
||||||
let pkg_entry: PkgEntry = file.read_le()?;
|
|
||||||
pkg_entries.push(pkg_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
let pkg_folder = Path::new(&output_folder).join(pkg_header.name());
|
|
||||||
fs::create_dir_all(&pkg_folder)?;
|
|
||||||
|
|
||||||
for (i, pkg_entry) in pkg_entries.iter().enumerate() {
|
|
||||||
println!("- 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;
|
|
||||||
let data = common::read_file(&file, calc_offset, pkg_entry.size as usize)?;
|
|
||||||
|
|
||||||
let output_path = Path::new(&pkg_folder).join(pkg_entry.name());
|
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
println!("-- Saved file!");
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct BdlHeader {
|
||||||
|
_magic_bytes: [u8; 4], //ibdl
|
||||||
|
_file_version: [u8; 8],
|
||||||
|
_unk1: u32,
|
||||||
|
pub pkg_count: u32,
|
||||||
|
_unk2: [u8; 12],
|
||||||
|
date_bytes: [u8; 256],
|
||||||
|
manufacturer_bytes: [u8; 256],
|
||||||
|
model_bytes: [u8; 256],
|
||||||
|
_unk3: [u8; 9],
|
||||||
|
version_bytes: [u8; 256],
|
||||||
|
info_bytes: [u8; 1280],
|
||||||
|
}
|
||||||
|
impl BdlHeader {
|
||||||
|
pub fn date(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.date_bytes)
|
||||||
|
}
|
||||||
|
pub fn manufacturer(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.manufacturer_bytes)
|
||||||
|
}
|
||||||
|
pub fn model(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.model_bytes)
|
||||||
|
}
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn info(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.info_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PkgListEntry {
|
||||||
|
pub offset: u64,
|
||||||
|
pub size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PkgHeader {
|
||||||
|
_magic_bytes: [u8; 4], //ipkg
|
||||||
|
_unk1: [u8; 12],
|
||||||
|
pub entry_count: u32,
|
||||||
|
_unk2: [u8; 12],
|
||||||
|
version_bytes: [u8; 256],
|
||||||
|
manufacturer_bytes: [u8; 256],
|
||||||
|
name_bytes: [u8; 256],
|
||||||
|
_unk3: [u8; 285],
|
||||||
|
}
|
||||||
|
impl PkgHeader {
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn manufacturer(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.manufacturer_bytes)
|
||||||
|
}
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PkgEntry {
|
||||||
|
name_bytes: [u8; 256],
|
||||||
|
pub offset: u64,
|
||||||
|
pub size: u64,
|
||||||
|
_crc32: u32,
|
||||||
|
}
|
||||||
|
impl PkgEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
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)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
|
if header == b"ibdl" {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_bdl(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
|
let header: BdlHeader = file.read_le()?;
|
||||||
|
|
||||||
|
println!("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();
|
||||||
|
|
||||||
|
for _i in 0..header.pkg_count {
|
||||||
|
let pkg_entry: PkgListEntry = file.read_le()?;
|
||||||
|
//println!("Package {} - Offset: {}, Size: {}", i + 1, pkg_entry.offset, pkg_entry.size);
|
||||||
|
pkgs.push(pkg_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
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: {}",
|
||||||
|
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();
|
||||||
|
|
||||||
|
for _i in 0..pkg_header.entry_count {
|
||||||
|
let pkg_entry: PkgEntry = file.read_le()?;
|
||||||
|
pkg_entries.push(pkg_entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkg_folder = Path::new(&app_ctx.output_dir).join(pkg_header.name());
|
||||||
|
fs::create_dir_all(&pkg_folder)?;
|
||||||
|
|
||||||
|
for (i, pkg_entry) in pkg_entries.iter().enumerate() {
|
||||||
|
println!("- 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;
|
||||||
|
let data = common::read_file(&file, calc_offset, pkg_entry.size as usize)?;
|
||||||
|
|
||||||
|
let output_path = Path::new(&pkg_folder).join(pkg_entry.name());
|
||||||
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
|
|
||||||
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
|
println!("-- Saved file!");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,30 +1,37 @@
|
|||||||
use std::fs::File;
|
use std::any::Any;
|
||||||
|
use std::io::Seek;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::formats;
|
use crate::formats;
|
||||||
|
|
||||||
pub fn is_epk_file(file: &File) -> bool {
|
pub struct EpkContext {
|
||||||
let versions = common::read_file(&file, 1712, 36).expect("Failed to read from file.");
|
epk_version: u8,
|
||||||
|
}
|
||||||
|
|
||||||
if check_epk_version(&versions).is_some() {
|
pub fn is_epk_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
true
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
|
||||||
|
let versions = common::read_file(&file, 1712, 36)?;
|
||||||
|
if let Some(epk_version) = check_epk_version(&versions) {
|
||||||
|
Ok(Some(Box::new(EpkContext {epk_version})))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_epk_version(versions: &[u8]) -> Option<String> {
|
fn check_epk_version(versions: &[u8]) -> Option<u8> {
|
||||||
// _ - 0x00 X - a number . - a dot
|
// _ - 0x00 X - a number . - a dot
|
||||||
let epk2_pattern = "____XXXX.XXXX.XXXX__XX.XX.XXX_______";
|
let epk2_pattern = "____XXXX.XXXX.XXXX__XX.XX.XXX_______";
|
||||||
let epk3_pattern = "____X.X.X___________X.X.X___________";
|
let epk3_pattern = "____X.X.X___________X.X.X___________";
|
||||||
let epk3_new_pattern = "____XX.X.X__________XX.X.X__________";
|
let epk3_new_pattern = "____XX.X.X__________XX.X.X__________";
|
||||||
|
|
||||||
if match_with_pattern(&versions, epk2_pattern) {
|
if match_with_pattern(&versions, epk2_pattern) {
|
||||||
Some("epk2".to_string())
|
Some(2)
|
||||||
} else if match_with_pattern(&versions, epk3_pattern) {
|
} else if match_with_pattern(&versions, epk3_pattern) {
|
||||||
Some("epk3".to_string())
|
Some(3)
|
||||||
} else if match_with_pattern(&versions, epk3_new_pattern) {
|
} else if match_with_pattern(&versions, epk3_new_pattern) {
|
||||||
Some("epk3".to_string())
|
Some(3)
|
||||||
}else {
|
}else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -42,20 +49,24 @@ fn match_with_pattern(data: &[u8], pattern: &str) -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_epk(file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
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 versions = common::read_file(&file, 1712, 36)?;
|
let versions = common::read_file(&file, 1712, 36)?;
|
||||||
let epk_version = check_epk_version(&versions);
|
|
||||||
|
|
||||||
let platform_version = common::string_from_bytes(&versions[4..20]);
|
let platform_version = common::string_from_bytes(&versions[4..20]);
|
||||||
let sdk_version = common::string_from_bytes(&versions[20..36]);
|
let sdk_version = common::string_from_bytes(&versions[20..36]);
|
||||||
println!("Platform version: {}\nSDK version: {}", platform_version, sdk_version);
|
println!("Platform version: {}\nSDK version: {}", platform_version, sdk_version);
|
||||||
|
|
||||||
if epk_version == Some("epk2".to_string()) {
|
file.seek(std::io::SeekFrom::Start(0))?;
|
||||||
|
|
||||||
|
if ctx.epk_version == 2 {
|
||||||
println!("EPK2 detected!\n");
|
println!("EPK2 detected!\n");
|
||||||
formats::epk2::extract_epk2(file, output_folder)?;
|
formats::epk2::extract_epk2(app_ctx, Box::new(()))?;
|
||||||
} else if epk_version == Some("epk3".to_string()) {
|
} else if ctx.epk_version == 3 {
|
||||||
println!("EPK3 detected!\n");
|
println!("EPK3 detected!\n");
|
||||||
formats::epk3::extract_epk3(file, output_folder)?;
|
formats::epk3::extract_epk3(app_ctx, Box::new(()))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct CommonHeader {
|
||||||
|
_magic_bytes: [u8; 4],
|
||||||
|
pub file_size: u32,
|
||||||
|
pub pak_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PakHeader {
|
||||||
|
pak_name_bytes: [u8; 4],
|
||||||
|
pub image_size: u32,
|
||||||
|
platform_id_bytes: [u8; 64],
|
||||||
|
_reserved: [u8; 56],
|
||||||
|
}
|
||||||
|
impl PakHeader {
|
||||||
|
pub fn pak_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.pak_name_bytes)
|
||||||
|
}
|
||||||
|
pub fn platform_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.platform_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Pak {
|
||||||
|
pub offset : u32,
|
||||||
|
pub size : u32,
|
||||||
|
}
|
||||||
@@ -1,51 +1,29 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_epk1_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct CommonHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
|
||||||
file_size: u32,
|
|
||||||
pak_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let epk2_magic = common::read_file(&file, 12, 4)?; //for epk2b
|
||||||
struct PakHeader {
|
let epak_magic = common::read_file(&file, 0, 4)?;
|
||||||
#[br(count = 4)] pak_name_bytes: Vec<u8>,
|
|
||||||
image_size: u32,
|
|
||||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
|
||||||
#[br(count = 56)] _reserved: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl PakHeader {
|
|
||||||
fn pak_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.pak_name_bytes)
|
|
||||||
}
|
|
||||||
fn platform_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.platform_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Pak {
|
|
||||||
offset : u32,
|
|
||||||
size : u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_epk1_file(file: &File) -> bool {
|
|
||||||
let epk2_magic = common::read_file(&file, 12, 4).expect("Failed to read from file."); //for epk2b
|
|
||||||
let epak_magic = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if epak_magic == b"epak" && epk2_magic != b"EPK2" {
|
if epak_magic == b"epak" && epk2_magic != b"EPK2" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_epk1(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
//check type of epk1
|
//check type of epk1
|
||||||
let epk1_type;
|
let epk1_type;
|
||||||
let init_pak_count_bytes = common::read_file(&file, 8, 4)?;
|
let init_pak_count_bytes = common::read_file(&file, 8, 4)?;
|
||||||
@@ -58,8 +36,7 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("\nLittle endian EPK1 detected.");
|
println!("\nLittle endian EPK1 detected.");
|
||||||
epk1_type = "le";
|
epk1_type = "le";
|
||||||
} else {
|
} else {
|
||||||
println!("\nUnknown EPK1 variant!");
|
return Err("Unknown EPK1 variant!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(0))?;
|
file.seek(SeekFrom::Start(0))?;
|
||||||
@@ -76,7 +53,10 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
paks.push(Pak { offset: pak.offset, size: pak.size });
|
paks.push(Pak { offset: pak.offset, size: pak.size });
|
||||||
}
|
}
|
||||||
assert!(header.pak_count as usize == paks.len(), "Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len());
|
|
||||||
|
if header.pak_count as usize != paks.len() {
|
||||||
|
return Err(format!("Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len()).into());
|
||||||
|
}
|
||||||
|
|
||||||
let version = common::read_exact(&mut file, 4)?;
|
let version = common::read_exact(&mut file, 4)?;
|
||||||
|
|
||||||
@@ -90,7 +70,9 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
let header_size_bytes = common::read_file(&file, 12, 4)?; //offset of first entry, can be treated as header size
|
let header_size_bytes = common::read_file(&file, 12, 4)?; //offset of first entry, can be treated as header size
|
||||||
let header_size = u32::from_le_bytes(header_size_bytes.try_into().unwrap());
|
let header_size = u32::from_le_bytes(header_size_bytes.try_into().unwrap());
|
||||||
let max_pak_count = (header_size - 48) / 8; //header size minus common header + ota id (48) divide by size of pak entry (8).
|
let max_pak_count = (header_size - 48) / 8; //header size minus common header + ota id (48) divide by size of pak entry (8).
|
||||||
assert!(max_pak_count < 128, "Unreasonable calculated pak count {}!!", max_pak_count);
|
if max_pak_count > 128 {
|
||||||
|
return Err(format!("Unreasonable calculated pak count {}!!", max_pak_count).into());
|
||||||
|
}
|
||||||
|
|
||||||
for _i in 0..max_pak_count {
|
for _i in 0..max_pak_count {
|
||||||
let pak: Pak = file.read_le()?;
|
let pak: Pak = file.read_le()?;
|
||||||
@@ -99,7 +81,10 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
paks.push(Pak { offset: pak.offset, size: pak.size });
|
paks.push(Pak { offset: pak.offset, size: pak.size });
|
||||||
}
|
}
|
||||||
assert!(header.pak_count as usize == paks.len(), "Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len());
|
|
||||||
|
if header.pak_count as usize != paks.len() {
|
||||||
|
return Err(format!("Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len()).into());
|
||||||
|
}
|
||||||
|
|
||||||
let version = common::read_exact(&mut file, 4)?;
|
let version = common::read_exact(&mut file, 4)?;
|
||||||
|
|
||||||
@@ -119,15 +104,13 @@ pub fn extract_epk1(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
|
|
||||||
let data = common::read_exact(&mut file, pak_header.image_size as usize)?;
|
let data = common::read_exact(&mut file, pak_header.image_size as usize)?;
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(pak_header.pak_name() + ".bin");
|
let output_path = Path::new(&app_ctx.output_dir).join(pak_header.pak_name() + ".bin");
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
println!("- Saved file!");
|
println!("- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub static SIGNATURE_SIZE: u32 = 128;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4], //epak
|
||||||
|
pub file_size: u32,
|
||||||
|
pub pak_count: u32,
|
||||||
|
_epk2_magic: [u8; 4], //EPK2
|
||||||
|
pub version: [u8; 4],
|
||||||
|
ota_id_bytes: [u8; 32],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn ota_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ota_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PakEntry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
name_bytes: [u8; 4],
|
||||||
|
_version: [u8; 4],
|
||||||
|
pub segment_size: u32,
|
||||||
|
}
|
||||||
|
impl PakEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PakHeader {
|
||||||
|
_pak_name_bytes: [u8; 4],
|
||||||
|
pub image_size: u32,
|
||||||
|
platform_id_bytes: [u8; 64],
|
||||||
|
_sw_version: u32,
|
||||||
|
_sw_date: u32,
|
||||||
|
_build_type: u32,
|
||||||
|
pub segment_count: u32,
|
||||||
|
pub segment_size: u32,
|
||||||
|
pub segment_index: u32,
|
||||||
|
_pak_magic_bytes: [u8; 4], //MPAK
|
||||||
|
_reserved: [u8; 24],
|
||||||
|
_segment_crc32: u32,
|
||||||
|
}
|
||||||
|
impl PakHeader {
|
||||||
|
pub fn platform_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.platform_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Pak {
|
||||||
|
pub offset: u32,
|
||||||
|
pub _size: u32,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
@@ -1,84 +1,32 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
use std::io::{Write, Seek, SeekFrom, Cursor};
|
use std::io::{Write, Seek, SeekFrom, Cursor};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
static SIGNATURE_SIZE: u32 = 128;
|
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)};
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 128, 4)?;
|
||||||
struct Header {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //epak
|
|
||||||
file_size: u32,
|
|
||||||
pak_count: u32,
|
|
||||||
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
|
|
||||||
#[br(count = 4)] version: Vec<u8>,
|
|
||||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn ota_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ota_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PakEntry {
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] _version: Vec<u8>,
|
|
||||||
segment_size: u32,
|
|
||||||
}
|
|
||||||
impl PakEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PakHeader {
|
|
||||||
#[br(count = 4)] _pak_name_bytes: Vec<u8>,
|
|
||||||
image_size: u32,
|
|
||||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
|
||||||
_sw_version: u32,
|
|
||||||
_sw_date: u32,
|
|
||||||
_build_type: u32,
|
|
||||||
segment_count: u32,
|
|
||||||
segment_size: u32,
|
|
||||||
segment_index: u32,
|
|
||||||
#[br(count = 4)] _pak_magic_bytes: Vec<u8>, //MPAK
|
|
||||||
#[br(count = 24)] _reserved: Vec<u8>,
|
|
||||||
_segment_crc32: u32,
|
|
||||||
}
|
|
||||||
impl PakHeader {
|
|
||||||
fn platform_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.platform_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Pak {
|
|
||||||
offset: u32,
|
|
||||||
_size: u32,
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_epk2_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 128, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"epak" {
|
if header == b"epak" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_epk2(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
file.seek(SeekFrom::Start(0))?;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
|
let _header_signature = common::read_exact(&mut file, SIGNATURE_SIZE as usize)?;
|
||||||
|
|
||||||
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
|
let stored_header = common::read_exact(&mut file, 1584)?; //max header size
|
||||||
let header;
|
let header;
|
||||||
|
|
||||||
@@ -98,8 +46,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
matching_key = Some(key_bytes);
|
matching_key = Some(key_bytes);
|
||||||
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
header = decrypt_aes_ecb_auto(matching_key.as_ref().unwrap(), &stored_header)?;
|
||||||
} else {
|
} else {
|
||||||
println!("No valid key found!");
|
return Err("No valid key found!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//parse header
|
//parse header
|
||||||
@@ -137,8 +84,7 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("Found correct key: {}", key_name);
|
println!("Found correct key: {}", key_name);
|
||||||
matching_key = Some(key_bytes);
|
matching_key = Some(key_bytes);
|
||||||
} else {
|
} else {
|
||||||
println!("No valid key found!");
|
return Err("No valid key found!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let matching_key_bytes = matching_key.as_ref().unwrap();
|
let matching_key_bytes = matching_key.as_ref().unwrap();
|
||||||
@@ -160,7 +106,9 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
pak_header = pak_header_reader.read_le()?;
|
pak_header = pak_header_reader.read_le()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(i == pak_header.segment_index, "Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index);
|
if i != pak_header.segment_index {
|
||||||
|
return Err(format!("Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index).into());
|
||||||
|
}
|
||||||
|
|
||||||
let actual_segment_size =
|
let actual_segment_size =
|
||||||
// check if this is the last segment and not the last PAK
|
// check if this is the last segment and not the last PAK
|
||||||
@@ -186,8 +134,8 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
let segment_data = common::read_exact(&mut file, actual_segment_size as usize)?;
|
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)?;
|
let out_data = decrypt_aes_ecb_auto(&matching_key_bytes, &segment_data)?;
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", pak.name));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", pak.name));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
@@ -195,7 +143,5 @@ pub fn extract_epk2(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct EpkHeader {
|
||||||
|
_epk_magic: [u8; 4], //epak
|
||||||
|
pub file_size: u32,
|
||||||
|
pub pak_count: u32,
|
||||||
|
_epk2_magic: [u8; 4], //EPK2
|
||||||
|
pub version: [u8; 4],
|
||||||
|
ota_id_bytes: [u8; 32],
|
||||||
|
}
|
||||||
|
impl EpkHeader {
|
||||||
|
pub fn ota_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ota_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PakHeader {
|
||||||
|
pak_name_bytes: [u8; 4],
|
||||||
|
pub image_size: u32,
|
||||||
|
platform_id_bytes: [u8; 64],
|
||||||
|
_sw_version: u32,
|
||||||
|
_sw_date: u32,
|
||||||
|
_build_type: u32,
|
||||||
|
pub segment_count: u32,
|
||||||
|
pub segment_size: u32,
|
||||||
|
pub segment_index: u32,
|
||||||
|
_pak_magic_bytes: [u8; 4], //MPAK
|
||||||
|
_reserved: [u8; 24],
|
||||||
|
_segment_crc32: u32,
|
||||||
|
}
|
||||||
|
impl PakHeader {
|
||||||
|
pub fn pak_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.pak_name_bytes)
|
||||||
|
}
|
||||||
|
pub fn platform_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.platform_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Pak {
|
||||||
|
pub offset : u32,
|
||||||
|
pub size : u32,
|
||||||
|
}
|
||||||
@@ -1,69 +1,31 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::{Path};
|
use std::path::{Path};
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_epk2b_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct EpkHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] _epk_magic: Vec<u8>, //epak
|
|
||||||
file_size: u32,
|
|
||||||
pak_count: u32,
|
|
||||||
#[br(count = 4)] _epk2_magic: Vec<u8>, //EPK2
|
|
||||||
#[br(count = 4)] version: Vec<u8>,
|
|
||||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl EpkHeader {
|
|
||||||
fn ota_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ota_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let epak_magic = common::read_file(&file, 0, 4)?;
|
||||||
struct PakHeader {
|
let epk2_magic = common::read_file(&file, 12, 4)?;
|
||||||
#[br(count = 4)] pak_name_bytes: Vec<u8>,
|
|
||||||
image_size: u32,
|
|
||||||
#[br(count = 64)] platform_id_bytes: Vec<u8>,
|
|
||||||
_sw_version: u32,
|
|
||||||
_sw_date: u32,
|
|
||||||
_build_type: u32,
|
|
||||||
segment_count: u32,
|
|
||||||
segment_size: u32,
|
|
||||||
segment_index: u32,
|
|
||||||
#[br(count = 4)] _pak_magic_bytes: Vec<u8>, //MPAK
|
|
||||||
#[br(count = 24)] _reserved: Vec<u8>,
|
|
||||||
_segment_crc32: u32,
|
|
||||||
}
|
|
||||||
impl PakHeader {
|
|
||||||
fn pak_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.pak_name_bytes)
|
|
||||||
}
|
|
||||||
fn platform_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.platform_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Pak {
|
|
||||||
offset : u32,
|
|
||||||
size : u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_epk2b_file(file: &File) -> bool {
|
|
||||||
let epk2_magic = common::read_file(&file, 12, 4).expect("Failed to read from file.");
|
|
||||||
let epak_magic = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if epak_magic == b"epak" && epk2_magic == b"EPK2" {
|
if epak_magic == b"epak" && epk2_magic == b"EPK2" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_epk2b(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let header: EpkHeader = file.read_le()?;
|
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?}",
|
println!("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]);
|
header.file_size, header.pak_count, header.ota_id(), header.version[2], header.version[1], header.version[0]);
|
||||||
|
|
||||||
@@ -77,7 +39,9 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
paks.push(Pak { offset: pak.offset, size: pak.size });
|
paks.push(Pak { offset: pak.offset, size: pak.size });
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(header.pak_count as usize == paks.len(), "Paks count in header does not match the amount of non empty pak entries!");
|
if header.pak_count as usize != paks.len() {
|
||||||
|
return Err(format!("Paks count in header({}) does not match the amount of non empty pak entries({})!", header.pak_count, paks.len()).into());
|
||||||
|
}
|
||||||
|
|
||||||
for (i, pak) in paks.iter().enumerate() {
|
for (i, pak) in paks.iter().enumerate() {
|
||||||
file.seek(SeekFrom::Start(pak.offset as u64))?;
|
file.seek(SeekFrom::Start(pak.offset as u64))?;
|
||||||
@@ -94,7 +58,9 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
pak_header = file.read_le()?;
|
pak_header = file.read_le()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(i == pak_header.segment_index, "Unexpected segment index in pak header!, expected: {}, got: {}", i , pak_header.segment_index);
|
if i != pak_header.segment_index {
|
||||||
|
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);
|
println!("- 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)?;
|
let out_data = common::read_exact(&mut file, pak_header.segment_size as usize)?;
|
||||||
@@ -108,8 +74,8 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
pak_header.segment_size
|
pak_header.segment_size
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", pak_header.pak_name()));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", pak_header.pak_name()));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data[..segment_limit as usize])?;
|
out_file.write_all(&out_data[..segment_limit as usize])?;
|
||||||
|
|
||||||
@@ -117,7 +83,5 @@ pub fn extract_epk2b(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4], //EPK3
|
||||||
|
pub version: [u8; 4],
|
||||||
|
ota_id_bytes: [u8; 32],
|
||||||
|
pub package_info_size: u32,
|
||||||
|
_bchunked: u32,
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn ota_id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ota_id_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct HeaderNewEx {
|
||||||
|
_pak_info_magic: [u8; 4],
|
||||||
|
encrypt_type_bytes: [u8; 6],
|
||||||
|
update_type_bytes: [u8; 6],
|
||||||
|
pub update_platform_version: f32,
|
||||||
|
pub compatible_minimum_version: f32,
|
||||||
|
pub need_to_check_compatible_version: i32,
|
||||||
|
}
|
||||||
|
impl HeaderNewEx {
|
||||||
|
pub fn encrypt_type(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.encrypt_type_bytes)
|
||||||
|
}
|
||||||
|
pub fn update_type(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.update_type_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PkgInfoHeader {
|
||||||
|
pub package_info_list_size: u32,
|
||||||
|
pub package_info_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PkgInfoEntry {
|
||||||
|
_package_type: u32,
|
||||||
|
_package_info_size: u32,
|
||||||
|
package_name_bytes: [u8; 128],
|
||||||
|
_package_version_bytes: [u8; 96],
|
||||||
|
_package_architecture_bytes: [u8; 32],
|
||||||
|
_checksum: [u8; 32],
|
||||||
|
pub package_size: u32,
|
||||||
|
_dipk: u32,
|
||||||
|
//segment info
|
||||||
|
_is_segmented: u32,
|
||||||
|
pub segment_index: u32,
|
||||||
|
pub segment_count: u32,
|
||||||
|
pub segment_size: u32,
|
||||||
|
//
|
||||||
|
_unk: u32,
|
||||||
|
}
|
||||||
|
impl PkgInfoEntry {
|
||||||
|
pub fn package_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.package_name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +1,24 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
use std::io::{Write, Seek, SeekFrom, Cursor};
|
use crate::AppContext;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::{Write, Cursor};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
use crate::formats::epk::{decrypt_aes_ecb_auto, find_key};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_epk3_file(_app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
Ok(None)
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //EPK3
|
|
||||||
#[br(count = 4)] version: Vec<u8>,
|
|
||||||
#[br(count = 32)] ota_id_bytes: Vec<u8>,
|
|
||||||
package_info_size: u32,
|
|
||||||
_bchunked: u32,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn ota_id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ota_id_bytes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn extract_epk3(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
struct HeaderNewEx {
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
#[br(count = 4)] _pak_info_magic: Vec<u8>,
|
|
||||||
#[br(count = 6)] encrypt_type_bytes: Vec<u8>,
|
|
||||||
#[br(count = 6)] update_type_bytes: Vec<u8>,
|
|
||||||
update_platform_version: f32,
|
|
||||||
compatible_minimum_version: f32,
|
|
||||||
need_to_check_compatible_version: i32,
|
|
||||||
}
|
|
||||||
impl HeaderNewEx {
|
|
||||||
fn encrypt_type(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.encrypt_type_bytes)
|
|
||||||
}
|
|
||||||
fn update_type(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.update_type_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PkgInfoHeader {
|
|
||||||
package_info_list_size: u32,
|
|
||||||
package_info_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PkgInfoEntry {
|
|
||||||
_package_type: u32,
|
|
||||||
_package_info_size: u32,
|
|
||||||
#[br(count = 128)] package_name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 96)] _package_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] _package_architecture_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] _checksum: Vec<u8>,
|
|
||||||
package_size: u32,
|
|
||||||
_dipk: u32,
|
|
||||||
//segment info
|
|
||||||
_is_segmented: u32,
|
|
||||||
segment_index: u32,
|
|
||||||
segment_count: u32,
|
|
||||||
segment_size: u32,
|
|
||||||
//
|
|
||||||
_unk: u32,
|
|
||||||
}
|
|
||||||
impl PkgInfoEntry {
|
|
||||||
fn package_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.package_name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
file.seek(SeekFrom::Start(0))?;
|
|
||||||
let stored_header = common::read_exact(&mut file, 1712)?;
|
let stored_header = common::read_exact(&mut file, 1712)?;
|
||||||
let header: Vec<u8>;
|
let header: Vec<u8>;
|
||||||
let _header_signature;
|
let _header_signature;
|
||||||
@@ -96,8 +43,7 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
new_type = true;
|
new_type = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
println!("No valid key found!");
|
return Err("No valid key found!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let signature_size = if new_type {256} else {128};
|
let signature_size = if new_type {256} else {128};
|
||||||
@@ -153,8 +99,8 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
let encrypted_data = common::read_exact(&mut file, entry.segment_size as usize + extra_segment_size)?;
|
let encrypted_data = common::read_exact(&mut file, entry.segment_size as usize + extra_segment_size)?;
|
||||||
let out_data = decrypt_aes_ecb_auto(matching_key_bytes, &encrypted_data)?;
|
let out_data = decrypt_aes_ecb_auto(matching_key_bytes, &encrypted_data)?;
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.package_name()));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", entry.package_name()));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data[extra_segment_size..])?;
|
out_file.write_all(&out_data[extra_segment_size..])?;
|
||||||
|
|
||||||
@@ -163,6 +109,5 @@ pub fn extract_epk3(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
pak_i += 1;
|
pak_i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 6],
|
||||||
|
pub entry_count: u16,
|
||||||
|
pub file_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub entry_type: u16,
|
||||||
|
pub entry_size: u32,
|
||||||
|
_unk: u16,
|
||||||
|
}
|
||||||
@@ -1,35 +1,28 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write};
|
use std::io::Write;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_funai_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 6)] _magic_bytes: Vec<u8>,
|
let header = common::read_file(&file, 0, 6)?;
|
||||||
entry_count: u16,
|
|
||||||
file_size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Entry {
|
|
||||||
entry_type: u16,
|
|
||||||
entry_size: u32,
|
|
||||||
_unk: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_funai_upg_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
|
|
||||||
if header == b"UPG\x00\x00\x00" {
|
if header == b"UPG\x00\x00\x00" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_funai_upg(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_funai_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
let header: Header = file.read_le()?;
|
let header: Header = file.read_le()?;
|
||||||
println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count);
|
println!("File info:\nFile size: {}\nEntry count: {}", header.file_size, header.entry_count);
|
||||||
|
|
||||||
@@ -45,16 +38,14 @@ pub fn extract_funai_upg(mut file: &File, output_folder: &str) -> Result<(), Box
|
|||||||
println!("Descriptor entry info:\n{}", entry_string);
|
println!("Descriptor entry info:\n{}", entry_string);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", i + 1, entry.entry_type));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}_{}.bin", i + 1, entry.entry_type));
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
println!("- Saved file!");
|
println!("- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
use std::path::{Path};
|
|
||||||
use std::fs::{self, File, OpenOptions};
|
|
||||||
use std::io::{Write, Read, Seek, SeekFrom, Cursor};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
|
||||||
use crate::utils::common;
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Header {
|
|
||||||
#[br(count = 16)] _magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] file_version: Vec<u8>,
|
|
||||||
_unk1: u32,
|
|
||||||
#[br(count = 16)] ver1_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] ver2_bytes: Vec<u8>,
|
|
||||||
_unk2: u16,
|
|
||||||
_type: u8,
|
|
||||||
keep_size: u32,
|
|
||||||
_unk3: u8,
|
|
||||||
data_start_offset: u32,
|
|
||||||
data_size: u32,
|
|
||||||
_data_size_2: u32,
|
|
||||||
skip_size: u32,
|
|
||||||
_unk4: u16,
|
|
||||||
_encryption_method: u8, // 0x01 - AES128, 0x02 - AES256
|
|
||||||
_hash_type: u8, // 0x01 - MD5, 0x02 - SHA1
|
|
||||||
#[br(count = 16)] ver3_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] ver4_bytes: Vec<u8>,
|
|
||||||
#[br(count = 11)] _unk6: Vec<u8>,
|
|
||||||
payload_count: u8,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn ver1(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ver1_bytes).replace('\n', "")
|
|
||||||
}
|
|
||||||
fn ver2(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ver2_bytes).replace('\n', "")
|
|
||||||
}
|
|
||||||
fn ver3(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ver3_bytes).replace('\n', "")
|
|
||||||
}
|
|
||||||
fn ver4(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.ver4_bytes).replace('\n', "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Entry {
|
|
||||||
#[br(count = 16)] name_bytes: Vec<u8>,
|
|
||||||
start_offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
impl Entry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_invincible_image_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 16).expect("Failed to read from file.");
|
|
||||||
if header == b"INVINCIBLE_IMAGE" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_invincible_image(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: Header = file.read_le()?;
|
|
||||||
|
|
||||||
println!("File info:\nFile Version: {}.{}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nData start offset: {}\nKeep data size: {}\nSkip data size: {}\n\nPayload Count: {}",
|
|
||||||
header.file_version[0], header.file_version[1], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.data_start_offset, header.keep_size, header.skip_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: {}",
|
|
||||||
i + 1, entry.name(), entry.start_offset, entry.size);
|
|
||||||
entries.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
if header.file_version[0] != 3 {
|
|
||||||
println!("\nSorry, this version of the file is not supported!");
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut encrypted_data = Vec::new();
|
|
||||||
let mut buffer = vec![0u8; header.keep_size as usize];
|
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(header.data_start_offset.into()))?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let bytes_read = file.read(&mut buffer)?;
|
|
||||||
if bytes_read == 0 {
|
|
||||||
break; // EOF
|
|
||||||
}
|
|
||||||
encrypted_data.extend_from_slice(&buffer[..bytes_read]);
|
|
||||||
file.seek(SeekFrom::Current(header.skip_size.into()))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
//v3 key + iv
|
|
||||||
let key: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36];
|
|
||||||
let iv: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43];
|
|
||||||
|
|
||||||
println!("\nDecrypting data...");
|
|
||||||
let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &key, &iv)?;
|
|
||||||
|
|
||||||
let mut data_reader = Cursor::new(decrypted_data);
|
|
||||||
|
|
||||||
let mut i = 1;
|
|
||||||
for entry in entries {
|
|
||||||
println!("\n({}/{}) - {}, Size: {}", i, header.payload_count, entry.name(), entry.size);
|
|
||||||
let data = common::read_exact(&mut data_reader, entry.size as usize)?;
|
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(entry.name() + ".bin");
|
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
println!("- Saved file!");
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
//v3 key + iv
|
||||||
|
pub static V3_KEY: [u8; 16] = [0x32, 0xe5, 0x26, 0x1e, 0x22, 0x67, 0x5e, 0x93, 0x20, 0xcf, 0x35, 0x91, 0x7c, 0x63, 0x7a, 0x36];
|
||||||
|
pub static V3_IV: [u8; 16] = [0xe3, 0x9f, 0x36, 0x39, 0x56, 0x9a, 0x6b, 0x8d, 0x3f, 0x2e, 0xc9, 0x44, 0xd9, 0xbc, 0xec, 0x43];
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 16],
|
||||||
|
pub file_version: [u8; 4],
|
||||||
|
_unk1: u32,
|
||||||
|
ver1_bytes: [u8; 16],
|
||||||
|
ver2_bytes: [u8; 16],
|
||||||
|
_unk2: u16,
|
||||||
|
_type: u8,
|
||||||
|
pub keep_size: u32,
|
||||||
|
_unk3: u8,
|
||||||
|
pub data_start_offset: u32,
|
||||||
|
pub data_size: u32,
|
||||||
|
_data_size_2: u32,
|
||||||
|
pub skip_size: u32,
|
||||||
|
_unk4: u16,
|
||||||
|
_encryption_method: u8, // 0x01 - AES128, 0x02 - AES256
|
||||||
|
_hash_type: u8, // 0x01 - MD5, 0x02 - SHA1
|
||||||
|
ver3_bytes: [u8; 16],
|
||||||
|
ver4_bytes: [u8; 16],
|
||||||
|
_unk6: [u8; 11],
|
||||||
|
pub payload_count: u8,
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn ver1(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ver1_bytes).replace('\n', "")
|
||||||
|
}
|
||||||
|
pub fn ver2(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ver2_bytes).replace('\n', "")
|
||||||
|
}
|
||||||
|
pub fn ver3(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ver3_bytes).replace('\n', "")
|
||||||
|
}
|
||||||
|
pub fn ver4(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.ver4_bytes).replace('\n', "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Entry {
|
||||||
|
name_bytes: [u8; 16],
|
||||||
|
pub start_offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
|
impl Entry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::{Path};
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{Write, Read, Seek, SeekFrom, Cursor};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
||||||
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
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)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 16)?;
|
||||||
|
if header == b"INVINCIBLE_IMAGE" {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_invincible_image(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
|
let header: Header = file.read_le()?;
|
||||||
|
println!("File info:\nFile Version: {}.{}\nVersion(1): {}\nVersion(2): {}\nVersion(3): {}\nVersion(4): {}\nData size: {}\nData start offset: {}\nKeep data size: {}\nSkip data size: {}\n\nPayload Count: {}",
|
||||||
|
header.file_version[0], header.file_version[1], header.ver1(), header.ver2(), header.ver3(), header.ver4(), header.data_size, header.data_start_offset, header.keep_size, header.skip_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: {}",
|
||||||
|
i + 1, entry.name(), entry.start_offset, entry.size);
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
if header.file_version[0] != 3 {
|
||||||
|
return Err("Unsupported format version! (Only 3 is supported right now)".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut encrypted_data = Vec::new();
|
||||||
|
let mut buffer = vec![0u8; header.keep_size as usize];
|
||||||
|
|
||||||
|
file.seek(SeekFrom::Start(header.data_start_offset.into()))?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let bytes_read = file.read(&mut buffer)?;
|
||||||
|
if bytes_read == 0 {
|
||||||
|
break; // EOF
|
||||||
|
}
|
||||||
|
encrypted_data.extend_from_slice(&buffer[..bytes_read]);
|
||||||
|
file.seek(SeekFrom::Current(header.skip_size.into()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nDecrypting data...");
|
||||||
|
let decrypted_data = decrypt_aes128_cbc_nopad(&encrypted_data, &V3_KEY, &V3_IV)?;
|
||||||
|
|
||||||
|
let mut data_reader = Cursor::new(decrypted_data);
|
||||||
|
|
||||||
|
let mut i = 1;
|
||||||
|
for entry in entries {
|
||||||
|
println!("\n({}/{}) - {}, Size: {}", i, header.payload_count, entry.name(), entry.size);
|
||||||
|
let data = common::read_exact(&mut data_reader, entry.size as usize)?;
|
||||||
|
|
||||||
|
let output_path = Path::new(&app_ctx.output_dir).join(entry.name() + ".bin");
|
||||||
|
|
||||||
|
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(&data)?;
|
||||||
|
|
||||||
|
println!("- Saved file!");
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
//MSD OUITH parsers
|
||||||
|
pub mod msd_ouith_parser_old;
|
||||||
|
pub mod msd_ouith_parser_tizen_1_8;
|
||||||
|
pub mod msd_ouith_parser_tizen_1_9;
|
||||||
|
|
||||||
// COMMON MSD FUNCTIONS
|
// COMMON MSD FUNCTIONS
|
||||||
use aes::Aes128;
|
use aes::Aes128;
|
||||||
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}};
|
use cbc::{Decryptor, cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit}};
|
||||||
@@ -7,7 +12,9 @@ type Aes128CbcDec = Decryptor<Aes128>;
|
|||||||
pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
let mut data = encrypted_data.to_vec();
|
let mut data = encrypted_data.to_vec();
|
||||||
|
|
||||||
assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!");
|
if data[0..8].to_vec() != b"Salted__" {
|
||||||
|
return Err("Invalid encrypted data!".into());
|
||||||
|
}
|
||||||
let salt = &data[8..16];
|
let salt = &data[8..16];
|
||||||
|
|
||||||
//key = md5 of (passphrase + salt)
|
//key = md5 of (passphrase + salt)
|
||||||
@@ -33,7 +40,9 @@ pub fn decrypt_aes_salted_old(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>)
|
|||||||
pub fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
pub fn decrypt_aes_salted_tizen(encrypted_data: &[u8], passphrase_bytes: &Vec<u8>) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
let mut data = encrypted_data.to_vec();
|
let mut data = encrypted_data.to_vec();
|
||||||
|
|
||||||
assert!(data[0..8].to_vec() == b"Salted__", "invalid encrypted data!");
|
if data[0..8].to_vec() != b"Salted__" {
|
||||||
|
return Err("Invalid encrypted data!".into());
|
||||||
|
}
|
||||||
let salt = &data[8..16];
|
let salt = &data[8..16];
|
||||||
|
|
||||||
//iv = md5 of salt
|
//iv = md5 of salt
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
_magic_bytes: [u8; 6],
|
||||||
|
pub section_count: u32
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct SectionEntry {
|
||||||
|
pub index: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct HeaderEntry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
_name_length: u8,
|
||||||
|
#[br(count = _name_length)] name_bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl HeaderEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,64 +1,42 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen};
|
use crate::formats::msd::{decrypt_aes_salted_old, decrypt_aes_salted_tizen, decrypt_aes_tizen};
|
||||||
use crate::utils::msd_ouith_parser_old::{parse_ouith_blob};
|
use crate::formats::msd::msd_ouith_parser_old::{parse_ouith_blob};
|
||||||
use crate::utils::msd_ouith_parser_tizen_1_8::{parse_blob_1_8};
|
use crate::formats::msd::msd_ouith_parser_tizen_1_8::{parse_blob_1_8};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_msd10_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct FileHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 6)] _magic_bytes: Vec<u8>,
|
|
||||||
section_count: u32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 0, 6)?;
|
||||||
struct SectionEntry {
|
|
||||||
index: u32,
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct HeaderEntry {
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
_name_length: u8,
|
|
||||||
#[br(count = _name_length)] name_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl HeaderEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Section {
|
|
||||||
index: u32,
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_msd10_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
|
|
||||||
if header == b"MSDU10" {
|
if header == b"MSDU10" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_msd10(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 save_cmac = app_ctx.options.iter().any(|e| e == "msd10:save_cmac");
|
||||||
|
|
||||||
let header: FileHeader = file.read_le()?;
|
let header: FileHeader = file.read_le()?;
|
||||||
println!("\nNumber of sections: {}", header.section_count);
|
println!("\nNumber of sections: {}", header.section_count);
|
||||||
|
|
||||||
let mut sections: Vec<Section> = Vec::new();
|
let mut sections: Vec<SectionEntry> = Vec::new();
|
||||||
for _i in 0..header.section_count {
|
for _i in 0..header.section_count {
|
||||||
let section: SectionEntry = file.read_le()?;
|
let section: SectionEntry = file.read_le()?;
|
||||||
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
|
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
|
||||||
sections.push(Section {index: section.index, offset: section.offset, size: section.size});
|
sections.push(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
let _zero_padding = common::read_exact(&mut file, 4)?;
|
let _zero_padding = common::read_exact(&mut file, 4)?;
|
||||||
@@ -93,8 +71,7 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
passphrase_bytes = hex::decode(p)?;
|
passphrase_bytes = hex::decode(p)?;
|
||||||
println!("Firmware type: {}", firmware_type);
|
println!("Firmware type: {}", firmware_type);
|
||||||
} else {
|
} else {
|
||||||
println!("Sorry, this firmware is not supported!");
|
return Err("This firmware is not supported!".into());
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let toc_offset = headers[0].offset;
|
let toc_offset = headers[0].offset;
|
||||||
@@ -118,7 +95,9 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("\n({}/{}) - {}, Size: {}",
|
println!("\n({}/{}) - {}, Size: {}",
|
||||||
i + 1, items.len(), item.name, size);
|
i + 1, items.len(), item.name, size);
|
||||||
|
|
||||||
assert!(sections[i as usize].index == item.item_id, "Item ID in TOC does not match ID from header!");
|
if sections[i as usize].index != item.item_id {
|
||||||
|
return Err("Item ID in TOC does not match ID from header!".into());
|
||||||
|
}
|
||||||
|
|
||||||
let stored_data = common::read_file(&file, offset as u64, size as usize)?;
|
let stored_data = common::read_file(&file, offset as u64, size as usize)?;
|
||||||
|
|
||||||
@@ -131,8 +110,8 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
out_data = stored_data;
|
out_data = stored_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(item.name.clone());
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}", item.name));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
@@ -155,12 +134,19 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("\n({}/{}) - {}, Type: {}, Size: {}",
|
println!("\n({}/{}) - {}, Type: {}, Size: {}",
|
||||||
item.item_id, items.len(), item.name, type_str, item.all_size);
|
item.item_id, items.len(), item.name, type_str, item.all_size);
|
||||||
|
|
||||||
assert!(sections[i as usize].index == item.item_id, "Item ID in TOC does not match ID from header!");
|
if sections[i as usize].index != item.item_id {
|
||||||
|
return Err("Item ID in TOC does not match ID from header!".into());
|
||||||
|
}
|
||||||
|
|
||||||
if item.item_type == 0x11 { //Skip CMAC DATA
|
let mut out_filename = format!("{}", item.name);
|
||||||
|
if item.item_type == 0x11 { //CMAC DATA
|
||||||
|
if 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...");
|
println!("- Skipping CMAC Data...");
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(offset as u64))?;
|
file.seek(SeekFrom::Start(offset as u64))?;
|
||||||
|
|
||||||
@@ -176,8 +162,8 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
out_data = stored_data;
|
out_data = stored_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(item.name.clone());
|
let output_path = Path::new(&app_ctx.output_dir).join(out_filename);
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
@@ -185,7 +171,5 @@ pub fn extract_msd10(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
_magic_bytes: [u8; 6],
|
||||||
|
_header_checksum: u32, //CRC32 of data with the size of "_header_size"
|
||||||
|
_header_size: u64,
|
||||||
|
pub section_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct SectionEntry {
|
||||||
|
pub index: u32,
|
||||||
|
pub offset: u64,
|
||||||
|
pub size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct HeaderEntry {
|
||||||
|
pub offset: u64,
|
||||||
|
pub size: u32,
|
||||||
|
_name_length: u8,
|
||||||
|
#[br(count = _name_length)] name_bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl HeaderEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +1,40 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
use std::io::{Write};
|
use crate::AppContext;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::Write;
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::formats::msd::{decrypt_aes_salted_tizen, decrypt_aes_tizen};
|
use crate::formats::msd::{decrypt_aes_salted_tizen, decrypt_aes_tizen};
|
||||||
use crate::utils::msd_ouith_parser_tizen_1_9::{parse_blob_1_9};
|
use crate::formats::msd::msd_ouith_parser_tizen_1_9::{parse_blob_1_9};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_msd11_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct FileHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 6)] _magic_bytes: Vec<u8>,
|
|
||||||
_header_checksum: u32, //CRC32 of data with the size of "_header_size"
|
|
||||||
_header_size: u64,
|
|
||||||
section_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 0, 6)?;
|
||||||
struct SectionEntry {
|
|
||||||
index: u32,
|
|
||||||
offset: u64,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct HeaderEntry {
|
|
||||||
offset: u64,
|
|
||||||
size: u32,
|
|
||||||
_name_length: u8,
|
|
||||||
#[br(count = _name_length)] name_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl HeaderEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Section {
|
|
||||||
index: u32,
|
|
||||||
offset: u64,
|
|
||||||
size: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_msd11_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 6).expect("Failed to read from file.");
|
|
||||||
if header == b"MSDU11" {
|
if header == b"MSDU11" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_msd11(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
let header: FileHeader = file.read_le()?;
|
let header: FileHeader = file.read_le()?;
|
||||||
println!("\nNumber of sections: {}", header.section_count);
|
println!("\nNumber of sections: {}", header.section_count);
|
||||||
|
|
||||||
let mut sections: Vec<Section> = Vec::new();
|
let mut sections: Vec<SectionEntry> = Vec::new();
|
||||||
for _i in 0..header.section_count {
|
for _i in 0..header.section_count {
|
||||||
let section: SectionEntry = file.read_le()?;
|
let section: SectionEntry = file.read_le()?;
|
||||||
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
|
println!("Section {}: offset: {}, size: {}", section.index, section.offset, section.size);
|
||||||
sections.push(Section {index: section.index, offset: section.offset, size: section.size});
|
sections.push(section);
|
||||||
}
|
}
|
||||||
|
|
||||||
let header_count: u32 = file.read_le()?;
|
let header_count: u32 = file.read_le()?;
|
||||||
@@ -90,8 +65,7 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("Passphrase: {}", p);
|
println!("Passphrase: {}", p);
|
||||||
passphrase_bytes = hex::decode(p)?;
|
passphrase_bytes = hex::decode(p)?;
|
||||||
} else {
|
} else {
|
||||||
println!("Sorry, this firmware is not supported!");
|
return Err("This firmware is not supported!".into());
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let toc_offset = headers[0].offset + 8;
|
let toc_offset = headers[0].offset + 8;
|
||||||
@@ -113,7 +87,9 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
println!("\n({}/{}) - {}, Size: {}",
|
println!("\n({}/{}) - {}, Size: {}",
|
||||||
i + 1, items.len(), item.name, size);
|
i + 1, items.len(), item.name, size);
|
||||||
|
|
||||||
assert!(sections[i as usize].index == item.item_id, "Item ID in TOC does not match ID from header!");
|
if sections[i as usize].index != item.item_id {
|
||||||
|
return Err("Item ID in TOC does not match ID from header!".into());
|
||||||
|
}
|
||||||
|
|
||||||
let stored_data = common::read_file(&file, offset as u64, size as usize)?;
|
let stored_data = common::read_file(&file, offset as u64, size as usize)?;
|
||||||
|
|
||||||
@@ -126,15 +102,13 @@ pub fn extract_msd11(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
out_data = stored_data;
|
out_data = stored_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(item.name.clone());
|
let output_path = Path::new(&app_ctx.output_dir).join(item.name.clone());
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub fn parse_number(s: &str) -> Option<u64> {
|
||||||
|
if let Some(hex_str) = s.strip_prefix("0x") {
|
||||||
|
u64::from_str_radix(hex_str, 16).ok()
|
||||||
|
} else {
|
||||||
|
u64::from_str_radix(s, 16).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,31 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
use std::path::{Path};
|
use std::any::Any;
|
||||||
use std::io::{Write};
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::compression::{decompress_lzma, decompress_lz4};
|
use crate::utils::compression::{decompress_lzma, decompress_lz4};
|
||||||
use crate::utils::lzop::{unlzop_to_file};
|
use crate::utils::lzop::{unlzop_to_file};
|
||||||
use crate::utils::sparse::{unsparse_to_file};
|
use crate::utils::sparse::{unsparse_to_file};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
//change whether the "userdata" partition is skipped
|
pub fn is_mstar_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
// this is because the userdata partition is sometimes enourmous sizes like 27gb, and it is empty anyways
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
// if you want to extract "userdata" you can disable the option
|
|
||||||
static CONFIG_SKIP_USERDATA: bool = true;
|
|
||||||
|
|
||||||
pub fn is_mstar_file(file: &File) -> bool {
|
let header = common::read_file(&file, 0, 32768)?;
|
||||||
let header = common::read_file(&file, 0, 32768).expect("Failed to read from file.");
|
|
||||||
let header_string = String::from_utf8_lossy(&header);
|
let header_string = String::from_utf8_lossy(&header);
|
||||||
|
|
||||||
if header_string.contains("filepartload"){
|
if header_string.contains("filepartload"){
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_number(s: &str) -> Option<u64> {
|
pub fn extract_mstar(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if let Some(hex_str) = s.strip_prefix("0x") {
|
let file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
u64::from_str_radix(hex_str, 16).ok()
|
|
||||||
} else {
|
|
||||||
u64::from_str_radix(s, 16).ok()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
|
|
||||||
let mut script = common::read_file(&file, 0, 32768)?;
|
let mut script = common::read_file(&file, 0, 32768)?;
|
||||||
|
|
||||||
@@ -53,7 +47,7 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
|
|||||||
script_string = String::from_utf8_lossy(&script);
|
script_string = String::from_utf8_lossy(&script);
|
||||||
|
|
||||||
if script_string == "" {
|
if script_string == "" {
|
||||||
println!("Failed to get script.");
|
return Err("Failed to get script".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,12 +126,10 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
|
|||||||
|
|
||||||
if partname == "unknown" {
|
if partname == "unknown" {
|
||||||
println!("- Unknown destination, skipping!");
|
println!("- Unknown destination, skipping!");
|
||||||
} else if partname == "userdata" && CONFIG_SKIP_USERDATA {
|
|
||||||
println!("- Skipping userdata according to config!")
|
|
||||||
} else {
|
} else {
|
||||||
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
|
let data = common::read_file(&file, offset, size.try_into().unwrap())?;
|
||||||
let out_data;
|
let out_data;
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", partname));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", partname));
|
||||||
|
|
||||||
if compression == "lzma" {
|
if compression == "lzma" {
|
||||||
println!("- Decompressing LZMA...");
|
println!("- Decompressing LZMA...");
|
||||||
@@ -166,7 +158,7 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
|
|||||||
out_data = data;
|
out_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().append(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
@@ -176,8 +168,5 @@ pub fn extract_mstar(file: &File, output_folder: &str) -> Result<(), Box<dyn std
|
|||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
|
||||||
println!("Extraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
|
||||||
|
data.windows(pattern.len()).position(|window| window == pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
|
||||||
|
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 pit_offset: u32,
|
||||||
|
pub pit_size: u32,
|
||||||
|
_table_id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PITITBITEntry {
|
||||||
|
pub bit_offset: u32,
|
||||||
|
pub bit_size: u32,
|
||||||
|
_private_data_1: u32,
|
||||||
|
_private_data_2: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PITHeader {
|
||||||
|
pub pit_magic: [u8; 8],
|
||||||
|
_version: u32,
|
||||||
|
pub first_entry_offset: u32, //"header len"
|
||||||
|
pub entry_size: u32, //"item lenght"
|
||||||
|
pub entry_count: u32, //"item num"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PITEntry {
|
||||||
|
name_bytes: [u8; 16],
|
||||||
|
pub partition_id: u32,
|
||||||
|
_part_info: u32,
|
||||||
|
pub offset_on_nand: u32,
|
||||||
|
pub size_on_nand: u32,
|
||||||
|
_enc_size: u32,
|
||||||
|
_no_enc_size: u32,
|
||||||
|
_reserve: [u8; 24],
|
||||||
|
}
|
||||||
|
impl PITEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct BITEntry {
|
||||||
|
pub partition_id: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
pub offset_in_target_part: u32,
|
||||||
|
_bin_info: u32, //"Bin info"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85];
|
||||||
|
pub static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT
|
||||||
@@ -1,78 +1,21 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::{Path};
|
use std::path::{Path};
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Seek, SeekFrom, Read, Write};
|
use std::io::{Seek, SeekFrom, Read, Write};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
pub struct MtkBdpContext {
|
pub struct MtkBdpContext {
|
||||||
pitit_offset: u64,
|
pitit_offset: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
|
pub fn is_mtk_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
|
let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PITITPITEntry {
|
|
||||||
nand_size: u32,
|
|
||||||
pit_offset: u32,
|
|
||||||
pit_size: u32,
|
|
||||||
_table_id: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PITITBITEntry {
|
|
||||||
bit_offset: u32,
|
|
||||||
bit_size: u32,
|
|
||||||
_private_data_1: u32,
|
|
||||||
_private_data_2: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PITHeader {
|
|
||||||
#[br(count = 8)] pit_magic: Vec<u8>,
|
|
||||||
_version: u32,
|
|
||||||
first_entry_offset: u32, //"header len"
|
|
||||||
entry_size: u32, //"item lenght"
|
|
||||||
entry_count: u32, //"item num"
|
|
||||||
}
|
|
||||||
|
|
||||||
static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PITEntry {
|
|
||||||
#[br(count = 16)] name_bytes: Vec<u8>,
|
|
||||||
partition_id: u32,
|
|
||||||
_part_info: u32,
|
|
||||||
offset_on_nand: u32,
|
|
||||||
size_on_nand: u32,
|
|
||||||
_enc_size: u32,
|
|
||||||
_no_enc_size: u32,
|
|
||||||
#[br(count = 24)] _reserve: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl PITEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct BITEntry {
|
|
||||||
partition_id: u32,
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
offset_in_target_part: u32,
|
|
||||||
_bin_info: u32, //"Bin info"
|
|
||||||
}
|
|
||||||
|
|
||||||
static BIT_MAGIC: [u8; 20] = [0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85, 0xCD, 0xAB, 0x30, 0x85];
|
|
||||||
static BIT_END_MARKER: u32 = 0x85_30_EF_EF; //EF EF 30 85 - end marker of BIT
|
|
||||||
|
|
||||||
fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
|
|
||||||
data.windows(pattern.len()).position(|window| window == pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_mtk_bdp_file(mut file: &File) -> Result<Option<MtkBdpContext>, Box<dyn std::error::Error>> {
|
|
||||||
let file_size = file.metadata()?.len();
|
let file_size = file.metadata()?.len();
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
||||||
@@ -83,14 +26,17 @@ pub fn is_mtk_bdp_file(mut file: &File) -> Result<Option<MtkBdpContext>, Box<dyn
|
|||||||
file.read_to_end(&mut data)?;
|
file.read_to_end(&mut data)?;
|
||||||
|
|
||||||
if let Some(pos) = find_bytes(&data, &PITIT_MAGIC) {
|
if let Some(pos) = find_bytes(&data, &PITIT_MAGIC) {
|
||||||
Ok(Some(MtkBdpContext {pitit_offset: start_offset + pos as u64}))
|
Ok(Some(Box::new(MtkBdpContext {pitit_offset: start_offset + pos as u64})))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpContext) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_mtk_bdp(app_ctx: &AppContext, ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let offset = context.pitit_offset;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
let ctx = ctx.downcast::<MtkBdpContext>().expect("Missing context");
|
||||||
|
|
||||||
|
let offset = ctx.pitit_offset;
|
||||||
println!("\nReading PITIT at: {}", offset);
|
println!("\nReading PITIT at: {}", offset);
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(offset + 8))?;
|
file.seek(SeekFrom::Start(offset + 8))?;
|
||||||
@@ -126,8 +72,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont
|
|||||||
let mut pit_entries: Vec<PITEntry> = Vec::new();
|
let mut pit_entries: Vec<PITEntry> = Vec::new();
|
||||||
let pit_header: PITHeader = file.read_le()?;
|
let pit_header: PITHeader = file.read_le()?;
|
||||||
if pit_header.pit_magic != PIT_MAGIC {
|
if pit_header.pit_magic != PIT_MAGIC {
|
||||||
println!("Invalid PIT Magic!");
|
return Err("Invalid PIT magic!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
println!("PIT Info - First entry offs: {}, Entry size: {}, Entry count: {}", pit_header.first_entry_offset, pit_header.entry_size, pit_header.entry_count);
|
println!("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))?;
|
file.seek(SeekFrom::Start(pit_offset + pit_header.first_entry_offset as u64))?;
|
||||||
@@ -144,8 +89,7 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont
|
|||||||
let mut bit_entries: Vec<BITEntry> = Vec::new();
|
let mut bit_entries: Vec<BITEntry> = Vec::new();
|
||||||
let bit_magic = common::read_exact(&mut file, 20)?;
|
let bit_magic = common::read_exact(&mut file, 20)?;
|
||||||
if bit_magic != BIT_MAGIC {
|
if bit_magic != BIT_MAGIC {
|
||||||
println!("Invalid BIT Magic!");
|
return Err("Invalid BIT magic!".into());
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut bit_i = 0;
|
let mut bit_i = 0;
|
||||||
@@ -173,8 +117,8 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont
|
|||||||
|
|
||||||
let data = common::read_file(&file, bit_entry.offset as u64, bit_entry.size as usize)?;
|
let data = common::read_file(&file, bit_entry.offset as u64, bit_entry.size as usize)?;
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", name));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", name));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
||||||
out_file.seek(SeekFrom::Start(bit_entry.offset_in_target_part as u64))?;
|
out_file.seek(SeekFrom::Start(bit_entry.offset_in_target_part as u64))?;
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
@@ -182,7 +126,5 @@ pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpCont
|
|||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static HEADER_IV: [u8; 16] = [0x00; 16];
|
||||||
|
|
||||||
|
pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm";
|
||||||
|
pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc";
|
||||||
|
pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK";
|
||||||
|
pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd";
|
||||||
|
pub static CRYPTED_HEADER_SIZE: usize = 0x30;
|
||||||
|
|
||||||
|
pub static HEADER_SIZE: usize = 0x90;
|
||||||
|
|
||||||
|
pub static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80;
|
||||||
|
pub static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
pub vendor_magic_bytes: [u8; 4],
|
||||||
|
_mtk_magic: [u8; 8], //#DH@FiRm
|
||||||
|
version_bytes: [u8; 60],
|
||||||
|
pub file_size: u32,
|
||||||
|
_flags: u32,
|
||||||
|
product_name_bytes: [u8; 32],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn vendor_magic(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.vendor_magic_bytes)
|
||||||
|
}
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn product_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.product_name_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PartEntry {
|
||||||
|
name_bytes: [u8; 4],
|
||||||
|
pub flags: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
|
impl PartEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
self.name().is_ascii()
|
||||||
|
}
|
||||||
|
pub fn is_encrypted(&self) -> bool {
|
||||||
|
(self.flags & 1 << 0) != 0
|
||||||
|
}
|
||||||
|
pub fn is_compressed(&self) -> bool { //lzhs fs
|
||||||
|
(self.flags & 1 << 8) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ use std::io::{Write, Cursor, Seek, SeekFrom, Read};
|
|||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::{BinRead, BinReaderExt};
|
||||||
use std::path::{PathBuf};
|
use std::path::{PathBuf};
|
||||||
|
|
||||||
use crate::utils::huffman_tables::{CHARLEN, POS};
|
use super::huffman_tables::{CHARLEN, POS};
|
||||||
use crate::utils::compression::{decompress_lz4};
|
use crate::utils::compression::{decompress_lz4};
|
||||||
|
|
||||||
#[derive(BinRead)]
|
#[derive(BinRead)]
|
||||||
@@ -11,7 +11,7 @@ struct LzhsHeader {
|
|||||||
uncompressed_size: u32,
|
uncompressed_size: u32,
|
||||||
compressed_size: u32,
|
compressed_size: u32,
|
||||||
checksum_or_seg_idx: u16, //as checksum in normal lzhs header, as index in lzhs_fs header
|
checksum_or_seg_idx: u16, //as checksum in normal lzhs header, as index in lzhs_fs header
|
||||||
#[br(count = 6)] padding: Vec<u8>,
|
padding: [u8; 6],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BinRead)]
|
#[derive(BinRead)]
|
||||||
@@ -35,7 +35,7 @@ pub fn decompress_lzhs_fs_file2file(mut file: &File, output_file: PathBuf) -> Re
|
|||||||
|
|
||||||
//lz4 type uses a 4 byte checksum instead of 1(2) byte , so the padding is not 0 anymore
|
//lz4 type uses a 4 byte checksum instead of 1(2) byte , so the padding is not 0 anymore
|
||||||
//maybe this method will be changed
|
//maybe this method will be changed
|
||||||
let is_lz4 = if lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false};
|
let is_lz4 = if &lzhs_header.padding != b"\x00\x00\x00\x00\x00\x00" {true} else {false};
|
||||||
|
|
||||||
println!("[{}] Segment {} - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
|
println!("[{}] Segment {} - Compressed size: {}, Decompressed size: {}, Expected Checksum: 0x{:02x?}",
|
||||||
if is_lz4 {"LZ4"} else {"LZHS"}, segment_header.checksum_or_seg_idx, lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx);
|
if is_lz4 {"LZ4"} else {"LZHS"}, segment_header.checksum_or_seg_idx, lzhs_header.compressed_size, lzhs_header.uncompressed_size, lzhs_header.checksum_or_seg_idx);
|
||||||
@@ -1,91 +1,38 @@
|
|||||||
|
pub mod include;
|
||||||
|
pub mod lzhs;
|
||||||
|
mod huffman_tables;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Cursor, Seek, SeekFrom};
|
use std::io::{Write, Cursor, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
||||||
use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
|
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
|
use lzhs::{decompress_lzhs_fs_file2file};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
pub struct MtkPkgContext {
|
pub struct MtkPkgContext {
|
||||||
is_philips_variant: bool,
|
is_philips_variant: bool,
|
||||||
decrypted_header: Vec<u8>,
|
decrypted_header: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_mtk_pkg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
|
|
||||||
#[br(count = 60)] version_bytes: Vec<u8>,
|
|
||||||
file_size: u32,
|
|
||||||
_flags: u32,
|
|
||||||
#[br(count = 32)] product_name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] _digest: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn vendor_magic(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.vendor_magic_bytes)
|
|
||||||
}
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn product_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.product_name_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PartEntry {
|
|
||||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
|
||||||
flags: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
impl PartEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn is_valid(&self) -> bool {
|
|
||||||
self.name().is_ascii()
|
|
||||||
}
|
|
||||||
fn is_encrypted(&self) -> bool {
|
|
||||||
(self.flags & 1 << 0) != 0
|
|
||||||
}
|
|
||||||
fn is_compressed(&self) -> bool { //lzhs fs
|
|
||||||
(self.flags & 1 << 8) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static MTK_HEADER_MAGIC: &[u8; 8] = b"#DH@FiRm";
|
|
||||||
pub static MTK_RESERVED_MAGIC: &[u8; 16] = b"reserved mtk inc";
|
|
||||||
pub static MTK_META_MAGIC: &[u8; 4] = b"iMtK";
|
|
||||||
pub static MTK_META_PAD_MAGIC: &[u8; 4] = b"iPAd";
|
|
||||||
pub static CRYPTED_HEADER_SIZE: usize = 0x30;
|
|
||||||
|
|
||||||
static HEADER_SIZE: usize = 0x90;
|
|
||||||
|
|
||||||
static PHILIPS_EXTRA_HEADER_SIZE: usize = 0x80;
|
|
||||||
static _PHILIPS_FOOTER_SIGNATURE_SIZE: usize = 0x100;
|
|
||||||
|
|
||||||
static HEADER_KEY: [u8; 16] = [
|
|
||||||
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
|
|
||||||
0x09, 0x29, 0x10, 0x94, 0x09, 0x29, 0x10, 0x94,
|
|
||||||
];
|
|
||||||
|
|
||||||
static HEADER_IV: [u8; 16] = [0x00; 16];
|
|
||||||
|
|
||||||
pub fn is_mtk_pkg_file(file: &File) -> Result<Option<MtkPkgContext>, Box<dyn std::error::Error>> {
|
|
||||||
let mut encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
|
let mut encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
|
||||||
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
|
let mut header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
|
||||||
if &header[4..12] == MTK_HEADER_MAGIC {
|
if &header[4..12] == MTK_HEADER_MAGIC {
|
||||||
Ok(Some(MtkPkgContext { is_philips_variant: false, decrypted_header: header}))
|
Ok(Some(Box::new(MtkPkgContext { is_philips_variant: false, decrypted_header: header})))
|
||||||
} else {
|
} else {
|
||||||
// try for philips which has additional 128 bytes at beginning
|
// try for philips which has additional 128 bytes at beginning
|
||||||
encrypted_header = common::read_file(&file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?;
|
encrypted_header = common::read_file(&file, PHILIPS_EXTRA_HEADER_SIZE as u64, HEADER_SIZE)?;
|
||||||
header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
|
header = decrypt_aes128_cbc_nopad(&encrypted_header, &HEADER_KEY, &HEADER_IV)?;
|
||||||
if &header[4..12] == MTK_HEADER_MAGIC {
|
if &header[4..12] == MTK_HEADER_MAGIC {
|
||||||
Ok(Some(MtkPkgContext { is_philips_variant: true, decrypted_header: header }))
|
Ok(Some(Box::new(MtkPkgContext { is_philips_variant: true, decrypted_header: header })))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
||||||
@@ -93,16 +40,19 @@ pub fn is_mtk_pkg_file(file: &File) -> Result<Option<MtkPkgContext>, Box<dyn std
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgContext) -> Result<(), Box<dyn std::error::Error>> {
|
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 file_size = file.metadata()?.len();
|
let file_size = file.metadata()?.len();
|
||||||
let header = context.decrypted_header;
|
let header = ctx.decrypted_header;
|
||||||
let mut hdr_reader = Cursor::new(header);
|
let mut hdr_reader = Cursor::new(header);
|
||||||
let hdr: Header = hdr_reader.read_le()?;
|
let hdr: Header = hdr_reader.read_le()?;
|
||||||
|
|
||||||
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
|
println!("File info:\nFile size: {}\nVendor magic: {}\nVersion info: {}\nProduct name: {}" ,
|
||||||
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
|
hdr.file_size, hdr.vendor_magic(), hdr.version(), hdr.product_name());
|
||||||
|
|
||||||
if context.is_philips_variant {
|
if ctx.is_philips_variant {
|
||||||
file.seek(SeekFrom::Start(HEADER_SIZE as u64 + PHILIPS_EXTRA_HEADER_SIZE as u64))?;
|
file.seek(SeekFrom::Start(HEADER_SIZE as u64 + PHILIPS_EXTRA_HEADER_SIZE as u64))?;
|
||||||
} else {
|
} else {
|
||||||
file.seek(SeekFrom::Start(HEADER_SIZE as u64))?;
|
file.seek(SeekFrom::Start(HEADER_SIZE as u64))?;
|
||||||
@@ -189,13 +139,13 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgCont
|
|||||||
};
|
};
|
||||||
|
|
||||||
//for compressed part create temp file
|
//for compressed part create temp file
|
||||||
let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
let output_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
||||||
out_file.write_all(&out_data[CRYPTED_HEADER_SIZE + extra_header_len as usize..])?;
|
out_file.write_all(&out_data[CRYPTED_HEADER_SIZE + extra_header_len as usize..])?;
|
||||||
|
|
||||||
if part_entry.is_compressed() {
|
if part_entry.is_compressed() {
|
||||||
let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin");
|
let lzhs_out_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + ".bin");
|
||||||
match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) {
|
match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("- Decompressed Successfully!");
|
println!("- Decompressed Successfully!");
|
||||||
@@ -212,7 +162,5 @@ pub fn extract_mtk_pkg(mut file: &File, output_folder: &str, context: MtkPkgCont
|
|||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
pub static HEADER_SIZE: usize = 0x170;
|
||||||
@@ -1,14 +1,18 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Cursor, Seek, SeekFrom};
|
use std::io::{Write, Cursor, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
||||||
use crate::utils::lzhs::{decompress_lzhs_fs_file2file};
|
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
|
use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file};
|
||||||
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
|
use crate::formats::mtk_pkg::include::{Header, PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
pub struct MtkPkgNewContext {
|
pub struct MtkPkgNewContext {
|
||||||
matching_key_name: String,
|
matching_key_name: String,
|
||||||
@@ -17,54 +21,9 @@ pub struct MtkPkgNewContext {
|
|||||||
decrypted_header: Vec<u8>,
|
decrypted_header: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_mtk_pkg_new_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
|
|
||||||
#[br(count = 56)] version_bytes: Vec<u8>,
|
|
||||||
_unk: u32,
|
|
||||||
file_size: u32,
|
|
||||||
_flags: u32,
|
|
||||||
#[br(count = 32)] product_name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 256)] _digest: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn vendor_magic(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.vendor_magic_bytes)
|
|
||||||
}
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn product_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.product_name_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PartEntry {
|
|
||||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
|
||||||
flags: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
impl PartEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn is_valid(&self) -> bool {
|
|
||||||
self.name().is_ascii()
|
|
||||||
}
|
|
||||||
fn is_encrypted(&self) -> bool {
|
|
||||||
(self.flags & 1 << 0) != 0
|
|
||||||
}
|
|
||||||
fn is_compressed(&self) -> bool { //lzhs fs
|
|
||||||
(self.flags & 1 << 8) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static HEADER_SIZE: usize = 0x170;
|
|
||||||
|
|
||||||
pub fn is_mtk_pkg_new_file(file: &File) -> Result<Option<MtkPkgNewContext>, Box<dyn std::error::Error>> {
|
|
||||||
let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
|
let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
|
||||||
for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST {
|
for (key_hex, iv_hex, name) in keys::MTK_PKG_CUST {
|
||||||
let key_array: [u8; 16] = hex::decode(key_hex)?.as_slice().try_into()?;
|
let key_array: [u8; 16] = hex::decode(key_hex)?.as_slice().try_into()?;
|
||||||
@@ -72,26 +31,29 @@ pub fn is_mtk_pkg_new_file(file: &File) -> Result<Option<MtkPkgNewContext>, Box<
|
|||||||
let try_decrypt = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array)?;
|
let try_decrypt = decrypt_aes128_cbc_nopad(&encrypted_header, &key_array, &iv_array)?;
|
||||||
|
|
||||||
if &try_decrypt[4..12] == MTK_HEADER_MAGIC {
|
if &try_decrypt[4..12] == MTK_HEADER_MAGIC {
|
||||||
return Ok(Some(MtkPkgNewContext {
|
return Ok(Some(Box::new(MtkPkgNewContext {
|
||||||
matching_key_name: name.to_string(),
|
matching_key_name: name.to_string(),
|
||||||
matching_key_key: key_array,
|
matching_key_key: key_array,
|
||||||
matching_key_iv: iv_array,
|
matching_key_iv: iv_array,
|
||||||
decrypted_header: try_decrypt
|
decrypted_header: try_decrypt
|
||||||
}));
|
})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkgNewContext) -> Result<(), Box<dyn std::error::Error>> {
|
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 file_size = file.metadata()?.len();
|
let file_size = file.metadata()?.len();
|
||||||
|
|
||||||
//the key was founf, and header was decrypted at detection stage so we can reuse
|
//the key was founf, and header was decrypted at detection stage so we can reuse
|
||||||
println!("Using key {}", context.matching_key_name);
|
println!("Using key {}", ctx.matching_key_name);
|
||||||
let key_array = context.matching_key_key;
|
let key_array = ctx.matching_key_key;
|
||||||
let iv_array = context.matching_key_iv;
|
let iv_array = ctx.matching_key_iv;
|
||||||
let header = context.decrypted_header;
|
let header = ctx.decrypted_header;
|
||||||
|
|
||||||
let mut hdr_reader = Cursor::new(header);
|
let mut hdr_reader = Cursor::new(header);
|
||||||
let hdr: Header = hdr_reader.read_le()?;
|
let hdr: Header = hdr_reader.read_le()?;
|
||||||
@@ -147,13 +109,13 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkg
|
|||||||
};
|
};
|
||||||
|
|
||||||
//for compressed part create temp file
|
//for compressed part create temp file
|
||||||
let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
let output_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
||||||
out_file.write_all(&out_data[48 + extra_header_len as usize..])?;
|
out_file.write_all(&out_data[48 + extra_header_len as usize..])?;
|
||||||
|
|
||||||
if part_entry.is_compressed() {
|
if part_entry.is_compressed() {
|
||||||
let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin");
|
let lzhs_out_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + ".bin");
|
||||||
match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) {
|
match decompress_lzhs_fs_file2file(&out_file, lzhs_out_path) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("-- Decompressed Successfully!");
|
println!("-- Decompressed Successfully!");
|
||||||
@@ -170,7 +132,5 @@ pub fn extract_mtk_pkg_new(mut file: &File, output_folder: &str, context: MtkPkg
|
|||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub static KEY: u32 = 0x94102909; //09 29 10 94
|
||||||
|
// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte
|
||||||
|
pub static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04
|
||||||
|
pub static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04
|
||||||
|
|
||||||
|
pub static HEADER_SIZE: usize = 0x98;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
pub vendor_magic_bytes: [u8; 4],
|
||||||
|
_mtk_magic: [u8; 8],
|
||||||
|
version_bytes: [u8; 68],
|
||||||
|
pub file_size: u32,
|
||||||
|
_flags: u32,
|
||||||
|
product_name_bytes: [u8; 32],
|
||||||
|
_digest: [u8; 32],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn vendor_magic(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.vendor_magic_bytes)
|
||||||
|
}
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn product_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.product_name_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,81 +1,42 @@
|
|||||||
|
mod include;
|
||||||
|
mod mtk_crypto;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Cursor, Seek};
|
use std::io::{Write, Cursor, Seek};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::mtk_crypto::{decrypt};
|
use crate::formats::mtk_pkg::lzhs::{decompress_lzhs_fs_file2file_old};
|
||||||
use crate::utils::lzhs::{decompress_lzhs_fs_file2file_old};
|
use crate::formats::mtk_pkg::include::{PartEntry, MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
|
||||||
|
use mtk_crypto::{decrypt};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
static KEY: u32 = 0x94102909; //09 29 10 94
|
pub fn is_mtk_pkg_old_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
// first 4 bytes of header and content are additionally XORed, they have different masks although only differ by half a byte
|
let mut file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
static HEADER_XOR_MASK: u32 = 0x04BE7C75; //75 7C BE 04
|
|
||||||
static CONTENT_XOR_MASK: u32 = 0x04BE7C72; //72 7C BE 04
|
|
||||||
|
|
||||||
use crate::formats::mtk_pkg::{MTK_HEADER_MAGIC, MTK_META_MAGIC, MTK_META_PAD_MAGIC};
|
let encrypted_header = common::read_file(&file, 0, HEADER_SIZE)?;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Header {
|
|
||||||
#[br(count = 4)] vendor_magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _mtk_magic: Vec<u8>, //#DH@FiRm
|
|
||||||
#[br(count = 68)] version_bytes: Vec<u8>,
|
|
||||||
file_size: u32,
|
|
||||||
_flags: u32,
|
|
||||||
#[br(count = 32)] product_name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] _digest: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn vendor_magic(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.vendor_magic_bytes)
|
|
||||||
}
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn product_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.product_name_bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PartEntry {
|
|
||||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
|
||||||
flags: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
impl PartEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn is_encrypted(&self) -> bool {
|
|
||||||
(self.flags & 1 << 0) == 1 << 0
|
|
||||||
}
|
|
||||||
fn is_compressed(&self) -> bool { //lzhs fs
|
|
||||||
(self.flags & 1 << 8) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static HEADER_SIZE: usize = 0x98;
|
|
||||||
|
|
||||||
pub fn is_mtk_pkg_old_file(mut file: &File) -> bool {
|
|
||||||
let encrypted_header = common::read_file(&file, 0, HEADER_SIZE).expect("Failed to read from file.");
|
|
||||||
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
|
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
|
||||||
if &header[4..12] == MTK_HEADER_MAGIC {
|
if &header[4..12] == MTK_HEADER_MAGIC {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else if &header[68..76] == MTK_HEADER_MAGIC {
|
} else if &header[68..76] == MTK_HEADER_MAGIC {
|
||||||
//check for 64 byte additional header used in some Sony and Philips firmwares and skip it
|
//check for 64 byte additional header used in some Sony and Philips firmwares and skip it
|
||||||
file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek");
|
file.seek(std::io::SeekFrom::Start(64))?;
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else if &header[132..140] == MTK_HEADER_MAGIC {
|
} else if &header[132..140] == MTK_HEADER_MAGIC {
|
||||||
//check for 128 byte additional header used in some Philips firmwares and skip it
|
//check for 128 byte additional header used in some Philips firmwares and skip it
|
||||||
file.seek(std::io::SeekFrom::Start(128)).expect("Failed to seek");
|
file.seek(std::io::SeekFrom::Start(128))?;
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
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 file_size = file.metadata()?.len();
|
let file_size = file.metadata()?.len();
|
||||||
let encrypted_header = common::read_exact(&mut file, HEADER_SIZE)?;
|
let encrypted_header = common::read_exact(&mut file, HEADER_SIZE)?;
|
||||||
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
|
let header = decrypt(&encrypted_header, KEY, Some(HEADER_XOR_MASK));
|
||||||
@@ -118,13 +79,13 @@ pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), B
|
|||||||
};
|
};
|
||||||
|
|
||||||
//for compressed part create temp file
|
//for compressed part create temp file
|
||||||
let output_path = Path::new(&output_folder).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
let output_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + if part_entry.is_compressed() {".lzhs"} else {".bin"});
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
let mut out_file = OpenOptions::new().write(true).read(true)/* for lzhs */.create(true).open(&output_path)?;
|
||||||
out_file.write_all(&out_data[extra_header_len as usize..])?;
|
out_file.write_all(&out_data[extra_header_len as usize..])?;
|
||||||
|
|
||||||
if part_entry.is_compressed() {
|
if part_entry.is_compressed() {
|
||||||
let lzhs_out_path = Path::new(&output_folder).join(part_entry.name() + ".bin");
|
let lzhs_out_path = Path::new(&app_ctx.output_dir).join(part_entry.name() + ".bin");
|
||||||
match decompress_lzhs_fs_file2file_old(&out_file, lzhs_out_path) {
|
match decompress_lzhs_fs_file2file_old(&out_file, lzhs_out_path) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
println!("- Decompressed Successfully!");
|
println!("- Decompressed Successfully!");
|
||||||
@@ -141,7 +102,5 @@ pub fn extract_mtk_pkg_old(mut file: &File, output_folder: &str) -> Result<(), B
|
|||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::path::Path;
|
|
||||||
use std::fs::{self, OpenOptions};
|
|
||||||
use std::io::{Write};
|
|
||||||
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Header {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
|
||||||
version_major: u32,
|
|
||||||
version_minor: u32,
|
|
||||||
_unused: u32,
|
|
||||||
#[br(count = 16)] firmware_name_bytes: Vec<u8>,
|
|
||||||
data_size: u32,
|
|
||||||
#[br(count = 16)] _md5_checksum: Vec<u8>, //data checksum
|
|
||||||
part_count: u32,
|
|
||||||
_data_start_offset: u32,
|
|
||||||
#[br(count = 128)] _signature: Vec<u8>,
|
|
||||||
_header_checksum: u32, //CRC32, calculated with the field set to 0
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn firmware_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.firmware_name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct PartEntry {
|
|
||||||
id: u32,
|
|
||||||
size: u32,
|
|
||||||
offset: u32,
|
|
||||||
#[br(count = 16)] _md5_checksum: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_novatek_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"NFWB" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_novatek(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: Header = file.read_le()?;
|
|
||||||
|
|
||||||
println!("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();
|
|
||||||
for _i in 0..header.part_count {
|
|
||||||
let part: PartEntry = file.read_le()?;
|
|
||||||
entries.push(part);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
let data = common::read_file(&file, entry.offset as u64, entry.size as usize)?;
|
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", e_i, entry.id));
|
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
println!("- Saved file!");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4],
|
||||||
|
pub version_major: u32,
|
||||||
|
pub version_minor: u32,
|
||||||
|
_unused: u32,
|
||||||
|
firmware_name_bytes: [u8; 16],
|
||||||
|
pub data_size: u32,
|
||||||
|
_md5_checksum: [u8; 16], //data checksum
|
||||||
|
pub part_count: u32,
|
||||||
|
_data_start_offset: u32,
|
||||||
|
_signature: [u8; 128],
|
||||||
|
_header_checksum: u32, //CRC32, calculated with the field set to 0
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn firmware_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.firmware_name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct PartEntry {
|
||||||
|
pub id: u32,
|
||||||
|
pub size: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
_md5_checksum: [u8; 16],
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::Write;
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
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)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
|
if header == b"NFWB" {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_novatek(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
|
let header: Header = file.read_le()?;
|
||||||
|
println!("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();
|
||||||
|
for _i in 0..header.part_count {
|
||||||
|
let part: PartEntry = file.read_le()?;
|
||||||
|
entries.push(part);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
let data = common::read_file(&file, entry.offset as u64, entry.size as usize)?;
|
||||||
|
|
||||||
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}_{}.bin", e_i, entry.id));
|
||||||
|
|
||||||
|
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(&data)?;
|
||||||
|
|
||||||
|
println!("- Saved file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
use std::str;
|
|
||||||
use std::path::{Path};
|
|
||||||
use std::io::{Seek, Write};
|
|
||||||
use std::fs::{self, File, OpenOptions};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
use crate::utils::compression::{decompress_gzip};
|
|
||||||
use crate::utils::sparse::{unsparse_to_file};
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
struct TIMG {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>, //TIMG
|
|
||||||
_unused1: u32,
|
|
||||||
data_size: u32,
|
|
||||||
_unused2: u32,
|
|
||||||
#[br(count = 16)] _md5_checksum: Vec<u8>,
|
|
||||||
#[br(count = 256)] _signature: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
struct PIMG {
|
|
||||||
#[br(count = 4)] magic_bytes: Vec<u8>, //PIMG
|
|
||||||
_unused1: u32,
|
|
||||||
size: u32,
|
|
||||||
_unused2: u32,
|
|
||||||
#[br(count = 16)] _md5_checksum: Vec<u8>,
|
|
||||||
#[br(count = 16)] name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 64)] dest_dev_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] comp_type_bytes: Vec<u8>,
|
|
||||||
_unknown1: u32,
|
|
||||||
#[br(count = 1024)] _comment: Vec<u8>,
|
|
||||||
_unknown2: u32,
|
|
||||||
}
|
|
||||||
impl PIMG {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn dest_dev(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.dest_dev_bytes)
|
|
||||||
}
|
|
||||||
fn comp_type(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.comp_type_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_nvt_timg_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"TIMG" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_nvt_timg(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let file_size = file.metadata()?.len();
|
|
||||||
let timg: TIMG = file.read_le()?;
|
|
||||||
println!("File info:\nData size: {}", timg.data_size);
|
|
||||||
|
|
||||||
let mut pimg_i = 0;
|
|
||||||
while file.stream_position()? < file_size as u64 {
|
|
||||||
pimg_i += 1;
|
|
||||||
let pimg: PIMG = file.read_le()?;
|
|
||||||
if pimg.magic_bytes != b"PIMG" {
|
|
||||||
println!("Invalid PIMG magic!");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = common::read_exact(&mut file, pimg.size as usize)?;
|
|
||||||
|
|
||||||
println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}", pimg_i, pimg.name(), pimg.size, pimg.dest_dev(), pimg.comp_type());
|
|
||||||
|
|
||||||
let out_data;
|
|
||||||
let output_path = Path::new(&output_folder).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...");
|
|
||||||
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...");
|
|
||||||
unsparse_to_file(&data, output_path)?;
|
|
||||||
println!("-- Saved file!");
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
println!("- Warning: unsupported compression type!");
|
|
||||||
out_data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&out_data)?;
|
|
||||||
|
|
||||||
println!("-- Saved file!");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TIMG {
|
||||||
|
_magic_bytes: [u8; 4], //TIMG
|
||||||
|
_unused1: u32,
|
||||||
|
pub data_size: u32,
|
||||||
|
_unused2: u32,
|
||||||
|
_md5_checksum: [u8; 16],
|
||||||
|
_signature: [u8; 256],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct PIMG {
|
||||||
|
pub magic_bytes: [u8; 4], //PIMG
|
||||||
|
_unused1: u32,
|
||||||
|
pub size: u32,
|
||||||
|
_unused2: u32,
|
||||||
|
_md5_checksum: [u8; 16],
|
||||||
|
name_bytes: [u8; 16],
|
||||||
|
dest_dev_bytes: [u8; 64],
|
||||||
|
comp_type_bytes: [u8; 16],
|
||||||
|
_unknown1: u32,
|
||||||
|
_comment: [u8; 1024],
|
||||||
|
_unknown2: u32,
|
||||||
|
}
|
||||||
|
impl PIMG {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
pub fn dest_dev(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.dest_dev_bytes)
|
||||||
|
}
|
||||||
|
pub fn comp_type(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.comp_type_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
|
use std::io::{Seek, Write};
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use crate::utils::compression::{decompress_gzip};
|
||||||
|
use crate::utils::sparse::{unsparse_to_file};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
pub fn is_nvt_timg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
|
if header == b"TIMG" {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 file_size = file.metadata()?.len();
|
||||||
|
let timg: TIMG = file.read_le()?;
|
||||||
|
println!("File info:\nData size: {}", timg.data_size);
|
||||||
|
|
||||||
|
let mut pimg_i = 0;
|
||||||
|
while file.stream_position()? < file_size as u64 {
|
||||||
|
pimg_i += 1;
|
||||||
|
let pimg: PIMG = file.read_le()?;
|
||||||
|
if &pimg.magic_bytes != b"PIMG" {
|
||||||
|
return Err("Invalid PIMG magic!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = common::read_exact(&mut file, pimg.size as usize)?;
|
||||||
|
|
||||||
|
println!("\n#{} - {}, Size: {}, Dest: {}, Compression: {}", pimg_i, pimg.name(), pimg.size, pimg.dest_dev(), pimg.comp_type());
|
||||||
|
|
||||||
|
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...");
|
||||||
|
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...");
|
||||||
|
unsparse_to_file(&data, output_path)?;
|
||||||
|
println!("-- Saved file!");
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
println!("- Warning: unsupported compression type, saving stored data!");
|
||||||
|
out_data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(&out_data)?;
|
||||||
|
|
||||||
|
println!("-- Saved file!");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
use super::pana_dvd_crypto::{decrypt_data};
|
||||||
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
||||||
|
|
||||||
|
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
|
||||||
|
for key_hex in key_array {
|
||||||
|
let key_bytes = hex::decode(key_hex)?;
|
||||||
|
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
|
||||||
|
let decrypted = decrypt_data(data, &key_array);
|
||||||
|
|
||||||
|
if decrypted[magic_offset..].starts_with(expected_magic) {
|
||||||
|
return Ok(Some(key_array));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<([u8; 16], [u8; 16], [u8; 8])>, Box<dyn std::error::Error>> {
|
||||||
|
for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array {
|
||||||
|
let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?;
|
||||||
|
let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?;
|
||||||
|
let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?;
|
||||||
|
|
||||||
|
let key_bytes = hex::decode(cust_key_hex)?;
|
||||||
|
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
|
||||||
|
let decrypted = decrypt_data(&aes_decrypted, &key_array);
|
||||||
|
|
||||||
|
if decrypted[magic_offset..].starts_with(expected_magic) {
|
||||||
|
return Ok(Some((aes_key, aes_iv, key_array)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static MAX_HEADER_SIZE: usize = 0x2000;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct AesHeaderFileEntry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileEntry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub base_offset: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
//checksums are mostly Adler32, but some very old files use Checksum32 instead.
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct ModuleEntry {
|
||||||
|
#[br(count = 4)] pub name_bytes: Vec<u8>,
|
||||||
|
version_bytes: [u8; 4],
|
||||||
|
_unk: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
platform_bytes: [u8; 8],
|
||||||
|
_unk1: u16,
|
||||||
|
id_bytes: [u8; 6],
|
||||||
|
pub size: u32,
|
||||||
|
pub data_checksum: u32, //checksum of the entrys' DATA
|
||||||
|
_unk2: u32,
|
||||||
|
_entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes)
|
||||||
|
}
|
||||||
|
impl ModuleEntry {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_bytes)
|
||||||
|
}
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn platform(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.platform_bytes)
|
||||||
|
}
|
||||||
|
pub fn id(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.id_bytes)
|
||||||
|
}
|
||||||
|
pub fn is_valid(&self) -> bool {
|
||||||
|
self.name().is_ascii() && self.platform().is_ascii()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct MainListHeader {
|
||||||
|
_checksum: u32, //checksum of the MAIN LIST
|
||||||
|
_unk: u32, //seems to be always 1?
|
||||||
|
pub list_size: u32,
|
||||||
|
pub decompressed_part_size: u32,
|
||||||
|
_unk2: u32,
|
||||||
|
}
|
||||||
|
impl MainListHeader {
|
||||||
|
pub fn entry_count(&self) -> u32 {
|
||||||
|
(&self.list_size - 20) / 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct MainListEntry {
|
||||||
|
pub size: u32,
|
||||||
|
pub checksum: u32, //checksum of this MAIN entrys' data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD";
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct CompressedFileHeader {
|
||||||
|
_header_string: [u8; 14], //EXTRHEADDRV \x01\x00
|
||||||
|
pub compression_type_byte: u16,
|
||||||
|
pub decompressed_size: u32,
|
||||||
|
_destination_address: u32,
|
||||||
|
pub compressed_size: u32,
|
||||||
|
_unk: u32,
|
||||||
|
_footer_offset: u32,
|
||||||
|
_base_address: u32,
|
||||||
|
_checksum: u32, //unknown type of checksum
|
||||||
|
_checksum_flag: u8,
|
||||||
|
_unused: [u8; 19],
|
||||||
|
}
|
||||||
|
impl CompressedFileHeader {
|
||||||
|
pub fn compression_type(&self) -> &str {
|
||||||
|
if self.compression_type_byte == 0 {
|
||||||
|
return "Uncompressed"
|
||||||
|
} else if self.compression_type_byte == 1 {
|
||||||
|
return "GZIP"
|
||||||
|
} else if self.compression_type_byte == 2 {
|
||||||
|
return "LZSS"
|
||||||
|
} else {
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,15 +1,21 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
|
mod pana_dvd_crypto;
|
||||||
|
mod lzss;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Read, Cursor, Seek, SeekFrom};
|
use std::io::{Write, Read, Cursor, Seek, SeekFrom};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::pana_dvd_crypto::{decrypt_data};
|
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad};
|
||||||
use crate::utils::compression::{decompress_gzip};
|
use crate::utils::compression::{decompress_gzip};
|
||||||
use crate::utils::lzss::{decompress_lzss};
|
use pana_dvd_crypto::{decrypt_data};
|
||||||
|
use lzss::{decompress_lzss};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
pub struct PanaDvdContext {
|
pub struct PanaDvdContext {
|
||||||
matching_key: [u8; 8],
|
matching_key: [u8; 8],
|
||||||
@@ -19,165 +25,42 @@ pub struct PanaDvdContext {
|
|||||||
aes_iv: Option<[u8; 16]>
|
aes_iv: Option<[u8; 16]>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_pana_dvd_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct AesHeaderFileEntry {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FileEntry {
|
|
||||||
offset: u32,
|
|
||||||
base_offset: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
//checksums are mostly Adler32, but some very old files use Checksum32 instead.
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct ModuleEntry {
|
|
||||||
#[br(count = 4)] name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] version_bytes: Vec<u8>,
|
|
||||||
_unk: u32,
|
|
||||||
offset: u32,
|
|
||||||
#[br(count = 8)] platform_bytes: Vec<u8>,
|
|
||||||
_unk1: u16,
|
|
||||||
#[br(count = 6)] id_bytes: Vec<u8>,
|
|
||||||
size: u32,
|
|
||||||
data_checksum: u32, //checksum of the entrys' DATA
|
|
||||||
_unk2: u32,
|
|
||||||
_entry_checksum: u32, //checksum of THIS header entry (all previous 44 bytes)
|
|
||||||
}
|
|
||||||
impl ModuleEntry {
|
|
||||||
fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_bytes)
|
|
||||||
}
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn platform(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.platform_bytes)
|
|
||||||
}
|
|
||||||
fn id(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.id_bytes)
|
|
||||||
}
|
|
||||||
fn is_valid(&self) -> bool {
|
|
||||||
self.name().is_ascii() && self.platform().is_ascii()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct MainListHeader {
|
|
||||||
_checksum: u32, //checksum of the MAIN LIST
|
|
||||||
_unk: u32, //seems to be always 1?
|
|
||||||
list_size: u32,
|
|
||||||
decompressed_part_size: u32,
|
|
||||||
_unk2: u32,
|
|
||||||
}
|
|
||||||
impl MainListHeader {
|
|
||||||
fn entry_count(&self) -> u32 {
|
|
||||||
(&self.list_size - 20) / 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct MainListEntry {
|
|
||||||
size: u32,
|
|
||||||
checksum: u32, //checksum of this MAIN entrys' data
|
|
||||||
}
|
|
||||||
|
|
||||||
const COMPRESSED_FILE_MAGIC: &[u8; 8] = b"EXTRHEAD";
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct CompressedFileHeader {
|
|
||||||
#[br(count = 14)] _header_string: Vec<u8>, //EXTRHEADDRV \x01\x00
|
|
||||||
compression_type_byte: u16,
|
|
||||||
decompressed_size: u32,
|
|
||||||
_destination_address: u32,
|
|
||||||
compressed_size: u32,
|
|
||||||
_unk: u32,
|
|
||||||
_footer_offset: u32,
|
|
||||||
_base_address: u32,
|
|
||||||
_checksum: u32, //unknown type of checksum
|
|
||||||
_checksum_flag: u8,
|
|
||||||
#[br(count = 19)] _unused: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl CompressedFileHeader {
|
|
||||||
fn compression_type(&self) -> &str {
|
|
||||||
if self.compression_type_byte == 0 {
|
|
||||||
return "Uncompressed"
|
|
||||||
} else if self.compression_type_byte == 1 {
|
|
||||||
return "GZIP"
|
|
||||||
} else if self.compression_type_byte == 2 {
|
|
||||||
return "LZSS"
|
|
||||||
} else {
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static MAX_HEADER_SIZE: usize = 0x2000;
|
|
||||||
|
|
||||||
pub fn find_key<'a>(key_array: &'a [&'a str], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<[u8; 8]>, Box<dyn std::error::Error>> {
|
|
||||||
for key_hex in key_array {
|
|
||||||
let key_bytes = hex::decode(key_hex)?;
|
|
||||||
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
|
|
||||||
let decrypted = decrypt_data(data, &key_array);
|
|
||||||
|
|
||||||
if decrypted[magic_offset..].starts_with(expected_magic) {
|
|
||||||
return Ok(Some(key_array));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_aes_key_pair<'a>(key_array: &'a [(&'a str, &'a str, &'a str)], data: &[u8], expected_magic: &[u8], magic_offset: usize) -> Result<Option<([u8; 16], [u8; 16], [u8; 8])>, Box<dyn std::error::Error>> {
|
|
||||||
for (aes_key_hex, aes_iv_hex, cust_key_hex) in key_array {
|
|
||||||
let aes_key: [u8; 16] = hex::decode(aes_key_hex)?.as_slice().try_into()?;
|
|
||||||
let aes_iv: [u8; 16] = hex::decode(aes_iv_hex)?.as_slice().try_into()?;
|
|
||||||
let aes_decrypted = decrypt_aes128_cbc_nopad(data, &aes_key, &aes_iv)?;
|
|
||||||
|
|
||||||
let key_bytes = hex::decode(cust_key_hex)?;
|
|
||||||
let key_array: [u8; 8] = key_bytes.as_slice().try_into()?;
|
|
||||||
let decrypted = decrypt_data(&aes_decrypted, &key_array);
|
|
||||||
|
|
||||||
if decrypted[magic_offset..].starts_with(expected_magic) {
|
|
||||||
return Ok(Some((aes_key, aes_iv, key_array)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pana_dvd_file(file: &File) -> Result<Option<PanaDvdContext>, Box<dyn std::error::Error>> {
|
|
||||||
let header = common::read_file(&file, 0, 64)?;
|
let header = common::read_file(&file, 0, 64)?;
|
||||||
if let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0)? {
|
if let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 0)? {
|
||||||
Ok(Some(PanaDvdContext {
|
Ok(Some(Box::new(PanaDvdContext {
|
||||||
matching_key: matching_key,
|
matching_key: matching_key,
|
||||||
base_hdr_size: 0,
|
base_hdr_size: 0,
|
||||||
is_aes: false,
|
is_aes: false,
|
||||||
aes_key: None,
|
aes_key: None,
|
||||||
aes_iv: None,
|
aes_iv: None,
|
||||||
}))
|
})))
|
||||||
} else if header.starts_with(b"PANASONIC\x00\x00\x00") && let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 48)? {
|
} else if header.starts_with(b"PANASONIC\x00\x00\x00") && let Some(matching_key) = find_key(&keys::PANA_DVD_KEYONLY, &header, b"PROG", 48)? {
|
||||||
Ok(Some(PanaDvdContext {
|
Ok(Some(Box::new(PanaDvdContext {
|
||||||
matching_key: matching_key,
|
matching_key: matching_key,
|
||||||
base_hdr_size: 48,
|
base_hdr_size: 48,
|
||||||
is_aes: false,
|
is_aes: false,
|
||||||
aes_key: None,
|
aes_key: None,
|
||||||
aes_iv: None,
|
aes_iv: None,
|
||||||
}))
|
})))
|
||||||
} else if let Some((aes_key, aes_iv, matching_key)) = find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32)? {
|
} else if let Some((aes_key, aes_iv, matching_key)) = find_aes_key_pair(&keys::PANA_DVD_AESPAIR, &header, b"PANASONIC", 32)? {
|
||||||
Ok(Some(PanaDvdContext {
|
Ok(Some(Box::new(PanaDvdContext {
|
||||||
matching_key: matching_key,
|
matching_key: matching_key,
|
||||||
base_hdr_size: 48,
|
base_hdr_size: 48,
|
||||||
is_aes: true,
|
is_aes: true,
|
||||||
aes_key: Some(aes_key),
|
aes_key: Some(aes_key),
|
||||||
aes_iv: Some(aes_iv),
|
aes_iv: Some(aes_iv),
|
||||||
}))
|
})))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_pana_dvd(mut file: &File, output_folder: &str, context: PanaDvdContext) -> Result<(), Box<dyn std::error::Error>> {
|
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 mut data = Vec::new(); // we need to load the entire file into memory so we can swap it with AES decrypted data if its AES encrypted
|
let mut data = Vec::new(); // we need to load the entire file into memory so we can swap it with AES decrypted data if its AES encrypted
|
||||||
file.read_to_end(&mut data)?;
|
file.read_to_end(&mut data)?;
|
||||||
let mut file_reader = Cursor::new(data);
|
let mut file_reader = Cursor::new(data);
|
||||||
@@ -213,21 +96,19 @@ pub fn extract_pana_dvd(mut file: &File, output_folder: &str, context: PanaDvdCo
|
|||||||
if file_entries.len() == 1 {
|
if file_entries.len() == 1 {
|
||||||
//only one file, standard extraction
|
//only one file, standard extraction
|
||||||
println!("File contains no extra sub-files...\n");
|
println!("File contains no extra sub-files...\n");
|
||||||
extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, output_folder)?;
|
extract_file(&mut file_reader, file_entries[0].offset as u64, file_entries[0].base_offset as u64, matching_key, &app_ctx.output_dir)?;
|
||||||
} else {
|
} else {
|
||||||
println!("File contains {} sub-files...", file_entries.len());
|
println!("File contains {} sub-files...", file_entries.len());
|
||||||
for (i, file_entry ) in file_entries.iter().enumerate() {
|
for (i, file_entry ) in file_entries.iter().enumerate() {
|
||||||
println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset);
|
println!("\nExtracting file {}/{} - Offset: {}, base: {}", i + 1, file_entries.len(), file_entry.offset, file_entry.base_offset);
|
||||||
extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &format!("{}/file_{}", output_folder, i + 1))?;
|
extract_file(&mut file_reader, file_entry.offset as u64, file_entry.base_offset as u64, matching_key, &app_ctx.output_dir.join(format!("file_{}", i + 1)))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
fn extract_file(file_reader: &mut Cursor<Vec<u8>>, offset: u64, base_offset: u64, key: [u8; 8], output_folder: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
file_reader.seek(SeekFrom::Start(offset + base_offset))?;
|
file_reader.seek(SeekFrom::Start(offset + base_offset))?;
|
||||||
|
|
||||||
let enc_header = common::read_exact(file_reader, MAX_HEADER_SIZE)?;
|
let enc_header = common::read_exact(file_reader, MAX_HEADER_SIZE)?;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
use aes::Aes256;
|
||||||
|
use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}};
|
||||||
|
|
||||||
|
pub static AUTO_FWS: &[(&str, &str)] = &[
|
||||||
|
("Q5551", "q5551"),
|
||||||
|
("Q5553", "q5551"),
|
||||||
|
("Q554E", "q5551"),
|
||||||
|
("Q554M", "q5551"),
|
||||||
|
("QF1EU", "qf1eu"),
|
||||||
|
("QF2EU", "qf1eu"),
|
||||||
|
("Q591E", "q591e"),
|
||||||
|
("Q522E", "q522e"),
|
||||||
|
("Q582E", "q522e"),
|
||||||
|
("Q5481", "q5481"),
|
||||||
|
("Q5431", "q5431"),
|
||||||
|
("Q5492", "q5492"),
|
||||||
|
("S5551", "q5551"), //Sharp
|
||||||
|
];
|
||||||
|
|
||||||
|
type Aes256EcbDec = Decryptor<Aes256>;
|
||||||
|
|
||||||
|
pub fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
let key_array: [u8; 32] = key.try_into()?;
|
||||||
|
|
||||||
|
let mut decryptor = Aes256EcbDec::new(&key_array.into());
|
||||||
|
let mut buffer = ciphertext.to_vec();
|
||||||
|
|
||||||
|
for chunk in buffer.chunks_exact_mut(16) {
|
||||||
|
let block: &mut [u8; 16] = chunk.try_into()?;
|
||||||
|
decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 8],
|
||||||
|
pub header_size: u32,
|
||||||
|
pub data_size: u32,
|
||||||
|
_crc32: u32,
|
||||||
|
pub mask: u32,
|
||||||
|
_data_size_decompressed: u32,
|
||||||
|
_padding2: u32,
|
||||||
|
description_bytes: [u8; 512],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn description(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.description_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
file_name_bytes: [u8; 60],
|
||||||
|
pub real_size: u32,
|
||||||
|
pub stored_size: u32,
|
||||||
|
pub header_size: u32,
|
||||||
|
pub attributes: [u8; 4],
|
||||||
|
}
|
||||||
|
impl FileHeader {
|
||||||
|
pub fn file_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.file_name_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,90 +1,32 @@
|
|||||||
use std::fs::{File};
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use rsa::{RsaPublicKey, BigUint};
|
use rsa::{RsaPublicKey, BigUint};
|
||||||
use hex::decode;
|
use hex::decode;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::io::{Read, Cursor, Write};
|
use std::io::{Read, Cursor, Write};
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
use aes::Aes256;
|
|
||||||
use ecb::{Decryptor, cipher::{BlockDecryptMut, KeyInit, generic_array::GenericArray}};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_pfl_upg_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 8)] _magic_bytes: Vec<u8>,
|
|
||||||
header_size: u32,
|
|
||||||
data_size: u32,
|
|
||||||
#[br(count = 4)] _crc32: Vec<u8>,
|
|
||||||
mask: u32,
|
|
||||||
_data_size_decompressed: u32,
|
|
||||||
_padding2: u32,
|
|
||||||
#[br(count = 512)] description_bytes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn description(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.description_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 0, 8)?;
|
||||||
struct FileHeader {
|
|
||||||
#[br(count = 60)] file_name_bytes: Vec<u8>,
|
|
||||||
real_size: u32,
|
|
||||||
stored_size: u32,
|
|
||||||
header_size: u32,
|
|
||||||
#[br(count = 4)] attributes: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl FileHeader {
|
|
||||||
fn file_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.file_name_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pfl_upg_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 8).expect("Failed to read from file.");
|
|
||||||
if header == b"2SWU3TXV" {
|
if header == b"2SWU3TXV" {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static AUTO_FWS: &[(&str, &str)] = &[
|
pub fn extract_pfl_upg(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
("Q5551", "q5551"),
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
("Q5553", "q5551"),
|
|
||||||
("Q554E", "q5551"),
|
|
||||||
("Q554M", "q5551"),
|
|
||||||
("QF1EU", "qf1eu"),
|
|
||||||
("QF2EU", "qf1eu"),
|
|
||||||
("Q591E", "q591e"),
|
|
||||||
("Q522E", "q522e"),
|
|
||||||
("Q582E", "q522e"),
|
|
||||||
("Q5481", "q5481"),
|
|
||||||
("Q5431", "q5431"),
|
|
||||||
("Q5492", "q5492"),
|
|
||||||
("S5551", "q5551"), //Sharp
|
|
||||||
];
|
|
||||||
|
|
||||||
type Aes256EcbDec = Decryptor<Aes256>;
|
|
||||||
|
|
||||||
fn decrypt_aes256_ecb(key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
|
||||||
let key_array: [u8; 32] = key.try_into()?;
|
|
||||||
|
|
||||||
let mut decryptor = Aes256EcbDec::new(&key_array.into());
|
|
||||||
let mut buffer = ciphertext.to_vec();
|
|
||||||
|
|
||||||
for chunk in buffer.chunks_exact_mut(16) {
|
|
||||||
let block: &mut [u8; 16] = chunk.try_into()?;
|
|
||||||
decryptor.decrypt_block_mut(GenericArray::from_mut_slice(block));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(buffer)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: Header = file.read_le()?;
|
let header: Header = file.read_le()?;
|
||||||
let signature = common::read_exact(&mut file, 128)?;
|
let signature = common::read_exact(&mut file, 128)?;
|
||||||
let _ = common::read_exact(&mut file, 32)?; //unknown
|
let _ = common::read_exact(&mut file, 32)?; //unknown
|
||||||
@@ -110,8 +52,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if key.is_none() {
|
if key.is_none() {
|
||||||
println!("Sorry, this firmware is not supported!");
|
return Err("This firmware is not supported!".into());
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//get key
|
//get key
|
||||||
@@ -157,7 +98,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
//its a folder not a file
|
//its a folder not a file
|
||||||
if (file_header.attributes[3] & (1 << 1)) != 0 {
|
if (file_header.attributes[3] & (1 << 1)) != 0 {
|
||||||
println!("\nFolder - {}", file_header.file_name());
|
println!("\nFolder - {}", file_header.file_name());
|
||||||
let output_path = Path::new(&output_folder).join(file_header.file_name().trim_start_matches('/'));
|
let output_path = Path::new(&app_ctx.output_dir).join(file_header.file_name().trim_start_matches('/'));
|
||||||
fs::create_dir_all(output_path)?;
|
fs::create_dir_all(output_path)?;
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -175,7 +116,7 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
println!("\nFile - {}, Size: {}", file_name, file_header.real_size);
|
println!("\nFile - {}, Size: {}", file_name, file_header.real_size);
|
||||||
let data = common::read_exact(&mut data_reader, file_header.stored_size as usize)?;
|
let data = common::read_exact(&mut data_reader, file_header.stored_size as usize)?;
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(file_name.trim_start_matches('/'));
|
let output_path = Path::new(&app_ctx.output_dir).join(file_name.trim_start_matches('/'));
|
||||||
let output_path_parent = output_path.parent().expect("Failed to get parent of the output path!");
|
let output_path_parent = output_path.parent().expect("Failed to get parent of the output path!");
|
||||||
|
|
||||||
//prevent collisions
|
//prevent collisions
|
||||||
@@ -188,11 +129,8 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
fs::create_dir_all(parent)?;
|
fs::create_dir_all(parent)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new()
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data[..file_header.real_size as usize])?;
|
out_file.write_all(&data[..file_header.real_size as usize])?;
|
||||||
|
|
||||||
@@ -204,7 +142,5 @@ pub fn extract_pfl_upg(mut file: &File, output_folder: &str) -> Result<(), Box<d
|
|||||||
println!("- Saved file!");
|
println!("- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4],
|
||||||
|
_unk1: u32,
|
||||||
|
_unk2: u16,
|
||||||
|
_flags: u8,
|
||||||
|
_unk3: u8,
|
||||||
|
_header_size: u16,
|
||||||
|
_hash_size: u16,
|
||||||
|
pub file_size: u64,
|
||||||
|
pub entry_count: u16,
|
||||||
|
_hash_count: u16,
|
||||||
|
_unk4: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead, Clone)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub flags: u32,
|
||||||
|
_unk1: u32,
|
||||||
|
pub offset: u64,
|
||||||
|
pub compressed_size: u64,
|
||||||
|
pub uncompressed_size: u64,
|
||||||
|
}
|
||||||
|
impl Entry {
|
||||||
|
pub fn id(&self) -> u32 {
|
||||||
|
self.flags >> 20
|
||||||
|
}
|
||||||
|
pub fn is_compressed(&self) -> bool {
|
||||||
|
(self.flags & 8) != 0
|
||||||
|
}
|
||||||
|
pub fn is_blocked(&self) -> bool {
|
||||||
|
(self.flags & 0x800) != 0
|
||||||
|
}
|
||||||
|
pub fn is_block_table(&self) -> bool {
|
||||||
|
(self.flags & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead, Clone)]
|
||||||
|
pub struct BlockEntry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
@@ -1,67 +1,31 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::{Path};
|
use std::path::{Path};
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::compression::{decompress_zlib};
|
use crate::utils::compression::{decompress_zlib};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_pup_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct Header {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
|
||||||
_unk1: u32,
|
|
||||||
_unk2: u16,
|
|
||||||
_flags: u8,
|
|
||||||
_unk3: u8,
|
|
||||||
_header_size: u16,
|
|
||||||
_hash_size: u16,
|
|
||||||
file_size: u64,
|
|
||||||
entry_count: u16,
|
|
||||||
_hash_count: u16,
|
|
||||||
_unk4: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead, Clone)]
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
struct Entry {
|
|
||||||
flags: u32,
|
|
||||||
_unk1: u32,
|
|
||||||
offset: u64,
|
|
||||||
compressed_size: u64,
|
|
||||||
uncompressed_size: u64,
|
|
||||||
}
|
|
||||||
impl Entry {
|
|
||||||
fn id(&self) -> u32 {
|
|
||||||
self.flags >> 20
|
|
||||||
}
|
|
||||||
fn is_compressed(&self) -> bool {
|
|
||||||
(self.flags & 8) != 0
|
|
||||||
}
|
|
||||||
fn is_blocked(&self) -> bool {
|
|
||||||
(self.flags & 0x800) != 0
|
|
||||||
}
|
|
||||||
fn is_block_table(&self) -> bool {
|
|
||||||
(self.flags & 1) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead, Clone)]
|
|
||||||
struct BlockEntry {
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pup_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"\x4F\x15\x3D\x1D" || header == b"\x54\x14\xF5\xEE" { //ps4, ps5
|
if header == b"\x4F\x15\x3D\x1D" || header == b"\x54\x14\xF5\xEE" { //ps4, ps5
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_pup(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let header: Header = file.read_le()?;
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
|
let header: Header = file.read_le()?;
|
||||||
println!("File info:\nFile size: {}\nEntry count: {}",
|
println!("File info:\nFile size: {}\nEntry count: {}",
|
||||||
header.file_size, header.entry_count);
|
header.file_size, header.entry_count);
|
||||||
|
|
||||||
@@ -132,14 +96,10 @@ pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box<dyn s
|
|||||||
out_data = data;
|
out_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.id()));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", entry.id()));
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.append(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
|
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)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
println!("-- Saved!");
|
println!("-- Saved!");
|
||||||
@@ -158,14 +118,10 @@ pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box<dyn s
|
|||||||
out_data = data;
|
out_data = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", entry.id()));
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", entry.id()));
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_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(&out_data)?;
|
out_file.write_all(&out_data)?;
|
||||||
|
|
||||||
println!("-- Saved file!");
|
println!("-- Saved file!");
|
||||||
@@ -177,6 +133,5 @@ pub fn extract_pup(mut file: &File, output_folder: &str) -> Result<(), Box<dyn s
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub static FILE_KEY: [u8; 16] = [
|
||||||
|
0x2A, 0x54, 0xA5, 0x30, 0xE0, 0x09, 0xA3, 0xDC,
|
||||||
|
0x03, 0xFB, 0xC3, 0x5E, 0x23, 0xA2, 0xC1, 0x0D,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static FILE_IV: [u8; 16] = [0x00; 16];
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct ImageHeader {
|
||||||
|
_empty: [u8; 8],
|
||||||
|
_magic_bytes: [u8; 8], //imgARMcC
|
||||||
|
_target_bytes: [u8; 4],
|
||||||
|
_platform_id: [u8; 4],
|
||||||
|
pub image_type: u32,
|
||||||
|
pub size1: u32,
|
||||||
|
_size2: u32,
|
||||||
|
pub data_start_offset: u32,
|
||||||
|
_data_link_address: u32,
|
||||||
|
_data_entry_point_offset: u32,
|
||||||
|
pub flags: u32,
|
||||||
|
_timestamp: u32,
|
||||||
|
_build_host: [u8; 4],
|
||||||
|
_unk: [u8; 4],
|
||||||
|
_rest_of_header: [u8; 192],
|
||||||
|
}
|
||||||
|
impl ImageHeader {
|
||||||
|
pub fn is_encrypted(&self) -> bool {
|
||||||
|
self.flags == 0x80
|
||||||
|
}
|
||||||
|
pub fn type_string(&self) -> &str {
|
||||||
|
if self.image_type == 0xa {
|
||||||
|
return "initfs"
|
||||||
|
} else if self.image_type == 0x18 {
|
||||||
|
return "uImage"
|
||||||
|
} else if self.image_type == 0x3 {
|
||||||
|
return "loader"
|
||||||
|
} else if self.image_type == 0xd {
|
||||||
|
return "app_cramfs"
|
||||||
|
} else if self.image_type == 0x6 {
|
||||||
|
return "Customization Package"
|
||||||
|
} else if self.image_type == 0xe {
|
||||||
|
return "firmware_blob"
|
||||||
|
} else {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +1,33 @@
|
|||||||
use std::fs::{self, File, OpenOptions};
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::io::{Write, Seek, Read, Cursor};
|
use std::io::{Write, Seek, Read, Cursor};
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
|
use crate::utils::aes::{decrypt_aes128_cbc_nopad, decrypt_aes128_cbc_pcks7};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
static FILE_KEY: [u8; 16] = [
|
pub fn is_roku_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
0x2A, 0x54, 0xA5, 0x30, 0xE0, 0x09, 0xA3, 0xDC,
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
0x03, 0xFB, 0xC3, 0x5E, 0x23, 0xA2, 0xC1, 0x0D,
|
|
||||||
];
|
|
||||||
|
|
||||||
static FILE_IV: [u8; 16] = [0x00; 16];
|
let header = common::read_file(&file, 0, 32)?;
|
||||||
|
let try_decrypt_header = decrypt_aes128_cbc_nopad(&header, &FILE_KEY, &FILE_IV)?;
|
||||||
#[derive(BinRead)]
|
|
||||||
struct ImageHeader {
|
|
||||||
#[br(count = 8)] _empty: Vec<u8>,
|
|
||||||
#[br(count = 8)] _magic_bytes: Vec<u8>, //imgARMcC
|
|
||||||
#[br(count = 4)] _target_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] _platform_id: Vec<u8>,
|
|
||||||
image_type: u32,
|
|
||||||
size1: u32,
|
|
||||||
_size2: u32,
|
|
||||||
data_start_offset: u32,
|
|
||||||
_data_link_address: u32,
|
|
||||||
_data_entry_point_offset: u32,
|
|
||||||
flags: u32,
|
|
||||||
_timestamp: u32,
|
|
||||||
#[br(count = 4)] _build_host: Vec<u8>,
|
|
||||||
#[br(count = 4)] _unk: Vec<u8>,
|
|
||||||
#[br(count = 192)] _rest_of_header: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl ImageHeader {
|
|
||||||
fn is_encrypted(&self) -> bool {
|
|
||||||
self.flags == 0x80
|
|
||||||
}
|
|
||||||
fn type_string(&self) -> &str {
|
|
||||||
if self.image_type == 0xa {
|
|
||||||
return "initfs"
|
|
||||||
} else if self.image_type == 0x18 {
|
|
||||||
return "uImage"
|
|
||||||
} else if self.image_type == 0x3 {
|
|
||||||
return "loader"
|
|
||||||
} else if self.image_type == 0xd {
|
|
||||||
return "app_cramfs"
|
|
||||||
} else if self.image_type == 0x6 {
|
|
||||||
return "Customization Package"
|
|
||||||
} else if self.image_type == 0xe {
|
|
||||||
return "firmware_blob"
|
|
||||||
} else {
|
|
||||||
return "unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_roku_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 32).expect("Failed to read from file.");
|
|
||||||
let try_decrypt_header = decrypt_aes128_cbc_nopad(&header, &FILE_KEY, &FILE_IV).expect("Decryption error!");
|
|
||||||
|
|
||||||
if try_decrypt_header.starts_with(b"manifest\x00\x00\x00\x00\x00\x00\x00\x00") {
|
if try_decrypt_header.starts_with(b"manifest\x00\x00\x00\x00\x00\x00\x00\x00") {
|
||||||
true
|
Ok(Some(Box::new(())))
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn extract_roku(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
let mut encrypted_data = Vec::new();
|
let mut encrypted_data = Vec::new();
|
||||||
file.read_to_end(&mut encrypted_data)?;
|
file.read_to_end(&mut encrypted_data)?;
|
||||||
|
|
||||||
@@ -109,7 +70,7 @@ pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
common::read_exact(&mut image_reader, image.size1 as usize - image.data_start_offset as usize)?
|
common::read_exact(&mut image_reader, image.size1 as usize - image.data_start_offset as usize)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let folder_path = Path::new(&output_folder).join(&path);
|
let folder_path = Path::new(&app_ctx.output_dir).join(&path);
|
||||||
let output_path = Path::new(&folder_path).join(format!("{}_{}.bin", i, image.type_string()));
|
let output_path = Path::new(&folder_path).join(format!("{}_{}.bin", i, image.type_string()));
|
||||||
|
|
||||||
fs::create_dir_all(&folder_path)?;
|
fs::create_dir_all(&folder_path)?;
|
||||||
@@ -123,9 +84,9 @@ pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
println!("\nOther/Unknown file: {:?}", path);
|
println!("\nOther/Unknown file: {:?}", path);
|
||||||
let output_path = Path::new(&output_folder).join(&path);
|
let output_path = Path::new(&app_ctx.output_dir).join(&path);
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&contents)?;
|
out_file.write_all(&contents)?;
|
||||||
|
|
||||||
@@ -134,6 +95,5 @@ pub fn extract_roku(mut file: &File, output_folder: &str) -> Result<(), Box<dyn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct RufHeader {
|
||||||
|
_magic_bytes: [u8; 6],
|
||||||
|
_upgrade_type_bytes: [u8; 2],
|
||||||
|
_unk1: u32,
|
||||||
|
date_time_bytes: [u8; 32],
|
||||||
|
buyer_bytes: [u8; 8],
|
||||||
|
model_bytes: [u8; 32],
|
||||||
|
region_info_bytes: [u8; 32],
|
||||||
|
pub version_bytes: [u8; 4],
|
||||||
|
pub data_size: u32,
|
||||||
|
_unk2: [u8; 20],
|
||||||
|
pub dual_ruf_flag: u32,
|
||||||
|
_unk3: [u8; 44],
|
||||||
|
pub payload_count: u16,
|
||||||
|
_payload_entry_size: u16,
|
||||||
|
pub payloads_start_offset: u32,
|
||||||
|
}
|
||||||
|
impl RufHeader {
|
||||||
|
pub fn date_time(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.date_time_bytes)
|
||||||
|
}
|
||||||
|
pub fn buyer(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.buyer_bytes)
|
||||||
|
}
|
||||||
|
pub fn model(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.model_bytes)
|
||||||
|
}
|
||||||
|
pub fn region_info(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.region_info_bytes)
|
||||||
|
}
|
||||||
|
pub fn is_dual_ruf(&self) -> bool {
|
||||||
|
if self.dual_ruf_flag == 0x44 {true} else {false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct RufEntry {
|
||||||
|
_metadata: [u8; 32],
|
||||||
|
pub payload_type_bytes: u32,
|
||||||
|
pub size: u32,
|
||||||
|
_unk1: u32,
|
||||||
|
_unk2: [u8; 20],
|
||||||
|
}
|
||||||
|
impl RufEntry {
|
||||||
|
pub fn payload_type(&self) -> &str {
|
||||||
|
if self.payload_type_bytes == 1 {
|
||||||
|
return "squashfs"
|
||||||
|
} else if self.payload_type_bytes == 2 {
|
||||||
|
return "cfe"
|
||||||
|
} else if self.payload_type_bytes == 3 {
|
||||||
|
return "vmlinuz"
|
||||||
|
} else if self.payload_type_bytes == 4 {
|
||||||
|
return "loader"
|
||||||
|
} else if self.payload_type_bytes == 5 {
|
||||||
|
return "splash"
|
||||||
|
} else {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,99 +1,45 @@
|
|||||||
use std::path::{Path};
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::{self, File, OpenOptions};
|
use std::fs::{self, File, OpenOptions};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
use binrw::BinReaderExt;
|
||||||
use std::io::{Write, Seek, SeekFrom, Cursor};
|
use std::io::{Write, Seek, SeekFrom, Cursor};
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
#[derive(BinRead)]
|
pub fn is_ruf_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
struct RufHeader {
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
#[br(count = 6)] _magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 2)] _upgrade_type_bytes: Vec<u8>,
|
|
||||||
_unk1: u32,
|
|
||||||
#[br(count = 32)] date_time_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] buyer_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] model_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] region_info_bytes: Vec<u8>,
|
|
||||||
#[br(count = 4)] version_bytes: Vec<u8>,
|
|
||||||
data_size: u32,
|
|
||||||
#[br(count = 20)] _unk2: Vec<u8>,
|
|
||||||
dual_ruf_flag: u32,
|
|
||||||
#[br(count = 44)] _unk3: Vec<u8>,
|
|
||||||
payload_count: u16,
|
|
||||||
_payload_entry_size: u16,
|
|
||||||
payloads_start_offset: u32,
|
|
||||||
}
|
|
||||||
impl RufHeader {
|
|
||||||
fn date_time(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.date_time_bytes)
|
|
||||||
}
|
|
||||||
fn buyer(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.buyer_bytes)
|
|
||||||
}
|
|
||||||
fn model(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.model_bytes)
|
|
||||||
}
|
|
||||||
fn region_info(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.region_info_bytes)
|
|
||||||
}
|
|
||||||
fn is_dual_ruf(&self) -> bool {
|
|
||||||
if self.dual_ruf_flag == 0x44 {true} else {false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
let header = common::read_file(&file, 0, 6)?;
|
||||||
struct RufEntry {
|
if header == b"RUF\x00\x00\x00" {
|
||||||
#[br(count = 32)] _metadata: Vec<u8>,
|
Ok(Some(Box::new(())))
|
||||||
payload_type_bytes: u32,
|
|
||||||
size: u32,
|
|
||||||
_unk1: u32,
|
|
||||||
#[br(count = 20)] _unk2: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl RufEntry {
|
|
||||||
fn payload_type(&self) -> &str {
|
|
||||||
if self.payload_type_bytes == 1 {
|
|
||||||
return "squashfs"
|
|
||||||
} else if self.payload_type_bytes == 2 {
|
|
||||||
return "cfe"
|
|
||||||
} else if self.payload_type_bytes == 3 {
|
|
||||||
return "vmlinuz"
|
|
||||||
} else if self.payload_type_bytes == 4 {
|
|
||||||
return "loader"
|
|
||||||
} else if self.payload_type_bytes == 5 {
|
|
||||||
return "splash"
|
|
||||||
} else {
|
} else {
|
||||||
return "unknown"
|
Ok(None)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_ruf_file(file: &File) -> bool {
|
pub fn extract_ruf(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let header = common::read_file(&file, 0, 3).expect("Failed to read from file.");
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
if header == b"RUF" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_ruf(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: RufHeader = file.read_be()?;
|
let header: RufHeader = file.read_be()?;
|
||||||
if header.is_dual_ruf() {
|
if header.is_dual_ruf() {
|
||||||
println!("\nDual RUF detected! Extracting 1st RUF...\n");
|
println!("\nDual RUF detected! Extracting 1st RUF...\n");
|
||||||
actually_extract_ruf(&file, &format!("{}/RUF_1", output_folder), 0)?;
|
actually_extract_ruf(file, &app_ctx.output_dir.join("RUF_1"), 0)?;
|
||||||
println!("\nExtracting 2nd RUF...\n");
|
println!("\nExtracting 2nd RUF...\n");
|
||||||
actually_extract_ruf(&file, &format!("{}/RUF_2", output_folder), 41943088)?;
|
actually_extract_ruf(file, &app_ctx.output_dir.join("RUF_2"), 41943088)?;
|
||||||
} else {
|
} else {
|
||||||
actually_extract_ruf(&file, &output_folder, 0)?;
|
actually_extract_ruf(file, &app_ctx.output_dir, 0)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64) -> Result<(), Box<dyn std::error::Error>> {
|
fn actually_extract_ruf(mut file: &File, output_folder: &PathBuf, start_offset: u64) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
file.seek(SeekFrom::Start(start_offset))?;
|
file.seek(SeekFrom::Start(start_offset))?;
|
||||||
let header: RufHeader = file.read_be()?;
|
let header: RufHeader = file.read_be()?;
|
||||||
|
|
||||||
@@ -139,8 +85,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64)
|
|||||||
println!("\nKey: {}", k);
|
println!("\nKey: {}", k);
|
||||||
key_bytes = hex::decode(k)?.as_slice().try_into()?;
|
key_bytes = hex::decode(k)?.as_slice().try_into()?;
|
||||||
} else {
|
} else {
|
||||||
println!("\nSorry, this firmware is not supported!");
|
return Err("This firmware is not supported!".into());
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
file.seek(SeekFrom::Start(start_offset + 2048))?;
|
file.seek(SeekFrom::Start(start_offset + 2048))?;
|
||||||
@@ -159,10 +104,7 @@ fn actually_extract_ruf(mut file: &File, output_folder: &str, start_offset: u64)
|
|||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", entry.payload_type_bytes, entry.payload_type()));
|
let output_path = Path::new(&output_folder).join(format!("{}_{}.bin", entry.payload_type_bytes, entry.payload_type()));
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&output_folder)?;
|
||||||
let mut out_file = OpenOptions::new()
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub fn decrypt_xor(data: &[u8]) -> Vec<u8> {
|
||||||
|
let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA";
|
||||||
|
data.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
@@ -1,38 +1,43 @@
|
|||||||
use std::fs::File;
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::fs::{self, OpenOptions};
|
use std::fs::{self, OpenOptions};
|
||||||
use std::io::{Write, Read, Cursor, Seek};
|
use std::io::{Write, Read, Cursor, Seek};
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
fn decrypt_xor(data: &[u8]) -> Vec<u8> {
|
pub struct RvpContext {
|
||||||
let key_bytes = b"\xCC\xF0\xC8\xC4\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA\xC6\xCA\xCC\xDA";
|
header_offset: u64,
|
||||||
data.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_rvp_file(mut file: &File) -> bool {
|
pub fn is_rvp_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)};
|
||||||
//MVP
|
//MVP
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
if header == b"UPDT" {
|
if header == b"UPDT" {
|
||||||
file.seek(std::io::SeekFrom::Start(36)).expect("Failed to seek"); //skip rest of header
|
return Ok(Some(Box::new(RvpContext {header_offset: 36})))
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//RVP
|
//RVP
|
||||||
let bytes = common::read_file(&file, 16, 18).expect("Failed to read from file.");
|
let bytes = common::read_file(&file, 16, 18)?;
|
||||||
for (_i, &b) in bytes.iter().enumerate().step_by(2) {
|
for (_i, &b) in bytes.iter().enumerate().step_by(2) {
|
||||||
if b != 0xA3 {
|
if b != 0xA3 {
|
||||||
return false;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file.seek(std::io::SeekFrom::Start(64)).expect("Failed to seek"); //skip rest of header
|
|
||||||
true
|
Ok(Some(Box::new(RvpContext {header_offset: 64})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
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");
|
||||||
|
|
||||||
|
file.seek(std::io::SeekFrom::Start(ctx.header_offset))?;
|
||||||
|
|
||||||
let mut obf_data = Vec::new(); //we sadly cannot deXOR on the fly because of its 32 byte pattern
|
let mut obf_data = Vec::new(); //we sadly cannot deXOR on the fly because of its 32 byte pattern
|
||||||
file.read_to_end(&mut obf_data)?;
|
file.read_to_end(&mut obf_data)?;
|
||||||
println!("DeXORing data..");
|
println!("DeXORing data..");
|
||||||
@@ -109,21 +114,18 @@ pub fn extract_rvp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn s
|
|||||||
} else {
|
} else {
|
||||||
println!("Unsupported header size!");
|
println!("Unsupported header size!");
|
||||||
break
|
break
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Size: {}", size);
|
println!("Size: {}", size);
|
||||||
let data = common::read_exact(&mut data_reader, size as usize)?;
|
let data = common::read_exact(&mut data_reader, size as usize)?;
|
||||||
let output_path = Path::new(&output_folder).join(if name=="" {format!("{}.bin", i)} else {format!("{}_{}.bin", i, name)});
|
let output_path = Path::new(&app_ctx.output_dir).join(if name=="" {format!("{}.bin", i)} else {format!("{}_{}.bin", i, name)});
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
||||||
out_file.write_all(&data)?;
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
println!("- Saved file!");
|
println!("- Saved file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
pub fn decrypt_xor(data: &[u8], key: &str) -> Vec<u8> {
|
||||||
|
let key_bytes = key.as_bytes();
|
||||||
|
data.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
@@ -1,33 +1,32 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path};
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{Write};
|
use std::io::{Write};
|
||||||
use hex::decode;
|
use hex::decode;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
use md5;
|
||||||
|
|
||||||
use crate::utils::common;
|
use crate::utils::common;
|
||||||
use crate::keys;
|
use crate::keys;
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
||||||
|
use include::decrypt_xor;
|
||||||
|
|
||||||
use md5;
|
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)};
|
||||||
|
|
||||||
pub fn is_samsung_old_dir(path: &PathBuf) -> bool {
|
if Path::new(&dir).join("image").is_dir() & Path::new(&dir).join("image/info.txt").exists(){
|
||||||
if Path::new(&path).join("image").is_dir() & Path::new(&path).join("image/info.txt").exists(){
|
Ok(Some(Box::new(())))
|
||||||
true
|
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt_xor(data: &[u8], key: &str) -> Vec<u8> {
|
pub fn extract_samsung_old(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let key_bytes = key.as_bytes();
|
let path = app_ctx.dir().ok_or("Extractor expected directory")?;
|
||||||
data.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, &byte)| byte ^ key_bytes[i % key_bytes.len()])
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
|
|
||||||
let fw_info = fs::read_to_string(Path::new(&path).join("image/info.txt"))?;
|
let fw_info = fs::read_to_string(Path::new(&path).join("image/info.txt"))?;
|
||||||
println!("Firmware info: {}", fw_info);
|
println!("Firmware info: {}", fw_info);
|
||||||
@@ -46,8 +45,7 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
|
|||||||
if let Some(p) = secret {
|
if let Some(p) = secret {
|
||||||
println!("Secret: {}", p);
|
println!("Secret: {}", p);
|
||||||
} else {
|
} else {
|
||||||
println!("Sorry, this firmware is not supported!");
|
return Err("This firmware is not supported!".into());
|
||||||
std::process::exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in fs::read_dir(image_path)? {
|
for entry in fs::read_dir(image_path)? {
|
||||||
@@ -114,9 +112,9 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
|
|||||||
let xor_key = fw_info.split_whitespace().next().unwrap();
|
let xor_key = fw_info.split_whitespace().next().unwrap();
|
||||||
let out_data = decrypt_xor(&decrypted_data, xor_key);
|
let out_data = decrypt_xor(&decrypted_data, xor_key);
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(filename.rsplit_once('.').map(|(left, _)| left).unwrap());
|
let output_path = Path::new(&app_ctx.output_dir).join(filename.rsplit_once('.').map(|(left, _)| left).unwrap());
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
let mut out_file = OpenOptions::new()
|
let mut out_file = OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
@@ -130,7 +128,5 @@ pub fn extract_samsung_old(path: &PathBuf, output_folder: &str) -> Result<(), Bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1,364 +0,0 @@
|
|||||||
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
|
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::fs::{self, File, OpenOptions};
|
|
||||||
use std::io::{Cursor, Seek, SeekFrom, Write};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
|
||||||
use crate::utils::compression::{decompress_zlib};
|
|
||||||
|
|
||||||
pub fn decipher(s: &[u8]) -> Vec<u8> {
|
|
||||||
let len_ = s.len();
|
|
||||||
let mut v3: u32 = 904;
|
|
||||||
let mut out = s.to_vec();
|
|
||||||
let mut cnt = 0;
|
|
||||||
|
|
||||||
if len_ > 0 {
|
|
||||||
let true_len = if len_ >= 0x80 {
|
|
||||||
128
|
|
||||||
} else {
|
|
||||||
len_
|
|
||||||
};
|
|
||||||
|
|
||||||
if true_len > 0 {
|
|
||||||
let mut i = 0;
|
|
||||||
let mut j: u8 = 0;
|
|
||||||
|
|
||||||
while i < s.len() {
|
|
||||||
let iter_ = s[i];
|
|
||||||
i += 1;
|
|
||||||
j = j.wrapping_add(1);
|
|
||||||
|
|
||||||
let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff;
|
|
||||||
let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8;
|
|
||||||
v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff;
|
|
||||||
|
|
||||||
if j == 0 {
|
|
||||||
v3 = 904;
|
|
||||||
}
|
|
||||||
|
|
||||||
if cnt < out.len() {
|
|
||||||
out[cnt] = v12;
|
|
||||||
cnt += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- STRUCTURES --
|
|
||||||
// -- SECFILE --
|
|
||||||
|
|
||||||
pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct SecHeader {
|
|
||||||
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
|
||||||
key_id_str_bytes: [u8; 4], //"key_id", purpose unknown
|
|
||||||
grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file
|
|
||||||
prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files
|
|
||||||
_unused_or_reserved: [u8; 16], //not used, is zeros
|
|
||||||
}
|
|
||||||
impl SecHeader {
|
|
||||||
pub fn key_id(&self) -> u32 {
|
|
||||||
let string = common::string_from_bytes(&self.key_id_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
pub fn grp_num(&self) -> u32 {
|
|
||||||
let string = common::string_from_bytes(&self.grp_num_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
pub fn prg_num(&self) -> u32 {
|
|
||||||
let string = common::string_from_bytes(&self.prg_num_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub static INFO_FILE_EXTENSION: &str = ".TXT";
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct FileHeader {
|
|
||||||
name_str_bytes: [u8; 12],
|
|
||||||
size_str_bytes: [u8; 12],
|
|
||||||
}
|
|
||||||
impl FileHeader {
|
|
||||||
pub fn name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.name_str_bytes)
|
|
||||||
}
|
|
||||||
pub fn size(&self) -> u64 {
|
|
||||||
let string = common::string_from_bytes(&self.size_str_bytes);
|
|
||||||
string.parse().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- MODULE --
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct ModuleComHeader { //"com_header"
|
|
||||||
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
|
||||||
_outer_maker_id: u8,
|
|
||||||
_outer_model_id: u8,
|
|
||||||
_inner_maker_id: u8,
|
|
||||||
_reserve1: u8,
|
|
||||||
_reserve2: u32,
|
|
||||||
_reserve3: u32,
|
|
||||||
_start_version: [u8; 4], //the first version that can upgrade to the new version
|
|
||||||
_end_version: [u8; 4], //the last version that can upgrade to the new version
|
|
||||||
_new_version: [u8; 4], //the new version, as in the version of the data in this module
|
|
||||||
_reserve4: u16,
|
|
||||||
_module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct ModuleHeader { //"header", appears after com_header
|
|
||||||
_module_id: u16,
|
|
||||||
module_atr: u8,
|
|
||||||
_target_id: u8,
|
|
||||||
pub cmp_size: u32,
|
|
||||||
_org_size: u32,
|
|
||||||
_crc_value: u32,
|
|
||||||
}
|
|
||||||
impl ModuleHeader {
|
|
||||||
pub fn is_ciphered(&self) -> bool {
|
|
||||||
(self.module_atr & 0x02) != 0
|
|
||||||
}
|
|
||||||
pub fn is_compressed(&self) -> bool {
|
|
||||||
(self.module_atr & 0x01) != 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct ContentHeader {
|
|
||||||
_magic1: u8, //always 0x01?
|
|
||||||
_dest_offset: u32,
|
|
||||||
_source_offset: u32,
|
|
||||||
pub size: u32,
|
|
||||||
_magic2: u8, //always 0x21?
|
|
||||||
}
|
|
||||||
impl ContentHeader {
|
|
||||||
//these hacks are needed because for some reason older files have the first nibble of the offset set to D/C
|
|
||||||
//no idea why, but masking them off makes it works properly
|
|
||||||
pub fn dest_offset(&self) -> u32 {
|
|
||||||
if ((self._dest_offset >> 28) & 0xF) == 0xD {
|
|
||||||
self._dest_offset & 0x0FFFFFFF
|
|
||||||
} else {
|
|
||||||
self._dest_offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn source_offset(&self) -> u32 {
|
|
||||||
if ((self._source_offset >> 28) & 0xF) == 0xC {
|
|
||||||
self._source_offset & 0x0FFFFFFF
|
|
||||||
} else {
|
|
||||||
self._source_offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn has_subfile(&self) -> bool {
|
|
||||||
self.source_offset() == 0x10E
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- TDI --
|
|
||||||
// Called SDIT.FDI in the secfile
|
|
||||||
|
|
||||||
pub static TDI_FILENAME: &str = "SDIT.FDI";
|
|
||||||
pub static SUPPORTED_TDI_VERSION: u16 = 2;
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct TdiHead {
|
|
||||||
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
|
||||||
pub num_of_group: u8,
|
|
||||||
_reserve1: u8,
|
|
||||||
pub format_version: u16, //checks for "2" here
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct TdiGroupHead {
|
|
||||||
pub group_id: u8,
|
|
||||||
pub num_of_target: u8, //logic checks that this is not more than 5
|
|
||||||
_reserved: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, BinRead)]
|
|
||||||
pub struct TdiTgtInf {
|
|
||||||
_outer_maker_id: u8,
|
|
||||||
_outer_model_id: u8,
|
|
||||||
_inner_maker_id: u8,
|
|
||||||
_reserve3: u8,
|
|
||||||
_inner_model_id: [u8; 4],
|
|
||||||
_ext_model_id: [u8; 4],
|
|
||||||
pub _start_version: [u8; 4], //the first version that can upgrade to the new version
|
|
||||||
pub _end_version: [u8; 4], //the last version that can upgrade to the new version
|
|
||||||
pub new_version: [u8; 4], //the new version, as in the version of the data in this module
|
|
||||||
pub target_id: u8,
|
|
||||||
_num_of_compatible_target: u8,
|
|
||||||
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
|
|
||||||
_unknown: [u8; 8],
|
|
||||||
module_name_bytes: [u8; 8],
|
|
||||||
}
|
|
||||||
impl TdiTgtInf {
|
|
||||||
pub fn module_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.module_name_bytes)
|
|
||||||
}
|
|
||||||
pub fn version_string(&self) -> String {
|
|
||||||
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// -- dec key --
|
|
||||||
static DEC_KEY: [u8; 16] = [
|
|
||||||
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
|
|
||||||
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
|
|
||||||
];
|
|
||||||
|
|
||||||
static DEC_IV: [u8; 16] = [
|
|
||||||
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
|
|
||||||
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
|
|
||||||
];
|
|
||||||
|
|
||||||
pub fn is_sddl_sec_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 32).expect("Failed to read from file.");
|
|
||||||
let deciph_header = decipher(&header);
|
|
||||||
if deciph_header.starts_with(b"\x11\x22\x33\x44") {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec<u8>), Box<dyn std::error::Error>> {
|
|
||||||
let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?);
|
|
||||||
let file_header: FileHeader = hdr_reader.read_be()?;
|
|
||||||
let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?;
|
|
||||||
|
|
||||||
Ok((file_header, file_data))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_tdi_to_modules(tdi_data: Vec<u8>) -> Result<Vec<TdiTgtInf>, Box<dyn std::error::Error>> {
|
|
||||||
let mut tdi_reader = Cursor::new(tdi_data);
|
|
||||||
let tdi_header: TdiHead = tdi_reader.read_be()?;
|
|
||||||
if tdi_header.download_id != DOWNLOAD_ID {
|
|
||||||
return Err("Invalid TDI header!".into());
|
|
||||||
}
|
|
||||||
if tdi_header.format_version != SUPPORTED_TDI_VERSION {
|
|
||||||
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);
|
|
||||||
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);
|
|
||||||
|
|
||||||
for _i in 0..group_head.num_of_target {
|
|
||||||
let tgt_inf: TdiTgtInf = tdi_reader.read_be()?;
|
|
||||||
println!("[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
|
|
||||||
if !modules.iter().any(|m| m.module_name() == tgt_inf.module_name()) {
|
|
||||||
modules.push(tgt_inf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(modules)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_sddl_sec(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
file.seek(SeekFrom::Start(0))?;
|
|
||||||
let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
|
||||||
let secfile_header: SecHeader = secfile_hdr_reader.read_be()?;
|
|
||||||
|
|
||||||
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
|
|
||||||
let (tdi_file, tdi_data) = get_sec_file(&file)?;
|
|
||||||
println!("[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(&output_folder).join(tdi_file.name()))?;
|
|
||||||
// out_file.write_all(&tdi_data)?;
|
|
||||||
//}
|
|
||||||
if tdi_file.name() != TDI_FILENAME {
|
|
||||||
return Err(format!("Invalid TDI filename {}!, expected: {}", tdi_file.name(), TDI_FILENAME).into());
|
|
||||||
}
|
|
||||||
//parse TDI
|
|
||||||
let modules = parse_tdi_to_modules(tdi_data)?;
|
|
||||||
|
|
||||||
//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)?;
|
|
||||||
println!("\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());
|
|
||||||
}
|
|
||||||
//if save_extra { //Save info file
|
|
||||||
// let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&output_folder).join(info_file.name()))?;
|
|
||||||
// out_file.write_all(&info_data)?;
|
|
||||||
//}
|
|
||||||
//print info file
|
|
||||||
println!("{}", String::from_utf8_lossy(&info_data));
|
|
||||||
}
|
|
||||||
|
|
||||||
//parse module data
|
|
||||||
for (i, module) in modules.iter().enumerate(){
|
|
||||||
println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}",
|
|
||||||
i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string());
|
|
||||||
|
|
||||||
for i in 0..module.num_of_txx {
|
|
||||||
let (module_file, module_data) = get_sec_file(&file)?;
|
|
||||||
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());
|
|
||||||
|
|
||||||
let mut module_reader = Cursor::new(module_data);
|
|
||||||
let com_header: ModuleComHeader = module_reader.read_be()?;
|
|
||||||
if com_header.download_id != DOWNLOAD_ID {
|
|
||||||
return Err("Invalid module com_header!".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let module_header: ModuleHeader = module_reader.read_be()?;
|
|
||||||
let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?;
|
|
||||||
if module_header.is_ciphered() {
|
|
||||||
println!(" - Deciphering...");
|
|
||||||
module_data = decipher(&module_data);
|
|
||||||
}
|
|
||||||
if module_header.is_compressed() {
|
|
||||||
println!(" - 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());
|
|
||||||
|
|
||||||
let output_path: PathBuf;
|
|
||||||
if content_header.has_subfile() {
|
|
||||||
let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?;
|
|
||||||
let sub_filename = common::string_from_bytes(&sub_filename_bytes);
|
|
||||||
println!(" --> {}", sub_filename);
|
|
||||||
|
|
||||||
let sub_folder_path = Path::new(&output_folder).join(module.module_name());
|
|
||||||
fs::create_dir_all(&sub_folder_path)?;
|
|
||||||
output_path = Path::new(&sub_folder_path).join(sub_filename);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
output_path = Path::new(&output_folder).join(format!("{}.bin", module.module_name()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = common::read_exact(&mut content_reader, content_header.size as usize)?;
|
|
||||||
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
|
||||||
out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?;
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
|
||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
pub fn decipher(s: &[u8]) -> Vec<u8> {
|
||||||
|
let len_ = s.len();
|
||||||
|
let mut v3: u32 = 904;
|
||||||
|
let mut out = s.to_vec();
|
||||||
|
let mut cnt = 0;
|
||||||
|
|
||||||
|
if len_ > 0 {
|
||||||
|
let true_len = if len_ >= 0x80 {
|
||||||
|
128
|
||||||
|
} else {
|
||||||
|
len_
|
||||||
|
};
|
||||||
|
|
||||||
|
if true_len > 0 {
|
||||||
|
let mut i = 0;
|
||||||
|
let mut j: u8 = 0;
|
||||||
|
|
||||||
|
while i < s.len() {
|
||||||
|
let iter_ = s[i];
|
||||||
|
i += 1;
|
||||||
|
j = j.wrapping_add(1);
|
||||||
|
|
||||||
|
let v11 = (iter_ as u32).wrapping_add(38400) & 0xffffffff;
|
||||||
|
let v12 = iter_ ^ ((v3 & 0xff00) >> 8) as u8;
|
||||||
|
v3 = v3.wrapping_add(v11).wrapping_add(163) & 0xffffffff;
|
||||||
|
|
||||||
|
if j == 0 {
|
||||||
|
v3 = 904;
|
||||||
|
}
|
||||||
|
|
||||||
|
if cnt < out.len() {
|
||||||
|
out[cnt] = v12;
|
||||||
|
cnt += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- STRUCTURES --
|
||||||
|
// -- SECFILE --
|
||||||
|
|
||||||
|
pub static DOWNLOAD_ID: [u8; 4] = [0x11, 0x22, 0x33, 0x44];
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct SecHeader {
|
||||||
|
pub _download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
key_id_str_bytes: [u8; 4], //"key_id", purpose unknown
|
||||||
|
grp_num_str_bytes: [u8; 4], //"grp_num", the count of groups, also represents the count of info files because each group has a respective info file
|
||||||
|
prg_num_str_bytes: [u8; 4], //"prg_num", the count of module (.FXX) files
|
||||||
|
_unused_or_reserved: [u8; 16], //not used, is zeros
|
||||||
|
}
|
||||||
|
impl SecHeader {
|
||||||
|
pub fn key_id(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.key_id_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
pub fn grp_num(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.grp_num_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
pub fn prg_num(&self) -> u32 {
|
||||||
|
let string = common::string_from_bytes(&self.prg_num_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static INFO_FILE_EXTENSION: &str = ".TXT";
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct FileHeader {
|
||||||
|
name_str_bytes: [u8; 12],
|
||||||
|
size_str_bytes: [u8; 12],
|
||||||
|
}
|
||||||
|
impl FileHeader {
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.name_str_bytes)
|
||||||
|
}
|
||||||
|
pub fn size(&self) -> u64 {
|
||||||
|
let string = common::string_from_bytes(&self.size_str_bytes);
|
||||||
|
string.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- MODULE --
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ModuleComHeader { //"com_header"
|
||||||
|
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
_outer_maker_id: u8,
|
||||||
|
_outer_model_id: u8,
|
||||||
|
_inner_maker_id: u8,
|
||||||
|
_reserve1: u8,
|
||||||
|
_reserve2: u32,
|
||||||
|
_reserve3: u32,
|
||||||
|
_start_version: [u8; 4], //the first version that can upgrade to the new version
|
||||||
|
_end_version: [u8; 4], //the last version that can upgrade to the new version
|
||||||
|
_new_version: [u8; 4], //the new version, as in the version of the data in this module
|
||||||
|
_reserve4: u16,
|
||||||
|
_module_num: u16, //the logic seems to indicate that there can be multiple entries in one module, but i have never seen this go above 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ModuleHeader { //"header", appears after com_header
|
||||||
|
_module_id: u16,
|
||||||
|
module_atr: u8,
|
||||||
|
_target_id: u8,
|
||||||
|
pub cmp_size: u32,
|
||||||
|
_org_size: u32,
|
||||||
|
_crc_value: u32,
|
||||||
|
}
|
||||||
|
impl ModuleHeader {
|
||||||
|
pub fn is_ciphered(&self) -> bool {
|
||||||
|
(self.module_atr & 0x02) != 0
|
||||||
|
}
|
||||||
|
pub fn is_compressed(&self) -> bool {
|
||||||
|
(self.module_atr & 0x01) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct ContentHeader {
|
||||||
|
_magic1: u8, //always 0x01?
|
||||||
|
_dest_offset: u32,
|
||||||
|
_source_offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
_magic2: u8, //always 0x21?
|
||||||
|
}
|
||||||
|
impl ContentHeader {
|
||||||
|
//these hacks are needed because for some reason older files have the first nibble of the offset set to D/C
|
||||||
|
//no idea why, but masking them off makes it works properly
|
||||||
|
pub fn dest_offset(&self) -> u32 {
|
||||||
|
if ((self._dest_offset >> 28) & 0xF) == 0xD {
|
||||||
|
self._dest_offset & 0x0FFFFFFF
|
||||||
|
} else {
|
||||||
|
self._dest_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn source_offset(&self) -> u32 {
|
||||||
|
if ((self._source_offset >> 28) & 0xF) == 0xC {
|
||||||
|
self._source_offset & 0x0FFFFFFF
|
||||||
|
} else {
|
||||||
|
self._source_offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn has_subfile(&self) -> bool {
|
||||||
|
self.source_offset() == 0x10E
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- TDI --
|
||||||
|
// Called SDIT.FDI in the secfile
|
||||||
|
|
||||||
|
pub static TDI_FILENAME: &str = "SDIT.FDI";
|
||||||
|
pub static SUPPORTED_TDI_VERSION: u16 = 2;
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiHead {
|
||||||
|
pub download_id: [u8; 4], //always 0x11, 0x22, 0x33, 0x44 - magic?
|
||||||
|
pub num_of_group: u8,
|
||||||
|
_reserve1: u8,
|
||||||
|
pub format_version: u16, //checks for "2" here
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiGroupHead {
|
||||||
|
pub group_id: u8,
|
||||||
|
pub num_of_target: u8, //logic checks that this is not more than 5
|
||||||
|
_reserved: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, BinRead)]
|
||||||
|
pub struct TdiTgtInf {
|
||||||
|
_outer_maker_id: u8,
|
||||||
|
_outer_model_id: u8,
|
||||||
|
_inner_maker_id: u8,
|
||||||
|
_reserve3: u8,
|
||||||
|
_inner_model_id: [u8; 4],
|
||||||
|
_ext_model_id: [u8; 4],
|
||||||
|
pub _start_version: [u8; 4], //the first version that can upgrade to the new version
|
||||||
|
pub _end_version: [u8; 4], //the last version that can upgrade to the new version
|
||||||
|
pub new_version: [u8; 4], //the new version, as in the version of the data in this module
|
||||||
|
pub target_id: u8,
|
||||||
|
_num_of_compatible_target: u8,
|
||||||
|
pub num_of_txx: u16, //"TXX" refers to the ".FXX" segment files of each module. I assume F is an encrypted version of T, the same happens with SDIT; "TDI" -> "FDI"
|
||||||
|
_unknown: [u8; 8],
|
||||||
|
module_name_bytes: [u8; 8],
|
||||||
|
}
|
||||||
|
impl TdiTgtInf {
|
||||||
|
pub fn module_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.module_name_bytes)
|
||||||
|
}
|
||||||
|
pub fn version_string(&self) -> String {
|
||||||
|
format!("{}.{}{}{}", self.new_version[0], self.new_version[1], self.new_version[2], self.new_version[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- dec key --
|
||||||
|
pub static DEC_KEY: [u8; 16] = [
|
||||||
|
0x26, 0xE0, 0x96, 0xD3, 0xEF, 0x8A, 0x8F, 0xBB,
|
||||||
|
0xAA, 0x5E, 0x51, 0x6F, 0x77, 0x26, 0xC2, 0x2C,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub static DEC_IV: [u8; 16] = [
|
||||||
|
0x3E, 0x4A, 0xE2, 0x3A, 0x69, 0xDB, 0x81, 0x54,
|
||||||
|
0xCD, 0x88, 0x38, 0xC4, 0xB9, 0x0C, 0x76, 0x66,
|
||||||
|
];
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
//sddl_dec 6.0 https://github.com/theubusu/sddl_dec
|
||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs::{self, File, OpenOptions};
|
||||||
|
use std::io::{Cursor, Seek, SeekFrom, Write};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use crate::utils::aes::{decrypt_aes128_cbc_pcks7};
|
||||||
|
use crate::utils::compression::{decompress_zlib};
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
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 deciph_header = decipher(&header);
|
||||||
|
if deciph_header.starts_with(b"\x11\x22\x33\x44") {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sec_file(mut file: &File) -> Result<(FileHeader, Vec<u8>), Box<dyn std::error::Error>> {
|
||||||
|
let mut hdr_reader = Cursor::new(decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, 32)?, &DEC_KEY, &DEC_IV)?);
|
||||||
|
let file_header: FileHeader = hdr_reader.read_be()?;
|
||||||
|
let file_data = decrypt_aes128_cbc_pcks7(&common::read_exact(&mut file, file_header.size() as usize)?, &DEC_KEY, &DEC_IV)?;
|
||||||
|
|
||||||
|
Ok((file_header, file_data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tdi_to_modules(tdi_data: Vec<u8>) -> Result<Vec<TdiTgtInf>, Box<dyn std::error::Error>> {
|
||||||
|
let mut tdi_reader = Cursor::new(tdi_data);
|
||||||
|
let tdi_header: TdiHead = tdi_reader.read_be()?;
|
||||||
|
if tdi_header.download_id != DOWNLOAD_ID {
|
||||||
|
return Err("Invalid TDI header!".into());
|
||||||
|
}
|
||||||
|
if tdi_header.format_version != SUPPORTED_TDI_VERSION {
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
for _i in 0..group_head.num_of_target {
|
||||||
|
let tgt_inf: TdiTgtInf = tdi_reader.read_be()?;
|
||||||
|
println!("[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
|
||||||
|
if !modules.iter().any(|m| m.module_name() == tgt_inf.module_name()) {
|
||||||
|
modules.push(tgt_inf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(modules)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_sddl_sec(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 save_extra = app_ctx.options.iter().any(|e| e == "sddl_sec:save_extra");
|
||||||
|
|
||||||
|
let mut secfile_hdr_reader = Cursor::new(decipher(&common::read_exact(&mut file, 32)?));
|
||||||
|
let secfile_header: SecHeader = secfile_hdr_reader.read_be()?;
|
||||||
|
|
||||||
|
println!("File info -\nKey ID: {}\nGroup count: {}\nModule file count: {}\n", secfile_header.key_id(), secfile_header.grp_num(), secfile_header.prg_num());
|
||||||
|
fs::create_dir_all(&app_ctx.output_dir)?;
|
||||||
|
|
||||||
|
let (tdi_file, tdi_data) = get_sec_file(&file)?;
|
||||||
|
println!("[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)?;
|
||||||
|
}
|
||||||
|
if tdi_file.name() != TDI_FILENAME {
|
||||||
|
return Err(format!("Invalid TDI filename {}!, expected: {}", tdi_file.name(), TDI_FILENAME).into());
|
||||||
|
}
|
||||||
|
//parse TDI
|
||||||
|
let modules = parse_tdi_to_modules(tdi_data)?;
|
||||||
|
|
||||||
|
//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)?;
|
||||||
|
println!("\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());
|
||||||
|
}
|
||||||
|
if save_extra { //Save info file
|
||||||
|
let mut out_file = OpenOptions::new().write(true).create(true).open(Path::new(&app_ctx.output_dir).join(info_file.name()))?;
|
||||||
|
out_file.write_all(&info_data)?;
|
||||||
|
}
|
||||||
|
//print info file
|
||||||
|
println!("{}", String::from_utf8_lossy(&info_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
//parse module data
|
||||||
|
for (i, module) in modules.iter().enumerate(){
|
||||||
|
println!("\nModule #{}/{} - {}, Target ID: {}, Segment count: {}, Version: {}",
|
||||||
|
i+1, &modules.len(), module.module_name(), module.target_id, module.num_of_txx, module.version_string());
|
||||||
|
|
||||||
|
for i in 0..module.num_of_txx {
|
||||||
|
let (module_file, module_data) = get_sec_file(&file)?;
|
||||||
|
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());
|
||||||
|
|
||||||
|
let mut module_reader = Cursor::new(module_data);
|
||||||
|
let com_header: ModuleComHeader = module_reader.read_be()?;
|
||||||
|
if com_header.download_id != DOWNLOAD_ID {
|
||||||
|
return Err("Invalid module com_header!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let module_header: ModuleHeader = module_reader.read_be()?;
|
||||||
|
let mut module_data = common::read_exact(&mut module_reader, module_header.cmp_size as usize)?;
|
||||||
|
if module_header.is_ciphered() {
|
||||||
|
println!(" - Deciphering...");
|
||||||
|
module_data = decipher(&module_data);
|
||||||
|
}
|
||||||
|
if module_header.is_compressed() {
|
||||||
|
println!(" - 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());
|
||||||
|
|
||||||
|
let output_path: PathBuf;
|
||||||
|
if content_header.has_subfile() {
|
||||||
|
let sub_filename_bytes = common::read_exact(&mut content_reader, 0x100)?;
|
||||||
|
let sub_filename = common::string_from_bytes(&sub_filename_bytes);
|
||||||
|
println!(" --> {}", sub_filename);
|
||||||
|
|
||||||
|
let sub_folder_path = Path::new(&app_ctx.output_dir).join(module.module_name());
|
||||||
|
fs::create_dir_all(&sub_folder_path)?;
|
||||||
|
output_path = Path::new(&sub_folder_path).join(sub_filename);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", module.module_name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = common::read_exact(&mut content_reader, content_header.size as usize)?;
|
||||||
|
let mut out_file = OpenOptions::new().read(true).write(true).create(true).open(output_path)?;
|
||||||
|
out_file.seek(SeekFrom::Start(content_header.dest_offset() as u64))?;
|
||||||
|
out_file.write_all(&data)?;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
use std::path::{Path};
|
|
||||||
use std::fs::{self, File, OpenOptions};
|
|
||||||
use std::io::{Write, Seek, SeekFrom};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Header {
|
|
||||||
#[br(count = 4)] _magic_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] model_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] firmware_bytes: Vec<u8>,
|
|
||||||
_unk: u32,
|
|
||||||
#[br(count = 8)] check: Vec<u8>,
|
|
||||||
#[br(count = 8)] _unk2: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.version_bytes)
|
|
||||||
}
|
|
||||||
fn model(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.model_bytes)
|
|
||||||
}
|
|
||||||
fn firmware(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.firmware_bytes)
|
|
||||||
}
|
|
||||||
fn is_new_type(&self) -> bool {
|
|
||||||
self.check == b"\x01VER_PR1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct EntryOld {
|
|
||||||
size: u32,
|
|
||||||
_unk: u32,
|
|
||||||
offset: u32,
|
|
||||||
_unk2: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct EntryNew {
|
|
||||||
size: u32,
|
|
||||||
_unk: u32,
|
|
||||||
offset: u32,
|
|
||||||
#[br(count = 12)] _unk2: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_slp_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"SLP\x00" {
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_slp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let header: Header = file.read_le()?;
|
|
||||||
|
|
||||||
println!("File info:\nModel: {}\nVersion: {}\nFirmware: {}\nNew type: {}\n",
|
|
||||||
header.model(), header.version(), header.firmware(), header.is_new_type());
|
|
||||||
|
|
||||||
let mut first_entry_offset = 0;
|
|
||||||
let mut entries: Vec<EntryOld> = Vec::new();
|
|
||||||
|
|
||||||
for i in 0..100 {
|
|
||||||
if (i != 0) && (file.stream_position()? >= first_entry_offset) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let offset;
|
|
||||||
let size;
|
|
||||||
if header.is_new_type() {
|
|
||||||
let entry: EntryNew = file.read_le()?;
|
|
||||||
offset = entry.offset;
|
|
||||||
size = entry.size;
|
|
||||||
} else {
|
|
||||||
let entry: EntryOld = file.read_le()?;
|
|
||||||
offset = entry.offset;
|
|
||||||
size = entry.size;
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
first_entry_offset = offset as u64;
|
|
||||||
}
|
|
||||||
println!("{}. Offset: {}, Size: {}", i + 1, offset, size);
|
|
||||||
entries.push(EntryOld {size: size, _unk: 0, offset: offset, _unk2: 0});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut i = 1;
|
|
||||||
for entry in &entries {
|
|
||||||
println!("\n({}/{}) - Offset: {}, Size: {}", i, &entries.len(), entry.offset, entry.size);
|
|
||||||
file.seek(SeekFrom::Start(entry.offset.into()))?;
|
|
||||||
let data = common::read_exact(&mut file, entry.size as usize)?;
|
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", i));
|
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new()
|
|
||||||
.write(true)
|
|
||||||
.create(true)
|
|
||||||
.open(output_path)?;
|
|
||||||
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
println!("- Saved file!");
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
_magic_bytes: [u8; 4],
|
||||||
|
version_bytes: [u8; 8],
|
||||||
|
model_bytes: [u8; 16],
|
||||||
|
firmware_bytes: [u8; 16],
|
||||||
|
_unk: u32,
|
||||||
|
check: [u8; 8],
|
||||||
|
_unk2: [u8; 8],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.version_bytes)
|
||||||
|
}
|
||||||
|
pub fn model(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.model_bytes)
|
||||||
|
}
|
||||||
|
pub fn firmware(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.firmware_bytes)
|
||||||
|
}
|
||||||
|
pub fn is_new_type(&self) -> bool {
|
||||||
|
&self.check == b"\x01VER_PR1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct EntryOld {
|
||||||
|
pub size: u32,
|
||||||
|
pub _unk: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
pub _unk2: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct EntryNew {
|
||||||
|
pub size: u32,
|
||||||
|
_unk: u32,
|
||||||
|
pub offset: u32,
|
||||||
|
_unk2: [u8; 12],
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::AppContext;
|
||||||
|
|
||||||
|
use std::path::{Path};
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{Write, Seek, SeekFrom};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
pub fn is_slp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
|
if header == b"SLP\x00" {
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 header: Header = file.read_le()?;
|
||||||
|
println!("File info:\nModel: {}\nVersion: {}\nFirmware: {}\nNew type: {}\n",
|
||||||
|
header.model(), header.version(), header.firmware(), header.is_new_type());
|
||||||
|
|
||||||
|
let mut first_entry_offset = 0;
|
||||||
|
let mut entries: Vec<EntryOld> = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..100 {
|
||||||
|
if (i != 0) && (file.stream_position()? >= first_entry_offset) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let offset;
|
||||||
|
let size;
|
||||||
|
if header.is_new_type() {
|
||||||
|
let entry: EntryNew = file.read_le()?;
|
||||||
|
offset = entry.offset;
|
||||||
|
size = entry.size;
|
||||||
|
} else {
|
||||||
|
let entry: EntryOld = file.read_le()?;
|
||||||
|
offset = entry.offset;
|
||||||
|
size = entry.size;
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
first_entry_offset = offset as u64;
|
||||||
|
}
|
||||||
|
println!("{}. Offset: {}, Size: {}", i + 1, offset, size);
|
||||||
|
entries.push(EntryOld {size: size, _unk: 0, offset: offset, _unk2: 0});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut i = 1;
|
||||||
|
for entry in &entries {
|
||||||
|
println!("\n({}/{}) - Offset: {}, Size: {}", i, &entries.len(), entry.offset, entry.size);
|
||||||
|
file.seek(SeekFrom::Start(entry.offset.into()))?;
|
||||||
|
let data = common::read_exact(&mut file, entry.size as usize)?;
|
||||||
|
|
||||||
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", i));
|
||||||
|
|
||||||
|
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(&data)?;
|
||||||
|
|
||||||
|
println!("- Saved file!");
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::fs::{self, OpenOptions};
|
|
||||||
use std::io::{Cursor, Write};
|
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
|
|
||||||
use crate::utils::common;
|
|
||||||
use crate::formats;
|
|
||||||
|
|
||||||
//thx sony
|
|
||||||
static HEX_SUBSTITUTION: [u8; 256] = [
|
|
||||||
0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30,
|
|
||||||
0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39,
|
|
||||||
0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5,
|
|
||||||
0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE,
|
|
||||||
0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F,
|
|
||||||
0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48,
|
|
||||||
0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2,
|
|
||||||
0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3,
|
|
||||||
];
|
|
||||||
|
|
||||||
fn hex_substitute(data: &[u8]) -> Vec<u8> {
|
|
||||||
data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Header {
|
|
||||||
#[br(count = 8)] firmware_name_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _unk_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] firmware_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] date_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _unk2_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 8)] _unk3_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] _unk4_version_bytes: Vec<u8>,
|
|
||||||
_unk: u32,
|
|
||||||
_checksum: u64,
|
|
||||||
file_size: u32,
|
|
||||||
#[br(count = 16)] _unk5_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 16)] _unk6_version_bytes: Vec<u8>,
|
|
||||||
#[br(count = 32)] _unk2: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl Header {
|
|
||||||
fn firmware_name(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.firmware_name_bytes)
|
|
||||||
}
|
|
||||||
fn firmware_version(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.firmware_version_bytes)
|
|
||||||
}
|
|
||||||
fn date(&self) -> String {
|
|
||||||
common::string_from_bytes(&self.date_bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(BinRead)]
|
|
||||||
struct Entry {
|
|
||||||
offset: u32,
|
|
||||||
size: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_sony_bdp_file(file: &File) -> bool {
|
|
||||||
let header = common::read_file(&file, 0, 4).expect("Failed to read from file.");
|
|
||||||
if header == b"\x01\x73\xEC\xC9" || header == b"\x01\x73\xEC\x1F" || header == b"\xEC\x7D\xB0\xB0" { //MSB1x, MSB0x, BDPPxx
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extract_sony_bdp(mut file: &File, output_folder: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let obf_header = common::read_exact(&mut file, 300)?;
|
|
||||||
let header = hex_substitute(&obf_header);
|
|
||||||
let mut hdr_reader = Cursor::new(header);
|
|
||||||
let hdr: Header = hdr_reader.read_le()?;
|
|
||||||
|
|
||||||
println!("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;
|
|
||||||
let mut first_entry_offset = 0;
|
|
||||||
let mut i = 1;
|
|
||||||
loop {
|
|
||||||
if (i != 1) && (hdr_reader.position() >= first_entry_offset) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry: Entry = hdr_reader.read_le()?;
|
|
||||||
if entry.size == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size);
|
|
||||||
if i == 1 {
|
|
||||||
first_entry_offset = entry.offset as u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
let obf_data = common::read_file(&file, entry.offset as u64, entry.size as usize)?;
|
|
||||||
let data = hex_substitute(&obf_data);
|
|
||||||
|
|
||||||
let output_path = Path::new(&output_folder).join(format!("{}.bin", i));
|
|
||||||
last_file_path = Some(output_path.clone());
|
|
||||||
|
|
||||||
fs::create_dir_all(&output_folder)?;
|
|
||||||
let mut out_file = OpenOptions::new().write(true).create(true).open(output_path)?;
|
|
||||||
out_file.write_all(&data)?;
|
|
||||||
|
|
||||||
println!("- Saved file!");
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//The last file is often a Mtk BDP file so we can extract that here.
|
|
||||||
if last_file_path.is_some() {
|
|
||||||
println!("\nChecking if it's also MTK BDP...");
|
|
||||||
let last_file = File::open(last_file_path.unwrap())?;
|
|
||||||
if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&last_file)? {
|
|
||||||
println!("- MTK BDP file detected!\n");
|
|
||||||
let mtk_extraction_path = format!("{}/{}", &output_folder, i - 1);
|
|
||||||
formats::mtk_bdp::extract_mtk_bdp(&last_file, &mtk_extraction_path, result)?;
|
|
||||||
} else {
|
|
||||||
println!("- Not an MTK BDP file.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nExtraction finished!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use crate::utils::common;
|
||||||
|
use binrw::BinRead;
|
||||||
|
|
||||||
|
//thx sony
|
||||||
|
static HEX_SUBSTITUTION: [u8; 256] = [
|
||||||
|
0xE8, 0x4D, 0x63, 0xF4, 0xF8, 0xA9, 0x21, 0x9C, 0xC7, 0x82, 0xCD, 0xE3, 0xC1, 0xCE, 0xC0, 0xFA, 0xE7, 0xD6, 0x96, 0x46, 0x12, 0x03, 0x14, 0x33, 0xED, 0x10, 0xEC, 0x69, 0x16, 0xE0, 0x28, 0x30,
|
||||||
|
0x77, 0x0E, 0x3D, 0xEF, 0x36, 0x4C, 0x18, 0xEB, 0x41, 0x89, 0x64, 0x8A, 0x70, 0x0C, 0x23, 0xA3, 0x79, 0x6D, 0x75, 0x7E, 0x1A, 0x2D, 0x01, 0x91, 0x88, 0xCB, 0xFC, 0x8B, 0xFD, 0x94, 0x0A, 0x39,
|
||||||
|
0xBC, 0x98, 0x87, 0xBB, 0xC2, 0x9B, 0x81, 0x1C, 0x4B, 0xA6, 0x58, 0x59, 0x45, 0x57, 0x8C, 0x7B, 0x5A, 0x56, 0x08, 0x73, 0x65, 0xEE, 0x2A, 0x25, 0xB0, 0x5B, 0x55, 0xB2, 0xB8, 0x1E, 0xEA, 0xC5,
|
||||||
|
0x6A, 0x40, 0x86, 0x5C, 0x3C, 0x54, 0xBF, 0xF6, 0xA8, 0xF2, 0x06, 0x4A, 0xFE, 0xC4, 0x32, 0x8D, 0xF0, 0x5D, 0x35, 0x53, 0xD7, 0xBD, 0xBA, 0x20, 0x2F, 0xCA, 0xE1, 0xCC, 0xA4, 0x44, 0x85, 0xDE,
|
||||||
|
0xDB, 0x5E, 0x27, 0x52, 0x6E, 0x38, 0x04, 0x66, 0xD0, 0x92, 0xD3, 0xC6, 0x7D, 0x71, 0xDA, 0x2C, 0x9A, 0x49, 0x8E, 0x80, 0x13, 0x5F, 0x11, 0x51, 0x15, 0x22, 0xCF, 0xAC, 0x0F, 0xD5, 0xFF, 0x3F,
|
||||||
|
0x17, 0xAD, 0xD8, 0xD9, 0xAB, 0x02, 0x6B, 0x0D, 0xDF, 0xF1, 0x84, 0x3B, 0x78, 0x19, 0x76, 0x60, 0x50, 0xDC, 0xC3, 0xB5, 0x43, 0x0B, 0x95, 0x97, 0x67, 0xD4, 0x29, 0xF7, 0xE4, 0x1B, 0xAE, 0x48,
|
||||||
|
0xB1, 0x8F, 0x24, 0x7A, 0x74, 0xFB, 0x34, 0x09, 0x00, 0x31, 0x9F, 0x61, 0x4F, 0xB6, 0xA0, 0xA7, 0xB4, 0x9E, 0x1D, 0xAA, 0xF9, 0xBE, 0x37, 0x2E, 0xB9, 0x6F, 0xA5, 0x83, 0xA1, 0x93, 0x07, 0xE2,
|
||||||
|
0x7F, 0x3E, 0xF3, 0x99, 0x62, 0x4E, 0xE9, 0xC8, 0x6C, 0x68, 0x1F, 0x47, 0x42, 0x26, 0x9D, 0xE5, 0x7C, 0x72, 0x3A, 0x2B, 0xE6, 0xF5, 0xD2, 0x90, 0x05, 0xD1, 0xDD, 0xC9, 0xAF, 0xB7, 0xA2, 0xB3,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn hex_substitute(data: &[u8]) -> Vec<u8> {
|
||||||
|
data.iter().map(|&b| HEX_SUBSTITUTION[b as usize]).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Header {
|
||||||
|
firmware_name_bytes: [u8; 8],
|
||||||
|
_unk_version_bytes: [u8; 8],
|
||||||
|
firmware_version_bytes: [u8; 16],
|
||||||
|
date_bytes: [u8; 16],
|
||||||
|
_unk2_version_bytes: [u8; 8],
|
||||||
|
_unk3_version_bytes: [u8; 8],
|
||||||
|
_unk4_version_bytes: [u8; 16],
|
||||||
|
_unk: u32,
|
||||||
|
_checksum: u64,
|
||||||
|
pub file_size: u32,
|
||||||
|
_unk5_version_bytes: [u8; 16],
|
||||||
|
_unk6_version_bytes: [u8; 16],
|
||||||
|
_unk2: [u8; 32],
|
||||||
|
}
|
||||||
|
impl Header {
|
||||||
|
pub fn firmware_name(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.firmware_name_bytes)
|
||||||
|
}
|
||||||
|
pub fn firmware_version(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.firmware_version_bytes)
|
||||||
|
}
|
||||||
|
pub fn date(&self) -> String {
|
||||||
|
common::string_from_bytes(&self.date_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BinRead)]
|
||||||
|
pub struct Entry {
|
||||||
|
pub offset: u32,
|
||||||
|
pub size: u32,
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
mod include;
|
||||||
|
use std::any::Any;
|
||||||
|
use crate::{InputTarget, AppContext};
|
||||||
|
|
||||||
|
use std::fs::File;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs::{self, OpenOptions};
|
||||||
|
use std::io::{Cursor, Write};
|
||||||
|
use binrw::BinReaderExt;
|
||||||
|
|
||||||
|
use crate::utils::common;
|
||||||
|
use crate::formats;
|
||||||
|
use include::*;
|
||||||
|
|
||||||
|
pub fn is_sony_bdp_file(app_ctx: &AppContext) -> Result<Option<Box<dyn Any>>, Box<dyn std::error::Error>> {
|
||||||
|
let file = match app_ctx.file() {Some(f) => f, None => return Ok(None)};
|
||||||
|
|
||||||
|
let header = common::read_file(&file, 0, 4)?;
|
||||||
|
if header == b"\x01\x73\xEC\xC9" || header == b"\x01\x73\xEC\x1F" || header == b"\xEC\x7D\xB0\xB0" { //MSB1x, MSB0x, BDPPxx
|
||||||
|
Ok(Some(Box::new(())))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_sony_bdp(app_ctx: &AppContext, _ctx: Box<dyn Any>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut file = app_ctx.file().ok_or("Extractor expected file")?;
|
||||||
|
|
||||||
|
let obf_header = common::read_exact(&mut file, 300)?;
|
||||||
|
let header = hex_substitute(&obf_header);
|
||||||
|
let mut hdr_reader = Cursor::new(header);
|
||||||
|
let hdr: Header = hdr_reader.read_le()?;
|
||||||
|
|
||||||
|
println!("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;
|
||||||
|
let mut first_entry_offset = 0;
|
||||||
|
let mut i = 1;
|
||||||
|
loop {
|
||||||
|
if (i != 1) && (hdr_reader.position() >= first_entry_offset) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry: Entry = hdr_reader.read_le()?;
|
||||||
|
if entry.size == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n#{} - Offset: {}, Size: {}", i, entry.offset, entry.size);
|
||||||
|
if i == 1 {
|
||||||
|
first_entry_offset = entry.offset as u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
let obf_data = common::read_file(&file, entry.offset as u64, entry.size as usize)?;
|
||||||
|
let data = hex_substitute(&obf_data);
|
||||||
|
|
||||||
|
let output_path = Path::new(&app_ctx.output_dir).join(format!("{}.bin", i));
|
||||||
|
last_file_path = Some(output_path.clone());
|
||||||
|
|
||||||
|
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(&data)?;
|
||||||
|
|
||||||
|
println!("- Saved file!");
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//The last file is often a Mtk BDP file so we can extract that here.
|
||||||
|
if last_file_path.is_some() {
|
||||||
|
println!("\nChecking if it's also MTK BDP...");
|
||||||
|
|
||||||
|
let last_file = File::open(last_file_path.unwrap())?;
|
||||||
|
let mtk_extraction_path = app_ctx.output_dir.join(format!("{}", i + 1));
|
||||||
|
|
||||||
|
//this is getting stupid...
|
||||||
|
let ctx: AppContext = AppContext { input: InputTarget::File(last_file), output_dir: mtk_extraction_path, options: app_ctx.options.clone() };
|
||||||
|
|
||||||
|
if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&ctx)? {
|
||||||
|
println!("- MTK BDP file detected!\n");
|
||||||
|
|
||||||
|
formats::mtk_bdp::extract_mtk_bdp(&ctx, result)?;
|
||||||
|
} else {
|
||||||
|
println!("- Not an MTK BDP file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
+79
-132
@@ -4,165 +4,112 @@ mod utils;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::path::{PathBuf};
|
use std::path::{PathBuf};
|
||||||
use std::io::{self};
|
use std::io::{self, Seek, SeekFrom};
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
|
use crate::formats::{Format, get_registry};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
struct Args {
|
struct Args {
|
||||||
input_target: String,
|
input_target: String,
|
||||||
output_folder: Option<String>,
|
output_directory: Option<String>,
|
||||||
|
|
||||||
|
///format specific options
|
||||||
|
#[arg(short, long)]
|
||||||
|
options: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum InputTarget {
|
||||||
|
File(File),
|
||||||
|
Directory(PathBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AppContext {
|
||||||
|
pub input: InputTarget,
|
||||||
|
pub output_dir: PathBuf,
|
||||||
|
pub options: Vec<String>,
|
||||||
|
}
|
||||||
|
impl AppContext {
|
||||||
|
pub fn file(&self) -> Option<&File> {
|
||||||
|
match &self.input {
|
||||||
|
InputTarget::File(f) => Some(f),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dir(&self) -> Option<&PathBuf> {
|
||||||
|
match &self.input {
|
||||||
|
InputTarget::Directory(p) => Some(p),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("unixtract Firmware extractor");
|
println!("unixtract Firmware extractor");
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let target_path = args.input_target;
|
let target_path_str = args.input_target;
|
||||||
println!("Input target: {}", target_path);
|
println!("Input target: {}", target_path_str);
|
||||||
let path = PathBuf::from(target_path);
|
let target_path = PathBuf::from(&target_path_str);
|
||||||
|
|
||||||
let output_path = if args.output_folder.is_some() {
|
let output_path_str = if args.output_directory.is_some() {
|
||||||
args.output_folder.unwrap()
|
args.output_directory.unwrap()
|
||||||
} else {
|
} else {
|
||||||
format!("_{}", path.file_name().and_then(|s| s.to_str()).unwrap())
|
format!("_{}", target_path.file_name().and_then(|s| s.to_str()).unwrap())
|
||||||
};
|
};
|
||||||
println!("Output folder: {}\n", output_path);
|
println!("Output directory: {}", output_path_str);
|
||||||
|
let output_directory_path = PathBuf::from(&output_path_str);
|
||||||
|
|
||||||
let output_folder_path = PathBuf::from(&output_path);
|
if output_directory_path.exists() {
|
||||||
if output_folder_path.exists() {
|
if output_directory_path.is_dir() {
|
||||||
if output_folder_path.is_dir() {
|
let is_empty = fs::read_dir(&output_directory_path)?.next().is_none();
|
||||||
let is_empty = fs::read_dir(&output_folder_path)?.next().is_none();
|
|
||||||
if !is_empty {
|
if !is_empty {
|
||||||
println!("Warning: Output folder already exists and is NOT empty! Files may be overwritten!");
|
println!("\nWarning: Output folder already exists and is NOT empty! Files may be overwritten!");
|
||||||
println!("Press Enter if you want to continue...");
|
println!("Press Enter if you want to continue...");
|
||||||
io::stdin().read_line(&mut String::new())?;
|
io::stdin().read_line(&mut String::new())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if path.is_dir() {
|
let app_ctx;
|
||||||
if formats::samsung_old::is_samsung_old_dir(&path) {
|
|
||||||
println!("Samsung old firmware dir detected!\n");
|
if target_path.is_file() {
|
||||||
formats::samsung_old::extract_samsung_old(&path, &output_path)?
|
let file = File::open(&target_path)?;
|
||||||
|
app_ctx = AppContext {
|
||||||
|
input: InputTarget::File(file),
|
||||||
|
output_dir: output_directory_path,
|
||||||
|
options: args.options,
|
||||||
|
};
|
||||||
|
} else if target_path.is_dir() {
|
||||||
|
app_ctx = AppContext {
|
||||||
|
input: InputTarget::Directory(target_path),
|
||||||
|
output_dir: output_directory_path,
|
||||||
|
options: args.options,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
println!("Input format not recognized!");
|
return Err("Invalid input path!".into());
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
let file = File::open(path)?;
|
let formats: Vec<Format> = get_registry();
|
||||||
if formats::sddl_sec::is_sddl_sec_file(&file) {
|
println!("Loaded {} formats!", formats.len());
|
||||||
println!("SDDL.SEC file detected!");
|
|
||||||
formats::sddl_sec::extract_sddl_sec(&file, &output_path)?;
|
for format in formats {
|
||||||
|
if let Some(ctx) = (format.detector_func)(&app_ctx)? {
|
||||||
|
println!("\n{} detected!", format.name);
|
||||||
|
|
||||||
|
//reset seek of the file if present
|
||||||
|
if let Some(mut file) = app_ctx.file() {
|
||||||
|
file.seek(SeekFrom::Start(0))?;
|
||||||
}
|
}
|
||||||
else if formats::invincible_image::is_invincible_image_file(&file) {
|
|
||||||
println!("INVINCIBLE_IMAGE file detected!");
|
(format.extractor_func)(&app_ctx, ctx)?;
|
||||||
formats::invincible_image::extract_invincible_image(&file, &output_path)?;
|
|
||||||
}
|
//extractor returned with no error
|
||||||
else if formats::msd10::is_msd10_file(&file) {
|
println!("\nExtraction finished! Saved extracted files to {}", output_path_str);
|
||||||
println!("MSD10 file detected!");
|
return Ok(());
|
||||||
formats::msd10::extract_msd10(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::msd11::is_msd11_file(&file) {
|
|
||||||
println!("MSD11 file detected!");
|
|
||||||
formats::msd11::extract_msd11(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::nvt_timg::is_nvt_timg_file(&file) {
|
|
||||||
println!("Novatek TIMG file detected!");
|
|
||||||
formats::nvt_timg::extract_nvt_timg(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::bdl::is_bdl_file(&file) {
|
|
||||||
println!("BDL file detected!");
|
|
||||||
formats::bdl::extract_bdl(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::android_ota_payload::is_android_ota_payload_file(&file) {
|
|
||||||
println!("Android OTA payload file detected!");
|
|
||||||
formats::android_ota_payload::extract_android_ota_payload(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::novatek::is_novatek_file(&file) {
|
|
||||||
println!("Novatek file detected!");
|
|
||||||
formats::novatek::extract_novatek(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::slp::is_slp_file(&file) {
|
|
||||||
println!("SLP file detected!");
|
|
||||||
formats::slp::extract_slp(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::epk1::is_epk1_file(&file) {
|
|
||||||
println!("EPK1 file detected!");
|
|
||||||
formats::epk1::extract_epk1(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::epk2b::is_epk2b_file(&file) {
|
|
||||||
println!("EPK2B file detected!");
|
|
||||||
formats::epk2b::extract_epk2b(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
//epk with encrypted header - it can be epk2 or epk3 so we need to check
|
|
||||||
else if formats::epk::is_epk_file(&file) {
|
|
||||||
println!("EPK file detected!");
|
|
||||||
formats::epk::extract_epk(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
//epk2 with unencrypted header
|
|
||||||
else if formats::epk2::is_epk2_file(&file) {
|
|
||||||
println!("EPK2 file detected!");
|
|
||||||
formats::epk2::extract_epk2(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::ruf::is_ruf_file(&file) {
|
|
||||||
println!("RUF file detected!");
|
|
||||||
formats::ruf::extract_ruf(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::funai_upg::is_funai_upg_file(&file) {
|
|
||||||
println!("Funai UPG file detected!");
|
|
||||||
formats::funai_upg::extract_funai_upg(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::pfl_upg::is_pfl_upg_file(&file) {
|
|
||||||
println!("PFL UPG file detected!");
|
|
||||||
formats::pfl_upg::extract_pfl_upg(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::amlogic::is_amlogic_file(&file) {
|
|
||||||
println!("Amlogic image file detected!");
|
|
||||||
formats::amlogic::extract_amlogic(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if let Some(result) = formats::pana_dvd::is_pana_dvd_file(&file)? {
|
|
||||||
println!("PANA_DVD file detected!");
|
|
||||||
formats::pana_dvd::extract_pana_dvd(&file, &output_path, result)?;
|
|
||||||
}
|
|
||||||
else if formats::pup::is_pup_file(&file) {
|
|
||||||
println!("PUP file detected!");
|
|
||||||
formats::pup::extract_pup(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::sony_bdp::is_sony_bdp_file(&file) {
|
|
||||||
println!("Sony BDP file detected!");
|
|
||||||
formats::sony_bdp::extract_sony_bdp(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::rvp::is_rvp_file(&file) {
|
|
||||||
println!("RVP/MVP file detected!");
|
|
||||||
formats::rvp::extract_rvp(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::mstar::is_mstar_file(&file) {
|
|
||||||
println!("Mstar upgrade file detected!");
|
|
||||||
formats::mstar::extract_mstar(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if formats::roku::is_roku_file(&file) {
|
|
||||||
println!("Roku file detected!");
|
|
||||||
formats::roku::extract_roku(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if let Some(result) = formats::mtk_pkg::is_mtk_pkg_file(&file)? {
|
|
||||||
println!("MTK PKG file detected!");
|
|
||||||
formats::mtk_pkg::extract_mtk_pkg(&file, &output_path, result)?;
|
|
||||||
}
|
|
||||||
else if formats::mtk_pkg_old::is_mtk_pkg_old_file(&file) {
|
|
||||||
println!("MTK PKG (Old) file detected!");
|
|
||||||
formats::mtk_pkg_old::extract_mtk_pkg_old(&file, &output_path)?;
|
|
||||||
}
|
|
||||||
else if let Some(result) = formats::mtk_pkg_new::is_mtk_pkg_new_file(&file)? {
|
|
||||||
println!("MTK PKG (New) file detected!");
|
|
||||||
formats::mtk_pkg_new::extract_mtk_pkg_new(&file, &output_path, result)?;
|
|
||||||
}
|
|
||||||
else if let Some(result) = formats::mtk_bdp::is_mtk_bdp_file(&file)? {
|
|
||||||
println!("MTK BDP file detected!");
|
|
||||||
formats::mtk_bdp::extract_mtk_bdp(&file, &output_path, result)?;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
println!("Input format not recognized!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
println!("\nInput format not recognized!");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod aes;
|
pub mod aes;
|
||||||
pub mod pana_dvd_crypto;
|
|
||||||
pub mod lzss;
|
|
||||||
pub mod lzop;
|
pub mod lzop;
|
||||||
pub mod sparse;
|
pub mod sparse;
|
||||||
pub mod compression;
|
pub mod compression;
|
||||||
pub mod android_ota_update_metadata;
|
|
||||||
pub mod mtk_crypto;
|
|
||||||
pub mod lzhs;
|
|
||||||
pub mod huffman_tables;
|
|
||||||
pub mod msd_ouith_parser_old;
|
|
||||||
pub mod msd_ouith_parser_tizen_1_8;
|
|
||||||
pub mod msd_ouith_parser_tizen_1_9;
|
|
||||||
Reference in New Issue
Block a user