1use binrw::{binread, BinRead, BinReaderExt};
2use bitflags::bitflags;
3use macintosh_utils::{chrono, decode_string, FinderFlags, Fork, FourCC};
4
5use crate::entry_reader::{CompressionMethod, EntrySpecification};
6
7#[derive(BinRead, Debug)]
8#[br(big)]
9pub struct ArchiveHeader {
10 pub magic: u8,
12 pub volume: u8,
14 pub cross_volume_magic: u16,
16 pub header_offset: u32,
18}
19
20impl ArchiveHeader {
21 pub const PACKED_SIZE: usize = 8;
23}
24
25#[binread]
26#[derive(Debug)]
27#[br(big)]
28pub struct CatalogHeader {
29 pub header_checksum: u32,
31 pub entry_count: u16,
33 #[br(map(macintosh_utils::string))]
35 pub comment: String,
36}
37
38#[derive(Debug, Clone)]
39pub enum Entry {
40 File(File),
41 Directory(Directory),
42}
43
44impl Entry {
45 pub fn name(&self) -> &str {
46 match self {
47 Entry::File(file_header) => &file_header.name,
48 Entry::Directory(dir_header) => &dir_header.name,
49 }
50 }
51
52 pub fn is_file(&self) -> bool {
53 matches!(self, Entry::File(_))
54 }
55
56 pub fn is_directory(&self) -> bool {
57 matches!(self, Entry::Directory(_))
58 }
59
60 pub fn as_file(&self) -> Option<&File> {
61 match self {
62 Entry::File(file) => Some(file),
63 Entry::Directory(_) => None,
64 }
65 }
66
67 pub fn as_directory(&self) -> Option<&Directory> {
68 match self {
69 Entry::File(_) => None,
70 Entry::Directory(directory) => Some(directory),
71 }
72 }
73
74 pub(crate) fn spec(&self, fork: Fork) -> EntrySpecification {
75 match self {
76 Entry::File(file) => file.spec(fork),
77 Entry::Directory(_) => EntrySpecification::default(),
78 }
79 }
80}
81
82impl BinRead for Entry {
83 type Args<'a> = ();
84
85 fn read_options<R: std::io::Read + std::io::Seek>(
86 reader: &mut R,
87 _: binrw::Endian,
88 _: Self::Args<'_>,
89 ) -> binrw::BinResult<Self> {
90 let name_len_and_type: u8 = reader.read_be()?;
91 let is_directory = (name_len_and_type & 0x80) != 0;
92 let name_len = name_len_and_type & !0x80;
93
94 if is_directory {
95 Ok(Entry::Directory(reader.read_be_args((name_len,))?))
96 } else {
97 Ok(Entry::File(reader.read_be_args((name_len,))?))
98 }
99 }
100}
101
102#[derive(BinRead, Debug, Clone)]
103#[br(import(name_len: u8), big)]
104pub struct File {
105 #[br(count(name_len), map(decode_string))]
106 pub name: String,
107 pub volume: u8,
109 pub offset: u32,
111 pub file_code: FourCC,
113 pub creator_code: FourCC,
115 #[br(map(macintosh_utils::date))]
117 pub created_at: chrono::DateTime<chrono::Utc>,
118 #[br(map(macintosh_utils::date))]
120 pub modified_at: chrono::DateTime<chrono::Utc>,
121 pub finder_flags: FinderFlags,
123 pub crc32: u32,
125 #[br(map(|v: u16| Flags::from_bits_retain(v)))]
127 pub flags: Flags,
128 pub rsrc_uncompressed_size: u32,
130 pub data_uncompressed_size: u32,
132 pub rsrc_compressed_size: u32,
134 pub data_compressed_size: u32,
136}
137
138impl File {
139 pub(crate) fn spec(&self, fork: Fork) -> EntrySpecification {
140 let method = if fork.is_data() {
141 if self.flags.contains(Flags::DATA_LZH_COMPRESSED) {
142 CompressionMethod::Lzh
143 } else {
144 CompressionMethod::Rle
145 }
146 } else if self.flags.contains(Flags::RSRC_LZH_COMPRESSED) {
147 CompressionMethod::Lzh
148 } else {
149 CompressionMethod::Rle
150 };
151
152 match fork {
153 Fork::Data => EntrySpecification {
154 method,
155 uncompressed_len: self.data_uncompressed_size as usize,
156 compressed_len: self.data_compressed_size as usize,
157 offset: self.offset as u64 + self.rsrc_compressed_size as u64,
158 },
159 Fork::Resource => EntrySpecification {
160 method,
161 uncompressed_len: self.rsrc_uncompressed_size as usize,
162 compressed_len: self.rsrc_compressed_size as usize,
163 offset: self.offset as u64,
164 },
165 }
166 }
167}
168
169bitflags! {
170 #[derive(Debug, Clone)]
171 pub struct Flags: u16 {
172 const ENCRYPTED = 1<<0;
173 const RSRC_LZH_COMPRESSED = 1<<1;
174 const DATA_LZH_COMPRESSED = 1<<2;
175 }
176}
177
178#[derive(BinRead, Debug, Clone)]
179#[br(import(name_len: u8), big)]
180pub struct Directory {
181 #[br(count(name_len), map(decode_string))]
182 pub name: String,
183 pub entries: u16,
184}