macbinary-rs 0.2.0

Transparent access to MacBinary-encoded files
Documentation
use binrw::binread;
use bitflags::bitflags;
use macintosh_utils::{
    FourCC, Point,
    chrono::{DateTime, Utc},
    decode_string,
};
use std::cmp::min;

bitflags! {
    #[derive(Debug, Clone)]
    pub struct Flags: u8 {
        const LOCKED = 1<<0;
    }
}

/// MacBinary file header
#[binread]
#[derive(Debug)]
#[br(big)]
pub struct Header {
    /// File format version
    pub version: u8,
    #[br(temp,assert(name_len > 0 && name_len < 63))]
    name_len: u8,
    /// Original file name
    #[br(map(|r: [u8; 63]| decode_string(r[0..min(name_len as usize, 63)].to_vec())))]
    pub name: String,
    /// Macintosh file type code
    pub type_code: FourCC,
    /// Macintosh creator code
    pub creator_code: FourCC,
    /// Upper 8-bit of the finder flags
    pub finder_flags_upper: u8,
    #[br(temp, assert(zero==0))]
    zero: u8,
    /// Position in parent window
    pub position: Point,
    /// Id of parent window
    pub window_id: u16,
    #[br(map(Flags::from_bits_retain))]
    pub flags: Flags,
    #[br(temp, assert(zero_again==0))]
    zero_again: u8,
    /// Number of bytes in data fork
    pub data_fork_len: u32,
    /// Number of bytes in resource fork
    pub resource_fork_len: u32,
    #[br(map(macintosh_utils::date))]
    pub created_at: DateTime<Utc>,
    #[br(map(macintosh_utils::date))]
    pub modified_at: DateTime<Utc>,

    /// Length of file comment
    pub comment_len: u16,
    /// Lower 8bits of finder flags
    pub finder_flags_lower: u8,
    //#[br(temp, assert(magic == fourcc!("mBIN")))]
    pub magic: FourCC,
    pub file_name_script: u8,
    pub extended_finder_flags: u8,
    #[br(temp)]
    _reserved_2: [u8; 8],
    pub unpacked_total_len: u32,
    pub extended_header_len: u16,
    pub uploader_version: u8,
    pub downloader_min_version: u8,
    /// xmodem crc 16
    pub checksum: u16,

    #[br(temp)]
    _reserved_3: u16,
}

impl Header {
    pub const FIXED_SIZE: usize = 128;

    fn extended_header_location(&self) -> u64 {
        Header::FIXED_SIZE as u64
    }

    pub fn data_fork_location(&self) -> u64 {
        self.extended_header_location() + align_128(self.extended_header_len as u64)
    }

    pub fn resource_fork_location(&self) -> u64 {
        self.data_fork_location() + align_128(self.data_fork_len as u64)
    }

    pub fn file_comment_location(&self) -> u64 {
        self.resource_fork_location() + align_128(self.resource_fork_len as u64)
    }
}

fn align_128(input: u64) -> u64 {
    if (0x80 - 1) & input != 0 {
        (input + 0x80) & !(0x80 - 1)
    } else {
        input
    }
}

#[cfg(test)]
mod test {
    use super::align_128;

    #[test]
    fn align_int() {
        assert_eq!(align_128(0), 0);
        assert_eq!(align_128(1), 128);
        assert_eq!(align_128(127), 128);
        assert_eq!(align_128(128), 128);
        assert_eq!(align_128(129), 256);
    }
}