From 6e2a1af92ee04caf61816a6bee3d5cb17349a2d2 Mon Sep 17 00:00:00 2001 From: theubusu <80545678+theubusu@users.noreply.github.com> Date: Sun, 21 Dec 2025 21:10:40 +0100 Subject: [PATCH] Add BDL extractor --- src/formats.rs | 1 + src/formats/bdl.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 ++ 3 files changed, 147 insertions(+) create mode 100644 src/formats/bdl.rs diff --git a/src/formats.rs b/src/formats.rs index 30c7943..e43a812 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -13,6 +13,7 @@ pub mod rvp; pub mod funai_upg; pub mod pana_dvd; pub mod android_ota_payload; +pub mod bdl; pub mod pup; diff --git a/src/formats/bdl.rs b/src/formats/bdl.rs new file mode 100644 index 0000000..bca2206 --- /dev/null +++ b/src/formats/bdl.rs @@ -0,0 +1,142 @@ +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, //ibdl + #[br(count = 8)] _file_version: Vec, + _unk1: u32, + pkg_count: u32, + #[br(count = 12)] _unk2: Vec, + #[br(count = 256)] date_bytes: Vec, + #[br(count = 256)] manufacturer_bytes: Vec, + #[br(count = 256)] model_bytes: Vec, + #[br(count = 9)] _unk3: Vec, + #[br(count = 256)] version_bytes: Vec, + #[br(count = 1280)] info_bytes: Vec, +} +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, //ipkg + #[br(count = 12)] _unk1: Vec, + entry_count: u32, + #[br(count = 12)] _unk2: Vec, + #[br(count = 256)] version_bytes: Vec, + #[br(count = 256)] manufacturer_bytes: Vec, + #[br(count = 256)] name_bytes: Vec, + #[br(count = 285)] _unk3: Vec, +} +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, + 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> { + 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 = 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 = 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(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 36262f3..dec4c59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -70,6 +70,10 @@ fn main() -> Result<(), Box> { 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)?;