1use std::fmt;
2use std::io;
3
4use binrw::{BinRead, BinReaderExt};
5use fourcc::FourCC;
6use macintosh_utils::Fork;
7use v5::EntryFlags;
8
9pub mod v1;
11pub mod v5;
13
14#[derive(BinRead, Debug)]
15#[br(big)]
16pub enum ArchiveHeader {
17 V1(v1::ArchiveHeader),
18 V5(v5::ArchiveHeader),
19}
20
21impl ArchiveHeader {
22 pub fn entry_count(&self) -> usize {
23 match self {
24 ArchiveHeader::V1(header) => header.entry_count as usize,
25 ArchiveHeader::V5(header) => header.entry_count as usize,
26 }
27 }
28
29 pub fn version(&self) -> Version {
30 match self {
31 ArchiveHeader::V1(header) => header.version,
32 ArchiveHeader::V5(header) => header.version,
33 }
34 }
35
36 pub fn checksum_valid(&self) -> bool {
37 match self {
38 ArchiveHeader::V1(_) => true,
39 ArchiveHeader::V5(header) => header.checksum_valid,
40 }
41 }
42}
43
44#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Copy, Clone)]
45pub enum Algorithm {
46 None,
47 RLE,
48 LZW,
49 Huffman,
50 LZAH,
51 HuffmanFixed,
52 LZMW,
53 LzHuffman,
54 Installer,
55 Arsenic,
56
57 Unknown(u8),
58}
59
60impl BinRead for Algorithm {
61 type Args<'a> = ();
62
63 fn read_options<R: io::Read + io::Seek>(
64 reader: &mut R,
65 _endian: binrw::Endian,
66 _args: Self::Args<'_>,
67 ) -> binrw::BinResult<Self> {
68 Ok(match reader.read_be::<u8>()? & !(128) {
69 0u8 => Self::None,
70 1u8 => Self::RLE,
71 2u8 => Self::LZW,
72 3u8 => Self::Huffman,
73 5u8 => Self::LZAH,
74 6u8 => Self::HuffmanFixed,
75 8u8 => Self::LZMW,
76 13u8 => Self::LzHuffman,
77 14u8 => Self::Installer,
78 15u8 => Self::Arsenic,
79 val => Self::Unknown(val),
80 })
81 }
82}
83impl fmt::Display for Algorithm {
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Algorithm::RLE => "RLE".fmt(f),
87 Algorithm::None => "None".fmt(f),
88 Algorithm::LZW => "LZW".fmt(f),
89 Algorithm::Huffman => "Huff".fmt(f),
90 Algorithm::LZAH => "LZAH".fmt(f),
91 Algorithm::HuffmanFixed => "HuFi".fmt(f),
92 Algorithm::LZMW => "LZMW".fmt(f),
93 Algorithm::LzHuffman => "LzHu".fmt(f),
94 Algorithm::Installer => "Inst".fmt(f),
95 Algorithm::Arsenic => "Ars".fmt(f),
96 Algorithm::Unknown(m) => format!("Unknown {m}").fmt(f),
97 }
98 }
99}
100
101#[derive(Debug, Clone)]
102pub enum Entry {
103 File(File),
104 Directory(Directory),
105 EndOfDirectory,
106}
107
108#[derive(Debug, Clone)]
109pub enum File {
110 V1(v1::File),
111 V5(v5::File),
112}
113
114impl File {
115 pub fn name(&self) -> &str {
116 match self {
117 File::V1(file) => file.file_name.as_str(),
118 File::V5(file) => file.file_name.as_str(),
119 }
120 }
121
122 pub fn creator(&self) -> FourCC {
123 match self {
124 File::V1(file) => file.creator_code,
125 File::V5(file) => file.creator_code,
126 }
127 }
128
129 pub fn file_code(&self) -> FourCC {
130 match self {
131 File::V1(file) => file.file_code,
132 File::V5(file) => file.file_code,
133 }
134 }
135
136 pub fn comment(&self) -> &str {
137 match self {
138 File::V1(_) => "",
139 File::V5(file) => file.comment.as_str(),
140 }
141 }
142
143 pub fn uses_encryption(&self) -> bool {
144 match self {
145 File::V1(file) => file.uses_encryption(),
146 File::V5(file) => file.uses_encryption(),
147 }
148 }
149
150 #[inline]
151 pub fn has(&self, fork: Fork) -> bool {
152 match self {
153 File::V1(file) => file.compressed_size(fork) != 0,
154 File::V5(file) => file.compressed_size(fork) != 0,
155 }
156 }
157
158 #[inline]
159 pub fn uncompressed_size(&self, fork: Fork) -> usize {
160 match self {
161 File::V1(file) => file.uncompressed_size(fork),
162 File::V5(file) => file.uncompressed_size(fork),
163 }
164 }
165
166 #[inline]
167 pub fn compressed_size(&self, fork: Fork) -> usize {
168 match self {
169 File::V1(file) => file.compressed_size(fork),
170 File::V5(file) => file.compressed_size(fork),
171 }
172 }
173
174 #[inline]
175 pub fn compression_method(&self, fork: Fork) -> Algorithm {
176 match self {
177 File::V1(file) => file.compression_method(fork),
178 File::V5(file) => file.compression_method(fork),
179 }
180 }
181
182 #[inline]
183 pub fn encrypted(&self, fork: Fork) -> bool {
184 match self {
185 File::V1(file) => file.encrypted(fork),
186 File::V5(file) => file.encrypted(fork),
187 }
188 }
189
190 #[inline]
191 pub fn checksum(&self, fork: Fork) -> u16 {
192 match self {
193 File::V1(file) => file.checksum(fork),
194 File::V5(file) => file.checksum(fork),
195 }
196 }
197
198 #[inline]
199 pub fn offset(&self, fork: Fork) -> u64 {
200 match self {
201 File::V1(file) => file.offset(fork),
202 File::V5(file) => file.offset(fork),
203 }
204 }
205
206 pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
207 match self {
209 File::V1(file) => file.created_at,
210 File::V5(file) => file.creation_date,
211 }
212 }
213
214 pub fn modified_at(&self) -> chrono::DateTime<chrono::Utc> {
215 match self {
217 File::V1(file) => file.modified_at,
218 File::V5(file) => file.modification_date,
219 }
220 }
221
222 pub fn index(&self) -> usize {
223 match self {
224 File::V1(file) => file.index,
225 File::V5(file) => file.index,
226 }
227 }
228}
229
230#[derive(Debug, Clone)]
231pub enum Directory {
232 V1(v1::Directory),
233 V5(v5::Directory),
234}
235
236impl Directory {
237 #[inline]
238 pub fn name(&self) -> &str {
239 match self {
240 Directory::V1(dir) => &dir.file_name,
241 Directory::V5(dir) => dir.file_name(),
242 }
243 }
244
245 #[inline]
246 pub fn encrypted(&self, _: Fork) -> bool {
247 match self {
248 Directory::V1(_) => false,
249 Directory::V5(dir) => dir.flags.contains(EntryFlags::ENCRYPTED),
250 }
251 }
252
253 #[inline]
254 pub fn algorithm(&self, fork: Fork) -> Algorithm {
255 match self {
256 Directory::V1(dir) => dir.algorithm(fork),
257 Directory::V5(dir) => dir.algorithm(fork),
258 }
259 }
260
261 #[inline]
262 pub fn uncompressed_size(&self, fork: Fork) -> usize {
263 match self {
264 Directory::V1(dir) => dir.uncompressed_size(fork),
265 Directory::V5(dir) => dir.uncompressed_size(fork),
266 }
267 }
268
269 #[inline]
270 pub fn compressed_size(&self, fork: Fork) -> usize {
271 match self {
272 Directory::V1(dir) => dir.compressed_size(fork),
273 Directory::V5(dir) => dir.compressed_size(fork),
274 }
275 }
276
277 #[inline]
278 pub fn offset(&self, fork: Fork) -> u64 {
279 match self {
280 Directory::V1(dir) => dir.offset(fork),
281 Directory::V5(dir) => dir.offset(fork),
282 }
283 }
284
285 #[inline]
286 pub fn checksum(&self, fork: Fork) -> u16 {
287 match self {
288 Directory::V1(dir) => dir.checksum(fork),
289 Directory::V5(dir) => dir.checksum(fork),
290 }
291 }
292
293 #[inline]
294 pub fn has(&self, fork: Fork) -> bool {
295 match self {
296 Directory::V1(dir) => dir.compressed_size(fork) != 0,
297 Directory::V5(dir) => dir.compressed_size(fork) != 0,
298 }
299 }
300
301 pub fn comment(&self) -> &str {
302 match self {
303 Directory::V1(_) => "", Directory::V5(dir) => dir.comment(),
305 }
306 }
307
308 pub fn uses_encryption(&self) -> bool {
309 match self {
310 Directory::V1(dir) => dir.uses_encryption(),
311 Directory::V5(dir) => dir.uses_encryption(),
312 }
313 }
314}
315
316#[derive(BinRead, Debug, PartialEq, PartialOrd, Copy, Clone)]
318#[br(big)]
319pub enum Version {
320 #[br(magic(1u8))]
322 Early,
323 #[br(magic(2u8))]
325 Later,
326 #[br(magic(5u8))]
328 Five,
329 Unknown(u8),
331}
332
333impl Version {
334 pub fn short_str(&self) -> &'static str {
335 match self {
336 Version::Early => "1",
337 Version::Later => "2",
338 Version::Five => "5",
339 Version::Unknown(_) => "x",
340 }
341 }
342}
343
344impl fmt::Display for Version {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 match self {
347 Version::Early => f.write_str("1.5.x and earlier"),
348 Version::Later => f.write_str("1.6 to 4.5"),
349 Version::Five => f.write_str("5.x"),
350 Version::Unknown(v) => f.write_fmt(format_args!("Unknown {v}")),
351 }
352 }
353}