diff --git a/src/formats.rs b/src/formats.rs index 331ed6e..390a62c 100644 --- a/src/formats.rs +++ b/src/formats.rs @@ -15,4 +15,5 @@ pub mod epk2; pub mod epk3; pub mod mtk_pkg; -pub mod mtk_upgrade_loader; \ No newline at end of file +pub mod mtk_upgrade_loader; +pub mod mtk_bdp; \ No newline at end of file diff --git a/src/formats/mtk_bdp.rs b/src/formats/mtk_bdp.rs new file mode 100644 index 0000000..0f8a121 --- /dev/null +++ b/src/formats/mtk_bdp.rs @@ -0,0 +1,149 @@ +use std::fs::File; +use std::path::{Path}; +use std::fs::{self, OpenOptions}; +use std::io::{Seek, SeekFrom, Read, Write}; +use binrw::{BinRead, BinReaderExt}; + +use crate::common; + +#[derive(BinRead)] +struct TocEntry { + id: u32, + offset: u32, + size: u32, + _unk: u32, + part_type: u32, +} + +#[derive(BinRead)] +struct PartHeader { + #[br(count = 20)] _unk1: Vec, + part_count: u32, + #[br(count = 40)] _unk2: Vec, +} + +#[derive(BinRead)] +struct PartEntry { + #[br(count = 16)] name_bytes: Vec, + id: u32, + _unk: u32, + _part_type: u32, + size: u32, + #[br(count = 32)] _unknown: Vec, +} +impl PartEntry { + fn name(&self) -> String { + common::string_from_bytes(&self.name_bytes) + } +} + +static ITIP_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50]; + +fn find_bytes(data: &[u8], pattern: &[u8]) -> Option { + data.windows(pattern.len()).position(|window| window == pattern) +} + +pub fn is_mtk_bdp_file(mut file: &File) -> Option { + let file_size = file.metadata().expect("REASON").len(); + let mut data = Vec::new(); + + let start_offset = file_size.saturating_sub(file_size / 20); // only search in the last 5% of file (lets not waste time) + let _ = file.seek(SeekFrom::Start(start_offset)); + + file.read_to_end(&mut data).expect("Failed to read from file."); + + if let Some(pos) = find_bytes(&data, &ITIP_MAGIC) { + Some(start_offset as usize + pos) + } else { + None + } +} + +pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, offset_opt: Option) -> Result<(), Box> { + let offset = offset_opt.unwrap(); + println!("Reading PIT at: {}", offset); + + file.seek(SeekFrom::Start(offset as u64 + 8))?; + + let ittp_check = common::read_exact(&mut file, 8)?; + let toc_offset; + if ittp_check == ITIP_MAGIC { + //old pit + toc_offset = offset + 80; + } else { + //new pit + let _ = common::read_exact(&mut file, 16)?; + let toc_offset_bytes = common::read_exact(&mut file, 4)?; + toc_offset = u32::from_le_bytes(toc_offset_bytes.try_into().unwrap()) as usize; + } + + println!("\nReading TOC at: {}", toc_offset); + file.seek(SeekFrom::Start(toc_offset as u64))?; + + let toc_check = common::read_exact(&mut file, 20)?; + if toc_check != b"\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85\xCD\xAB\x30\x85" { + println!("Invalid TOC!"); + return Ok(()) + } + + let mut entries: Vec = Vec::new(); + let mut part_table_offset: Option = None; + let mut n = 1; + loop { + let entry: TocEntry = file.read_le()?; + if entry.id == 0x8530efef { + break + } + println!("Entry {}. ID: {:02x}, Offset: {}, Size: {}, Type: {:02x}", n, entry.id, entry.offset, entry.size, entry.part_type); + + if entry.id == 0x02 && entry.part_type == 0x00 { + part_table_offset = Some(entry.offset as u64); + } + + entries.push(entry); + n += 1; + } + + if part_table_offset.is_none() { + println!("Failed to find partition table offset!"); + return Ok(()); + } + + println!("\nReading partition table at: {}", part_table_offset.unwrap()); + + file.seek(SeekFrom::Start(part_table_offset.unwrap()))?; + let part_header: PartHeader = file.read_le()?; + println!("Part count: {}", part_header.part_count); + + for i in 0..part_header.part_count { + let part_entry: PartEntry = file.read_le()?; + println!("\n{}/{}. {}, ID: {:02x}, Size: {}", i + 1, part_header.part_count, part_entry.name(), part_entry.id, part_entry.size); + + for entry in &entries{ + if entry.id == part_entry.id && entry.size == part_entry.size { + //println!("- Saving {}.bin, Offset: {}, Size: {}", part_entry.name(), entry.offset, entry.size); + let current_pos = file.stream_position()?; + file.seek(SeekFrom::Start(entry.offset as u64))?; + let data = common::read_exact(&mut file, entry.size as usize)?; + + let output_path = Path::new(&output_folder).join(part_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!"); + file.seek(SeekFrom::Start(current_pos))?; + break + } + } + } + + println!("\nExtraction finished!"); + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8e4abc9..0ebd9e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -97,9 +97,16 @@ fn main() -> Result<(), Box> { else if formats::mtk_upgrade_loader::is_mtk_upgrade_loader_file(&file) { println!("MTK upgrade_loader file detected!"); formats::mtk_upgrade_loader::extract_mtk_upgrade_loader(&file, &output_path)?; - } + } else { - println!("Input format not recognized!"); + // I dont like this need to pass result from search to avoid searching twice + let mtk_bdp_result = formats::mtk_bdp::is_mtk_bdp_file(&file); + if mtk_bdp_result.is_some() { + println!("MTK BDP file detected!\n"); + formats::mtk_bdp::extract_mtk_bdp(&file, &output_path, mtk_bdp_result)?; + } else { + println!("Input format not recognized!"); + } } }