use byteorder::{BigEndian, ReadBytesExt};
use std::io::{Read, Seek, SeekFrom};
use crate::error::{DppError, Result};
pub const KOLY_MAGIC: &[u8; 4] = b"koly";
pub const MISH_MAGIC: &[u8; 4] = b"mish";
pub const KOLY_SIZE: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum BlockType {
ZeroFill = 0x00000000,
Raw = 0x00000001,
Ignore = 0x00000002,
Adc = 0x80000004,
Zlib = 0x80000005,
Bzip2 = 0x80000006,
Lzfse = 0x80000007,
Xz = 0x80000008,
Comment = 0x7FFFFFFE,
End = 0xFFFFFFFF,
}
impl TryFrom<u32> for BlockType {
type Error = DppError;
fn try_from(value: u32) -> Result<Self> {
match value {
0x00000000 => Ok(BlockType::ZeroFill),
0x00000001 => Ok(BlockType::Raw),
0x00000002 => Ok(BlockType::Ignore),
0x80000004 => Ok(BlockType::Adc),
0x80000005 => Ok(BlockType::Zlib),
0x80000006 => Ok(BlockType::Bzip2),
0x80000007 => Ok(BlockType::Lzfse),
0x80000008 => Ok(BlockType::Xz),
0x7FFFFFFE => Ok(BlockType::Comment),
0xFFFFFFFF => Ok(BlockType::End),
_ => Err(DppError::UnsupportedCompression(value)),
}
}
}
#[derive(Debug, Clone)]
pub struct KolyHeader {
pub magic: [u8; 4],
pub version: u32,
pub header_size: u32,
pub flags: u32,
pub running_data_fork_offset: u64,
pub data_fork_offset: u64,
pub data_fork_length: u64,
pub rsrc_fork_offset: u64,
pub rsrc_fork_length: u64,
pub segment_number: u32,
pub segment_count: u32,
pub segment_id: [u8; 16],
pub data_checksum_type: u32,
pub data_checksum_size: u32,
pub data_checksum: [u8; 128],
pub plist_offset: u64,
pub plist_length: u64,
pub reserved: [u8; 64],
pub master_checksum_type: u32,
pub master_checksum_size: u32,
pub master_checksum: [u8; 128],
pub image_variant: u32,
pub sector_count: u64,
}
impl KolyHeader {
pub fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
reader.seek(SeekFrom::End(-(KOLY_SIZE as i64)))?;
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
if &magic != KOLY_MAGIC {
return Err(DppError::InvalidMagic);
}
let version = reader.read_u32::<BigEndian>()?;
let header_size = reader.read_u32::<BigEndian>()?;
let flags = reader.read_u32::<BigEndian>()?;
let running_data_fork_offset = reader.read_u64::<BigEndian>()?;
let data_fork_offset = reader.read_u64::<BigEndian>()?;
let data_fork_length = reader.read_u64::<BigEndian>()?;
let rsrc_fork_offset = reader.read_u64::<BigEndian>()?;
let rsrc_fork_length = reader.read_u64::<BigEndian>()?;
let segment_number = reader.read_u32::<BigEndian>()?;
let segment_count = reader.read_u32::<BigEndian>()?;
let mut segment_id = [0u8; 16];
reader.read_exact(&mut segment_id)?;
let data_checksum_type = reader.read_u32::<BigEndian>()?;
let data_checksum_size = reader.read_u32::<BigEndian>()?;
let mut data_checksum = [0u8; 128];
reader.read_exact(&mut data_checksum)?;
let plist_offset = reader.read_u64::<BigEndian>()?;
let plist_length = reader.read_u64::<BigEndian>()?;
let mut reserved = [0u8; 64];
reader.read_exact(&mut reserved)?;
let master_checksum_type = reader.read_u32::<BigEndian>()?;
let master_checksum_size = reader.read_u32::<BigEndian>()?;
let mut master_checksum = [0u8; 128];
reader.read_exact(&mut master_checksum)?;
let image_variant = reader.read_u32::<BigEndian>()?;
let sector_count = reader.read_u64::<BigEndian>()?;
Ok(KolyHeader {
magic,
version,
header_size,
flags,
running_data_fork_offset,
data_fork_offset,
data_fork_length,
rsrc_fork_offset,
rsrc_fork_length,
segment_number,
segment_count,
segment_id,
data_checksum_type,
data_checksum_size,
data_checksum,
plist_offset,
plist_length,
reserved,
master_checksum_type,
master_checksum_size,
master_checksum,
image_variant,
sector_count,
})
}
pub fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<()> {
use byteorder::WriteBytesExt;
writer.write_all(&self.magic)?;
writer.write_u32::<BigEndian>(self.version)?;
writer.write_u32::<BigEndian>(self.header_size)?;
writer.write_u32::<BigEndian>(self.flags)?;
writer.write_u64::<BigEndian>(self.running_data_fork_offset)?;
writer.write_u64::<BigEndian>(self.data_fork_offset)?;
writer.write_u64::<BigEndian>(self.data_fork_length)?;
writer.write_u64::<BigEndian>(self.rsrc_fork_offset)?;
writer.write_u64::<BigEndian>(self.rsrc_fork_length)?;
writer.write_u32::<BigEndian>(self.segment_number)?;
writer.write_u32::<BigEndian>(self.segment_count)?;
writer.write_all(&self.segment_id)?;
writer.write_u32::<BigEndian>(self.data_checksum_type)?;
writer.write_u32::<BigEndian>(self.data_checksum_size)?;
writer.write_all(&self.data_checksum)?;
writer.write_u64::<BigEndian>(self.plist_offset)?;
writer.write_u64::<BigEndian>(self.plist_length)?;
writer.write_all(&self.reserved)?;
writer.write_u32::<BigEndian>(self.master_checksum_type)?;
writer.write_u32::<BigEndian>(self.master_checksum_size)?;
writer.write_all(&self.master_checksum)?;
writer.write_u32::<BigEndian>(self.image_variant)?;
writer.write_u64::<BigEndian>(self.sector_count)?;
writer.write_all(&[0u8; 68])?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct BlockRun {
pub block_type: BlockType,
pub comment: u32,
pub sector_number: u64,
pub sector_count: u64,
pub compressed_offset: u64,
pub compressed_length: u64,
}
impl BlockRun {
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 40 {
return Err(DppError::InvalidBlockMap("block run too short".into()));
}
let mut cursor = std::io::Cursor::new(data);
let block_type_raw = cursor.read_u32::<BigEndian>()?;
let block_type = BlockType::try_from(block_type_raw)?;
let comment = cursor.read_u32::<BigEndian>()?;
let sector_number = cursor.read_u64::<BigEndian>()?;
let sector_count = cursor.read_u64::<BigEndian>()?;
let compressed_offset = cursor.read_u64::<BigEndian>()?;
let compressed_length = cursor.read_u64::<BigEndian>()?;
Ok(BlockRun {
block_type,
comment,
sector_number,
sector_count,
compressed_offset,
compressed_length,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
use byteorder::WriteBytesExt;
let mut buf = Vec::with_capacity(40);
buf.write_u32::<BigEndian>(self.block_type as u32).unwrap();
buf.write_u32::<BigEndian>(self.comment).unwrap();
buf.write_u64::<BigEndian>(self.sector_number).unwrap();
buf.write_u64::<BigEndian>(self.sector_count).unwrap();
buf.write_u64::<BigEndian>(self.compressed_offset).unwrap();
buf.write_u64::<BigEndian>(self.compressed_length).unwrap();
buf
}
}
#[derive(Debug, Clone)]
pub struct MishHeader {
pub magic: [u8; 4],
pub version: u32,
pub first_sector: u64,
pub sector_count: u64,
pub data_offset: u64,
pub buffers_needed: u32,
pub block_descriptor_count: u32,
pub reserved: [u8; 24],
pub checksum_type: u32,
pub checksum_size: u32,
pub checksum: [u8; 128],
pub actual_block_count: u32,
pub block_runs: Vec<BlockRun>,
}
impl MishHeader {
pub fn from_bytes(data: &[u8]) -> Result<Self> {
if data.len() < 204 {
return Err(DppError::InvalidBlockMap("mish data too short".into()));
}
let mut cursor = std::io::Cursor::new(data);
let mut magic = [0u8; 4];
cursor.read_exact(&mut magic)?;
if &magic != MISH_MAGIC {
return Err(DppError::InvalidBlockMap(format!(
"invalid mish magic: {:?}",
magic
)));
}
let version = cursor.read_u32::<BigEndian>()?;
let first_sector = cursor.read_u64::<BigEndian>()?;
let sector_count = cursor.read_u64::<BigEndian>()?;
let data_offset = cursor.read_u64::<BigEndian>()?;
let buffers_needed = cursor.read_u32::<BigEndian>()?;
let block_descriptor_count = cursor.read_u32::<BigEndian>()?;
let mut reserved = [0u8; 24];
cursor.read_exact(&mut reserved)?;
let checksum_type = cursor.read_u32::<BigEndian>()?;
let checksum_size = cursor.read_u32::<BigEndian>()?;
let mut checksum = [0u8; 128];
cursor.read_exact(&mut checksum)?;
let actual_block_count = cursor.read_u32::<BigEndian>()?;
let mut block_runs = Vec::with_capacity(actual_block_count as usize);
for _ in 0..actual_block_count {
let mut run_data = [0u8; 40];
cursor.read_exact(&mut run_data)?;
block_runs.push(BlockRun::from_bytes(&run_data)?);
}
Ok(MishHeader {
magic,
version,
first_sector,
sector_count,
data_offset,
buffers_needed,
block_descriptor_count,
reserved,
checksum_type,
checksum_size,
checksum,
actual_block_count,
block_runs,
})
}
pub fn uncompressed_size(&self) -> u64 {
self.sector_count * 512
}
pub fn compressed_size(&self) -> u64 {
self.block_runs.iter().map(|r| r.compressed_length).sum()
}
}
#[derive(Debug, Clone)]
pub struct PartitionEntry {
pub name: String,
pub id: i32,
pub attributes: u32,
pub block_map: MishHeader,
}
pub fn is_dmg<R: Read + Seek>(reader: &mut R) -> bool {
let pos = reader.stream_position().ok();
let result = (|| {
reader.seek(SeekFrom::End(-(KOLY_SIZE as i64)))?;
let mut magic = [0u8; 4];
reader.read_exact(&mut magic)?;
Ok::<_, std::io::Error>(magic == *KOLY_MAGIC)
})();
if let Some(p) = pos {
let _ = reader.seek(SeekFrom::Start(p));
}
result.unwrap_or(false)
}