use std::{
collections::HashMap,
ffi::CString,
io::{Read, Seek, SeekFrom, Write},
};
use deku::prelude::*;
use log::{info, trace};
pub const VTBL_VOLID: u32 = 0x7fffefff;
pub const UBI_EC_HDR_MAGIC: &[u8] = b"UBI#";
pub const UBI_VID_HDR_MAGIC: &[u8] = b"UBI!";
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Clone, Hash)]
#[deku(endian = "big")]
pub struct EcHdr {
#[deku(assert_eq = "UBI_EC_HDR_MAGIC")]
pub magic: [u8; 4],
pub version: u8,
pub padding1: [u8; 3],
pub ec: u64,
pub vid_hdr_offset: u32,
pub data_offset: u32,
pub image_seq: u32,
pub padding2: [u8; 32],
pub hdr_crc: u32,
}
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq)]
#[deku(endian = "big")]
pub struct VidHdr {
#[deku(assert_eq = "UBI_VID_HDR_MAGIC")]
pub magic: [u8; 4],
#[deku(assert_eq = "1")]
pub version: u8,
pub vol_type: VolType,
pub copy_flag: u8,
pub compat: u8,
pub vol_id: u32,
pub lnum: u32,
pub padding1: [u8; 4],
pub data_size: u32,
pub used_ebs: u32,
pub data_pad: u32,
pub data_crc: u32,
pub padding2: [u8; 4],
pub sqnum: u64,
pub padding3: [u8; 12],
pub hdr_crc: u32,
}
const UBI_VOL_NAME_MAX: usize = 127;
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(ctx = "endian: deku::ctx::Endian", endian = "endian")]
#[deku(id_type = "u8")]
pub enum VolType {
Dynamic = 1,
Static = 2,
}
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct VtblRecord {
pub reserved_pebs: u32,
pub alignment: u32,
pub data_pad: u32,
pub vol_type: VolType,
pub upd_marker: u8,
#[deku(assert = "*name_len < UBI_VOL_NAME_MAX as u16")]
pub name_len: u16,
pub name: [u8; UBI_VOL_NAME_MAX + 1],
pub flags: u8,
pub padding: [u8; 23],
pub crc: u32,
}
impl VtblRecord {
pub const SIZE: usize = 4 * 4 + UBI_VOL_NAME_MAX + 1 + 24 + 4;
pub fn name(&self) -> Option<CString> {
if self.name_len == 0 {
None
} else {
let val = CString::from_vec_with_nul(self.name[..self.name_len as usize + 1].to_vec())
.unwrap();
Some(val)
}
}
}
const UBI_FM_MAX_BLOCKS: usize = 32;
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct FmSb {
pub magic: u32,
pub version: u8,
pub padding1: [u8; 3],
pub data_crc: u32,
pub used_blocks: u32,
pub block_loc: [u8; UBI_FM_MAX_BLOCKS],
pub block_ec: [u8; UBI_FM_MAX_BLOCKS],
pub sqnum: u64,
pub padding2: [u8; 32],
}
const UBI_FM_MAX_POOL_SIZE: usize = 256;
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct FmScanPool {
pub magic: u32,
pub size: u16,
pub pebs: [u32; UBI_FM_MAX_POOL_SIZE],
pub padding: [u32; 4],
}
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct FmEc {
pub magic: u32,
pub ec: u32,
}
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct FmVolHdr {
pub magic: u32,
pub vol_id: u32,
pub padding1: [u8; 3],
pub data_pad: u32,
pub used_ebs: u32,
pub last_eb_bytes: u32,
pub padding2: [u8; 8],
}
#[derive(Debug, DekuRead, DekuWrite, PartialEq, Eq, Hash)]
#[deku(endian = "big")]
pub struct FmEba {
pub magic: u32,
pub reserved_pebs: u32,
#[deku(count = "*reserved_pebs")]
pub pnum: Vec<u8>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct Image {
pub vtable: Vec<(u32, VtblRecord)>,
pub map: HashMap<u32, Vec<u64>>,
}
impl Image {
pub fn read<RS: Read + Seek>(reader: &mut RS, file_len: u64, block_size: u64) -> Self {
let maxlebs = file_len / block_size;
let mut map = HashMap::<u32, Vec<u64>>::new();
for lnum in 0..maxlebs {
reader.seek(SeekFrom::Start(lnum * block_size)).unwrap();
let ec = EcHdr::from_reader((reader, 0)).unwrap().1;
reader.seek(SeekFrom::Start(lnum * block_size + u64::from(ec.vid_hdr_offset))).unwrap();
let vid = VidHdr::from_reader((reader, 0)).unwrap().1;
if let Some(x) = map.get_mut(&vid.vol_id) {
x.push(lnum);
} else {
map.insert(vid.vol_id, vec![lnum]);
}
}
let lnum = map[&VTBL_VOLID][0]; let start_of_volume = lnum * block_size;
reader.seek(SeekFrom::Start(start_of_volume)).unwrap();
let ec = EcHdr::from_reader((reader, 0)).unwrap().1;
trace!("ec: {ec:02x?}");
reader.seek(SeekFrom::Start(lnum * block_size + u64::from(ec.vid_hdr_offset))).unwrap();
let vid = VidHdr::from_reader((reader, 0)).unwrap().1;
assert_eq!(vid.lnum as u64, lnum);
trace!("vid: {vid:02x?}");
let mut vtable = vec![];
reader.seek(SeekFrom::Start(start_of_volume + ec.data_offset as u64)).unwrap();
let end_of_volume = start_of_volume + block_size;
let mut n = 0;
while reader.stream_position().unwrap() < end_of_volume - VtblRecord::SIZE as u64 {
let save_before_position = reader.stream_position().unwrap();
match VtblRecord::from_reader((reader, 0)) {
Ok((_, vtbl)) => {
trace!("vtbl: {vtbl:02x?}");
vtable.push((n, vtbl));
}
Err(_) => {
reader
.seek(SeekFrom::Start(save_before_position + VtblRecord::SIZE as u64))
.unwrap();
}
}
n += 1;
}
Self { vtable, map }
}
pub fn read_volume<RS: Read + Seek, W: Write>(
&self,
reader: &mut RS,
writer: &mut W,
block_size: u64,
volume: &(u32, VtblRecord),
) {
let (volume_id, volume) = volume;
info!("Extracting volume: {:?}", volume.name().unwrap());
let extract_map = &self.map[volume_id];
for lnum in extract_map {
let ec_seek = SeekFrom::Start(lnum * block_size);
reader.seek(ec_seek).unwrap();
let ec = EcHdr::from_reader((reader, 0)).unwrap().1;
let vid_seek = lnum * block_size + u64::from(ec.vid_hdr_offset);
reader.seek(SeekFrom::Start(vid_seek)).unwrap();
let vid = VidHdr::from_reader((reader, 0)).unwrap().1;
let vol_seek = lnum * block_size + ec.data_offset as u64;
reader.seek(SeekFrom::Start(vol_seek)).unwrap();
match vid.vol_type {
VolType::Static => {
let mut buf = vec![0; vid.data_size as usize];
reader.read_exact(&mut buf).unwrap();
writer.write_all(&buf).unwrap();
}
VolType::Dynamic => {
let mut buf = vec![0; block_size as usize - ec.data_offset as usize];
reader.read_exact(&mut buf).unwrap();
writer.write_all(&buf).unwrap();
}
}
}
}
}