Skip to main content

sit/
archive.rs

1use std::{
2    fmt::Debug,
3    fs,
4    io::{self},
5    path::{self},
6    rc::Rc,
7};
8
9use binrw::BinReaderExt;
10use macintosh_utils::Fork;
11
12use crate::{
13    Entry, Error, VerifyingEntryReader,
14    structs::{
15        Algorithm, ArchiveHeader, File, Version, v1,
16        v5::{self, EntryBinReadArgs},
17    },
18    verify::VerifyingIterator,
19};
20
21pub struct EntryReader<'a, T: io::Read + io::Seek>(crate::algos::EntryReader<'a, T>, u16);
22
23impl<'a, T: io::Read + io::Seek> EntryReader<'a, T> {
24    #[inline]
25    pub(crate) fn try_from(
26        reader: &'a mut Rc<T>,
27        algo: Algorithm,
28        uncompressed_size: u64,
29        compressed_size: usize,
30        offset: u64,
31        checksum: u16,
32    ) -> Result<Self, Error> {
33        Ok(Self(
34            crate::algos::EntryReader::try_from(
35                reader,
36                algo,
37                uncompressed_size,
38                compressed_size,
39                offset,
40            )?,
41            checksum,
42        ))
43    }
44
45    pub fn verifying(self) -> VerifyingEntryReader<'a, T> {
46        self.0.verifying(self.1)
47    }
48
49    pub fn verify(self) -> Result<(), Error> {
50        // Arsenic compression uses 32-bit checksums interleaved with the compressed blocks
51        // so it get's special treatment
52        let is_arsenic = matches!(self.0, crate::algos::EntryReader::Arsenic { .. });
53
54        VerifyingEntryReader::new(self.0, self.1, is_arsenic).slurp()
55    }
56}
57
58impl<'a, T: io::Read + io::Seek> io::Read for EntryReader<'a, T> {
59    #[inline]
60    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
61        self.0.read(buf)
62    }
63}
64
65impl<'a, T: io::Read + io::Seek> io::Seek for EntryReader<'a, T> {
66    #[inline]
67    fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
68        self.0.seek(pos)
69    }
70
71    #[inline]
72    fn stream_len(&mut self) -> io::Result<u64> {
73        self.0.stream_len()
74    }
75
76    #[inline]
77    fn stream_position(&mut self) -> io::Result<u64> {
78        self.0.stream_position()
79    }
80}
81
82#[derive(Debug)]
83pub struct Archive<R> {
84    header_location: u64,
85    header: ArchiveHeader,
86    inner: Rc<R>,
87}
88
89impl Archive<fs::File> {
90    pub fn open_path<P: AsRef<path::Path>>(p: P) -> Result<Self, Error> {
91        Archive::try_from(fs::File::open(p.as_ref())?)
92    }
93}
94
95impl<R: io::Seek + io::Read> Archive<R> {
96    pub fn try_from(mut inner: R) -> Result<Self, Error> {
97        let header_location = inner.stream_position()?;
98
99        match inner.read_be() {
100            Ok(header) => Ok(Self {
101                inner: Rc::new(inner),
102                header_location,
103                header,
104            }),
105            Err(binrw::Error::AssertFail { .. }) => Err(Error::InvalidFile(
106                crate::error::InvalidFileReason::InvalidHeader,
107            )),
108            Err(e) => Err(e.into()),
109        }
110    }
111
112    pub fn version(&self) -> Version {
113        self.header.version()
114    }
115
116    pub fn header(&self) -> &ArchiveHeader {
117        &self.header
118    }
119
120    /// Verify the archive's integrity by validating all checksums
121    pub fn verify(&mut self) -> Result<(), Error> {
122        if !self.header.checksum_valid() {
123            return Err(Error::ChecksumMismatch(
124                crate::error::ChecksumLocation::ArchiveHeader,
125            ));
126        }
127
128        self.iter().try_for_each(|e| self.verify_entry(e))
129    }
130
131    /// Open an archive entry for reading
132    pub fn open_fork<'a, E: ReadableEntry>(
133        &'a mut self,
134        entry: &E,
135        fork: Fork,
136    ) -> Result<EntryReader<'a, R>, Error> {
137        let algo = entry.algorithm(fork);
138        let compressed_size = entry.compressed_size(fork);
139        let uncompressed_size = entry.uncompressed_size(fork);
140        let offset = entry.offset(fork);
141        let checksum = entry.checksum(fork);
142
143        if entry.encrypted(fork) {
144            return Err(Error::UnsupportedFeature(
145                crate::error::UnsupportedFeature::Encryption,
146            ));
147        }
148
149        EntryReader::try_from(
150            &mut self.inner,
151            algo,
152            uncompressed_size as u64,
153            compressed_size,
154            offset,
155            checksum,
156        )
157    }
158
159    /// Iterate through all archive entries
160    pub fn iter(&self) -> EntryIterator<R> {
161        // SAFETY: Archives can only be iterated once, and opening an entry locks the archive until
162        // the handle is released. That ensures we don't modify the underlying reader concurrently
163        self.ensure_not_iterating();
164
165        let catalog_offset = match &self.header {
166            ArchiveHeader::V1(hdr) => self.header_location + hdr.first_entry_offset(),
167            ArchiveHeader::V5(hdr) => self.header_location + hdr.first_entry_offset(),
168        };
169
170        EntryIterator::new(
171            self.inner.clone(),
172            self.header.entry_count(),
173            catalog_offset,
174            matches!(self.header, ArchiveHeader::V1(_)),
175        )
176    }
177
178    /// Returns the underlying reader
179    pub fn into_inner(self) -> R {
180        self.ensure_not_iterating();
181
182        // SAFETY: Since no one is iterating, and any open entry would borrow the archive mutably
183        // this is safe
184        let Archive { inner, .. } = self;
185        unsafe { Rc::try_unwrap(inner).unwrap_unchecked() }
186    }
187
188    pub fn reset(&mut self) -> Result<(), Error> {
189        self.ensure_not_iterating();
190
191        // SAFETY: Since no one is iterating, and any open entry would borrow the archive mutably,
192        // this is safe
193        unsafe {
194            Rc::get_mut_unchecked(&mut self.inner)
195                .seek(io::SeekFrom::Start(self.header_location))?;
196        }
197
198        Ok(())
199    }
200
201    fn ensure_not_iterating(&self) {
202        if Rc::strong_count(&self.inner) != 1 || Rc::weak_count(&self.inner) != 0 {
203            panic!("Can not modify archive while an iterator is runnning")
204        }
205    }
206
207    /// Verify integrity of an entry
208    pub fn verify_entry(&mut self, e: Entry) -> Result<(), Error> {
209        if let Entry::Directory(crate::structs::Directory::V5(dir)) = &e
210            && dir.marks_end()
211        {
212            return Ok(());
213        }
214
215        if !e.is_file() {
216            return Ok(());
217        }
218
219        if e.has(Fork::Resource) {
220            self.open_fork(&e, Fork::Resource)?.verifying().slurp()?;
221        }
222
223        if e.has(Fork::Data) {
224            self.open_fork(&e, Fork::Data)?.verifying().slurp()?;
225        }
226
227        Ok(())
228    }
229}
230
231/// Trait implemented by archive entries to create a decompression stream
232pub trait ReadableEntry {
233    /// Algorithm used to compress the entry's data or resource fork
234    fn algorithm(&self, fork: Fork) -> Algorithm;
235    /// Amount of bytes occupied by the specified fork
236    fn compressed_size(&self, fork: Fork) -> usize;
237    /// Size of the fork in bytes after decompression
238    fn uncompressed_size(&self, fork: Fork) -> usize;
239    fn encrypted(&self, fork: Fork) -> bool;
240    /// Offset from start of the archive header to beginning of the compressed data
241    fn offset(&self, fork: Fork) -> u64;
242    /// Checksum of the uncompressed data
243    fn checksum(&self, fork: Fork) -> u16;
244}
245
246impl ReadableEntry for Entry {
247    fn encrypted(&self, fork: Fork) -> bool {
248        match self {
249            Entry::File(e) => e.encrypted(fork),
250            Entry::Directory(_) => false,
251            Entry::DirectoryEnd(_) => false,
252        }
253    }
254
255    fn algorithm(&self, fork: Fork) -> Algorithm {
256        match self {
257            Entry::File(e) => e.algorithm(fork),
258            Entry::Directory(e) => e.algorithm(fork),
259            Entry::DirectoryEnd(_) => Algorithm::None,
260        }
261    }
262
263    fn compressed_size(&self, fork: Fork) -> usize {
264        match self {
265            Entry::File(e) => e.compressed_size(fork),
266            Entry::Directory(e) => e.compressed_size(fork),
267            Entry::DirectoryEnd(_) => 0,
268        }
269    }
270
271    fn uncompressed_size(&self, fork: Fork) -> usize {
272        match self {
273            Entry::File(e) => e.uncompressed_size(fork),
274            Entry::Directory(e) => e.uncompressed_size(fork),
275            Entry::DirectoryEnd(_) => 0,
276        }
277    }
278
279    fn offset(&self, fork: Fork) -> u64 {
280        match self {
281            Entry::File(e) => e.offset(fork),
282            Entry::Directory(e) => e.offset(fork),
283            Entry::DirectoryEnd(off) => *off,
284        }
285    }
286
287    fn checksum(&self, fork: Fork) -> u16 {
288        match self {
289            Entry::File(file) => file.checksum(fork),
290            Entry::Directory(_) => 0,
291            Entry::DirectoryEnd(_) => 0,
292        }
293    }
294}
295
296impl ReadableEntry for File {
297    #[inline]
298    fn algorithm(&self, fork: Fork) -> Algorithm {
299        File::compression_method(self, fork)
300    }
301
302    #[inline]
303    fn compressed_size(&self, fork: Fork) -> usize {
304        File::compressed_size(self, fork)
305    }
306
307    #[inline]
308    fn uncompressed_size(&self, fork: Fork) -> usize {
309        File::uncompressed_size(self, fork)
310    }
311
312    #[inline]
313    fn encrypted(&self, fork: Fork) -> bool {
314        File::encrypted(self, fork)
315    }
316
317    #[inline]
318    fn offset(&self, fork: Fork) -> u64 {
319        File::offset(self, fork)
320    }
321
322    #[inline]
323    fn checksum(&self, fork: Fork) -> u16 {
324        File::checksum(self, fork)
325    }
326}
327
328impl ReadableEntry for v5::File {
329    #[inline]
330    fn algorithm(&self, fork: Fork) -> Algorithm {
331        v5::File::compression_method(self, fork)
332    }
333
334    #[inline]
335    fn compressed_size(&self, fork: Fork) -> usize {
336        v5::File::compressed_size(self, fork)
337    }
338
339    #[inline]
340    fn uncompressed_size(&self, fork: Fork) -> usize {
341        v5::File::uncompressed_size(self, fork)
342    }
343
344    #[inline]
345    fn encrypted(&self, fork: Fork) -> bool {
346        v5::File::encrypted(self, fork)
347    }
348
349    #[inline]
350    fn offset(&self, fork: Fork) -> u64 {
351        v5::File::offset(self, fork)
352    }
353
354    #[inline]
355    fn checksum(&self, fork: Fork) -> u16 {
356        v5::File::checksum(self, fork)
357    }
358}
359
360impl ReadableEntry for v1::File {
361    #[inline]
362    fn algorithm(&self, fork: Fork) -> Algorithm {
363        v1::File::compression_method(self, fork)
364    }
365
366    #[inline]
367    fn compressed_size(&self, fork: Fork) -> usize {
368        v1::File::compressed_size(self, fork)
369    }
370
371    #[inline]
372    fn uncompressed_size(&self, fork: Fork) -> usize {
373        v1::File::uncompressed_size(self, fork)
374    }
375
376    #[inline]
377    fn encrypted(&self, fork: Fork) -> bool {
378        v1::File::encrypted(self, fork)
379    }
380
381    #[inline]
382    fn offset(&self, fork: Fork) -> u64 {
383        v1::File::offset(self, fork)
384    }
385
386    #[inline]
387    fn checksum(&self, fork: Fork) -> u16 {
388        v1::File::checksum(self, fork)
389    }
390}
391
392pub struct EntryIterator<R: io::Read + io::Seek> {
393    next_offset: u64,
394    next_file_index: usize,
395    stack: Vec<u32>,
396    v1: bool,
397
398    pub(crate) reader: Rc<R>,
399}
400
401impl<R: io::Read + io::Seek> EntryIterator<R> {
402    fn new(reader: Rc<R>, entry_count: usize, offset: u64, v1: bool) -> Self {
403        Self {
404            next_offset: offset,
405            next_file_index: 0,
406            stack: vec![entry_count as u32],
407            reader,
408            v1,
409        }
410    }
411
412    pub fn verifying(self) -> VerifyingIterator<R> {
413        let Self {
414            next_offset,
415            stack,
416            reader,
417            v1,
418            next_file_index,
419        } = self;
420
421        VerifyingIterator {
422            next_offset,
423            stack,
424            reader,
425            v1,
426            _next_file_index: next_file_index,
427        }
428    }
429}
430
431impl<R: io::Read + io::Seek> Iterator for EntryIterator<R> {
432    type Item = Entry;
433
434    fn next(&mut self) -> Option<Self::Item> {
435        let reader = unsafe { Rc::get_mut_unchecked(&mut self.reader) };
436
437        match self.stack.last_mut() {
438            None => return None,
439            Some(0) => {
440                self.stack.pop();
441
442                // Don't send directory end marker for the root catalog
443                return if self.stack.is_empty() {
444                    None
445                } else {
446                    Some(Entry::DirectoryEnd(self.next_offset))
447                };
448            }
449            Some(d) => *d -= 1,
450        }
451
452        let Ok(entry_offset) = reader.seek(io::SeekFrom::Start(self.next_offset)) else {
453            log::warn!("Failed seeking to next archive entry");
454            return None;
455        };
456
457        if self.v1 {
458            let Ok(entry) = reader.read_be::<v1::Entry>() else {
459                return None;
460            };
461
462            let Ok(payload_offset) = reader.stream_position() else {
463                return None;
464            };
465
466            match entry {
467                v1::Entry::Directory(dir) => {
468                    self.next_offset = payload_offset;
469                    self.stack.push(u32::MAX);
470
471                    Some(Entry::Directory(dir.into()))
472                }
473                v1::Entry::DirectoryEnd => {
474                    self.next_offset = payload_offset;
475                    self.stack.pop();
476
477                    Some(Entry::DirectoryEnd(payload_offset))
478                }
479                v1::Entry::File(mut file) => {
480                    self.next_offset = payload_offset
481                        + file.data_compressed_size as u64
482                        + file.rsrc_compressed_size as u64;
483                    log::debug!(
484                        "Entry: {}, {:?} {} 0x{:04x}",
485                        file.file_name,
486                        file.data_compression,
487                        file.data_compressed_size,
488                        file.checksum(Fork::Data)
489                    );
490
491                    file.index = self.next_file_index;
492                    self.next_file_index += 1;
493
494                    Some(Entry::File(file.into()))
495                }
496            }
497        } else {
498            let Ok(entry) = reader.read_be_args::<v5::Entry>(
499                EntryBinReadArgs::builder().offset(entry_offset).finalize(),
500            ) else {
501                return None;
502            };
503
504            let Ok(payload_offset) = reader.stream_position() else {
505                return None;
506            };
507
508            match entry {
509                v5::Entry::Directory(dir) => {
510                    if dir.marks_end() {
511                        self.next_offset = payload_offset;
512                        return self.next();
513                    }
514
515                    self.stack.push(dir.child_count);
516                    self.next_offset = dir.first_child_offset;
517
518                    Some(Entry::Directory(dir.into()))
519                }
520                v5::Entry::File(mut file) => {
521                    self.next_offset = file.next_entry_offset as u64;
522                    file.payload_offset = payload_offset;
523
524                    file.index = self.next_file_index;
525                    self.next_file_index += 1;
526
527                    Some(Entry::File(file.into()))
528                }
529            }
530        }
531    }
532}