use binrw::{binread, BinRead, BinReaderExt};
use bitflags::bitflags;
use macintosh_utils::{chrono, decode_string, FinderFlags, Fork, FourCC};
use crate::entry_reader::{CompressionMethod, StreamDescription};
#[derive(BinRead, Debug)]
#[br(big)]
pub struct ArchiveHeader {
pub magic: u8,
pub volume: u8,
pub cross_volume_magic: u16,
pub header_offset: u32,
}
impl ArchiveHeader {
pub const PACKED_SIZE: usize = 8;
}
#[binread]
#[derive(Debug)]
#[br(big)]
pub struct CatalogHeader {
pub header_checksum: u32,
pub entry_count: u16,
#[br(map(macintosh_utils::string))]
pub comment: String,
}
#[derive(Debug, Clone)]
pub enum Entry {
File(File),
Directory(Directory),
}
impl Entry {
pub fn name(&self) -> &str {
match self {
Entry::File(file_header) => &file_header.name,
Entry::Directory(dir_header) => &dir_header.name,
}
}
pub fn is_file(&self) -> bool {
matches!(self, Entry::File(_))
}
pub fn is_directory(&self) -> bool {
matches!(self, Entry::Directory(_))
}
pub fn as_file(&self) -> Option<&File> {
match self {
Entry::File(file) => Some(file),
Entry::Directory(_) => None,
}
}
pub fn as_directory(&self) -> Option<&Directory> {
match self {
Entry::File(_) => None,
Entry::Directory(directory) => Some(directory),
}
}
pub(crate) fn spec(&self, fork: Fork) -> StreamDescription {
match self {
Entry::File(file) => file.spec(fork),
Entry::Directory(_) => StreamDescription::default(),
}
}
}
impl BinRead for Entry {
type Args<'a> = ();
fn read_options<R: std::io::Read + std::io::Seek>(
reader: &mut R,
_: binrw::Endian,
_: Self::Args<'_>,
) -> binrw::BinResult<Self> {
let name_len_and_type: u8 = reader.read_be()?;
let is_directory = (name_len_and_type & 0x80) != 0;
let name_len = name_len_and_type & !0x80;
if is_directory {
Ok(Entry::Directory(reader.read_be_args((name_len,))?))
} else {
Ok(Entry::File(reader.read_be_args((name_len,))?))
}
}
}
#[derive(BinRead, Debug, Clone)]
#[br(import(name_len: u8), big)]
pub struct File {
#[br(count(name_len), map(decode_string))]
pub name: String,
pub volume: u8,
pub offset: u32,
pub file_code: FourCC,
pub creator_code: FourCC,
#[br(map(macintosh_utils::date))]
pub created_at: chrono::DateTime<chrono::Utc>,
#[br(map(macintosh_utils::date))]
pub modified_at: chrono::DateTime<chrono::Utc>,
pub finder_flags: FinderFlags,
pub crc32: u32,
#[br(map(|v: u16| Flags::from_bits_retain(v)))]
pub flags: Flags,
pub rsrc_uncompressed_size: u32,
pub data_uncompressed_size: u32,
pub rsrc_compressed_size: u32,
pub data_compressed_size: u32,
}
impl File {
pub(crate) fn spec(&self, fork: Fork) -> StreamDescription {
let method = if fork.is_data() {
if self.flags.contains(Flags::DATA_LZH_COMPRESSED) {
CompressionMethod::Lzh
} else {
CompressionMethod::Rle
}
} else if self.flags.contains(Flags::RSRC_LZH_COMPRESSED) {
CompressionMethod::Lzh
} else {
CompressionMethod::Rle
};
match fork {
Fork::Data => StreamDescription {
method,
uncompressed_len: self.data_uncompressed_size as usize,
_compressed_len: self.data_compressed_size as usize,
offset: self.offset as u64 + self.rsrc_compressed_size as u64,
},
Fork::Resource => StreamDescription {
method,
uncompressed_len: self.rsrc_uncompressed_size as usize,
_compressed_len: self.rsrc_compressed_size as usize,
offset: self.offset as u64,
},
}
}
}
bitflags! {
#[derive(Debug, Clone)]
pub struct Flags: u16 {
const ENCRYPTED = 1<<0;
const RSRC_LZH_COMPRESSED = 1<<1;
const DATA_LZH_COMPRESSED = 1<<2;
}
}
#[derive(BinRead, Debug, Clone)]
#[br(import(name_len: u8), big)]
pub struct Directory {
#[br(count(name_len), map(decode_string))]
pub name: String,
pub child_count: u16,
}