2025-10-16 21:04:49 +02:00
|
|
|
use std::fs::File;
|
|
|
|
|
use std::path::{Path};
|
|
|
|
|
use std::fs::{self, OpenOptions};
|
|
|
|
|
use std::io::{Seek, SeekFrom, Read, Write};
|
|
|
|
|
use binrw::{BinRead, BinReaderExt};
|
|
|
|
|
|
2025-11-26 17:29:25 +01:00
|
|
|
use crate::utils::common;
|
2026-02-02 01:54:35 +01:00
|
|
|
pub struct MtkBdpContext {
|
|
|
|
|
pitit_offset: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static PITIT_MAGIC: [u8; 8] = [0x69, 0x54, 0x49, 0x50, 0x69, 0x54, 0x49, 0x50];
|
|
|
|
|
static PITIT_END_MARKER: u32 = 0x69_54_49_50; //PITi - end marker of PITIT
|
2025-10-16 21:04:49 +02:00
|
|
|
|
|
|
|
|
#[derive(BinRead)]
|
2026-01-21 23:34:14 +01:00
|
|
|
struct PITITPITEntry {
|
|
|
|
|
nand_size: u32,
|
|
|
|
|
pit_offset: u32,
|
|
|
|
|
pit_size: u32,
|
|
|
|
|
_private_data_2: u32,
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(BinRead)]
|
2026-01-21 23:34:14 +01:00
|
|
|
struct PITITBITEntry {
|
|
|
|
|
bit_offset: u32,
|
|
|
|
|
bit_size: u32,
|
|
|
|
|
_private_data_1: u32,
|
|
|
|
|
_private_data_2: u32,
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(BinRead)]
|
2026-01-21 23:34:14 +01:00
|
|
|
struct PITHeader {
|
|
|
|
|
#[br(count = 8)] pit_magic: Vec<u8>,
|
2025-10-16 21:04:49 +02:00
|
|
|
_unk: u32,
|
2026-01-21 23:34:14 +01:00
|
|
|
first_entry_offset: u32,
|
|
|
|
|
entry_size: u32,
|
|
|
|
|
entry_count: u32,
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
2026-01-21 23:34:14 +01:00
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
static PIT_MAGIC: [u8; 8] = [0xDC, 0xEA, 0x30, 0x85, 0xDC, 0xEA, 0x30, 0x85];
|
|
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
#[derive(BinRead)]
|
|
|
|
|
struct PITEntry {
|
|
|
|
|
#[br(count = 16)] name_bytes: Vec<u8>,
|
|
|
|
|
partition_id: u32,
|
|
|
|
|
_unknown: u32,
|
|
|
|
|
offset_on_nand: u32,
|
|
|
|
|
size_on_nand: u32,
|
|
|
|
|
#[br(count = 32)] _unused: Vec<u8>,
|
|
|
|
|
}
|
|
|
|
|
impl PITEntry {
|
2025-10-16 21:04:49 +02:00
|
|
|
fn name(&self) -> String {
|
|
|
|
|
common::string_from_bytes(&self.name_bytes)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
#[derive(BinRead)]
|
|
|
|
|
struct BITEntry {
|
|
|
|
|
partition_id: u32,
|
|
|
|
|
offset: u32,
|
|
|
|
|
size: u32,
|
|
|
|
|
offset_in_target_part: u32,
|
|
|
|
|
_unknown: u32,
|
|
|
|
|
}
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
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
|
2025-10-19 17:14:41 +02:00
|
|
|
|
2025-10-16 21:04:49 +02:00
|
|
|
fn find_bytes(data: &[u8], pattern: &[u8]) -> Option<usize> {
|
|
|
|
|
data.windows(pattern.len()).position(|window| window == pattern)
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
pub fn is_mtk_bdp_file(mut file: &File) -> Result<Option<MtkBdpContext>, Box<dyn std::error::Error>> {
|
|
|
|
|
let file_size = file.metadata()?.len();
|
2025-10-16 21:04:49 +02:00
|
|
|
let mut data = Vec::new();
|
|
|
|
|
|
2025-10-19 17:14:41 +02:00
|
|
|
// I wish there was a better way
|
|
|
|
|
let start_offset = file_size.saturating_sub(file_size / 20);
|
2025-10-16 21:04:49 +02:00
|
|
|
let _ = file.seek(SeekFrom::Start(start_offset));
|
|
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
file.read_to_end(&mut data)?;
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
if let Some(pos) = find_bytes(&data, &PITIT_MAGIC) {
|
2026-02-02 01:54:35 +01:00
|
|
|
Ok(Some(MtkBdpContext {pitit_offset: start_offset + pos as u64}))
|
2025-10-16 21:04:49 +02:00
|
|
|
} else {
|
2026-02-02 01:54:35 +01:00
|
|
|
Ok(None)
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
pub fn extract_mtk_bdp(mut file: &File, output_folder: &str, context: MtkBdpContext) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
let offset = context.pitit_offset;
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("\nReading PITIT at: {}", offset);
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-02-02 01:54:35 +01:00
|
|
|
file.seek(SeekFrom::Start(offset + 8))?;
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
let pitit_check = common::read_exact(&mut file, 8)?;
|
|
|
|
|
//{UPG_INFO}the upg bin is %d version(old is 0,new is 1)!\n
|
|
|
|
|
let pitit_ver = if pitit_check == PITIT_MAGIC {0} else {1};
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
let mut pit_offset: u64 = 0;
|
|
|
|
|
let mut bit_offset: u64 = 0;
|
2025-10-16 21:04:49 +02:00
|
|
|
loop {
|
2026-01-21 23:34:14 +01:00
|
|
|
let pitit_pit_entry: PITITPITEntry = file.read_le()?;
|
2026-02-02 01:54:35 +01:00
|
|
|
if pitit_pit_entry.nand_size == PITIT_END_MARKER {break};
|
2026-01-21 23:34:14 +01:00
|
|
|
if pitit_ver == 1 {
|
|
|
|
|
//old PITIT does not have BIT entry, because BIT appears directly after PITIT
|
|
|
|
|
let pitit_bit_entry: PITITBITEntry = file.read_le()?;
|
|
|
|
|
println!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}, BIT Offset: {}, BIT Size: {}",
|
|
|
|
|
pitit_pit_entry.nand_size, pitit_pit_entry.pit_offset, pitit_pit_entry.pit_size, pitit_bit_entry.bit_offset, pitit_bit_entry.bit_size);
|
|
|
|
|
if bit_offset == 0 { bit_offset = pitit_bit_entry.bit_offset as u64 } //use the first entry in PITIT
|
|
|
|
|
} else {
|
|
|
|
|
println!("PITIT Entry - NAND Size: {}, PIT Offset: {}, PIT Size: {}",
|
|
|
|
|
pitit_pit_entry.nand_size, pitit_pit_entry.pit_offset, pitit_pit_entry.pit_size);
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
2026-01-21 23:34:14 +01:00
|
|
|
if pit_offset == 0 { pit_offset = pitit_pit_entry.pit_offset as u64 } //use the first entry in PITIT
|
|
|
|
|
}
|
|
|
|
|
if pitit_ver == 0 && bit_offset == 0{
|
|
|
|
|
//in old upg bin, BIT appears directly after PITIT. so we can use the current file pos cuz we just ended reading PITIT
|
|
|
|
|
bit_offset = file.stream_position()?;
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
|
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("\nReading PIT at: {}", pit_offset); //PIT is the NAND partition table.
|
|
|
|
|
file.seek(SeekFrom::Start(pit_offset))?;
|
|
|
|
|
let mut pit_entries: Vec<PITEntry> = Vec::new();
|
|
|
|
|
let pit_header: PITHeader = file.read_le()?;
|
2026-02-02 01:54:35 +01:00
|
|
|
if pit_header.pit_magic != PIT_MAGIC {
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("Invalid PIT Magic!");
|
2025-10-16 21:04:49 +02:00
|
|
|
return Ok(());
|
|
|
|
|
}
|
2026-01-21 23:34:14 +01:00
|
|
|
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))?;
|
|
|
|
|
|
|
|
|
|
for i in 0..pit_header.entry_count {
|
|
|
|
|
let pit_entry: PITEntry = file.read_le()?;
|
|
|
|
|
println!("{}. ID: {:02x}, Name: {}, NAND Offset: {}, NAND Size: {}",
|
|
|
|
|
i + 1, pit_entry.partition_id, pit_entry.name(), pit_entry.offset_on_nand, pit_entry.size_on_nand);
|
|
|
|
|
pit_entries.push(pit_entry);
|
|
|
|
|
}
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("\nReading BIT at: {}", bit_offset); //BIT is the table of objects present in the update file.
|
|
|
|
|
file.seek(SeekFrom::Start(bit_offset))?;
|
|
|
|
|
let mut bit_entries: Vec<BITEntry> = Vec::new();
|
|
|
|
|
let bit_magic = common::read_exact(&mut file, 20)?;
|
2026-02-02 01:54:35 +01:00
|
|
|
if bit_magic != BIT_MAGIC {
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("Invalid BIT Magic!");
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
let mut bit_i = 0;
|
|
|
|
|
loop {
|
|
|
|
|
let bit_entry: BITEntry = file.read_le()?;
|
2026-02-02 01:54:35 +01:00
|
|
|
if bit_entry.partition_id == BIT_END_MARKER {break};
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("{}. ID: {:02x}, Offset: {}, Size: {}, Offset in part: {}",
|
|
|
|
|
bit_i + 1, bit_entry.partition_id, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
|
|
|
|
|
bit_entries.push(bit_entry);
|
|
|
|
|
bit_i += 1;
|
|
|
|
|
}
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
//extraction logic
|
|
|
|
|
for (i, bit_entry) in bit_entries.iter().enumerate() {
|
|
|
|
|
//find the name of partition using PIT by partition_id. if not found use the ID as placeholder
|
|
|
|
|
let mut name = format!("unknown_{:02x}", bit_entry.partition_id);
|
|
|
|
|
for pit_entry in &pit_entries {
|
|
|
|
|
if pit_entry.partition_id == bit_entry.partition_id {
|
|
|
|
|
name = pit_entry.name();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("\n({}/{}) - {}, Offset: {}, Size: {}, Offset in partition: {}",
|
|
|
|
|
i + 1, bit_entries.len(), name, bit_entry.offset, bit_entry.size, bit_entry.offset_in_target_part);
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
let data = common::read_file(&file, bit_entry.offset as u64, bit_entry.size as usize)?;
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
let output_path = Path::new(&output_folder).join(format!("{}.bin", name));
|
|
|
|
|
fs::create_dir_all(&output_folder)?;
|
|
|
|
|
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.write_all(&data)?;
|
2025-10-16 21:04:49 +02:00
|
|
|
|
2026-01-21 23:34:14 +01:00
|
|
|
println!("-- Saved file!");
|
2025-10-16 21:04:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
println!("\nExtraction finished!");
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|