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 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 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| {
129 if let Entry::Directory(crate::structs::Directory::V5(dir)) = &e
130 && dir.marks_end()
131 {
132 return Ok(());
133 }
134
135 if !e.is_file() {
136 return Ok(());
137 }
138
139 if e.has(Fork::Resource) {
140 self.open(&e, Fork::Resource)?.verifying().slurp()?;
141 }
142
143 if e.has(Fork::Data) {
144 self.open(&e, Fork::Data)?.verifying().slurp()?;
145 }
146
147 Ok(())
148 })
149 }
150
151 pub fn open<'a, E: ReadableEntry>(
153 &'a mut self,
154 entry: &E,
155 fork: Fork,
156 ) -> Result<EntryReader<'a, R>, Error> {
157 let algo = entry.algorithm(fork);
158 let compressed_size = entry.compressed_size(fork);
159 let uncompressed_size = entry.uncompressed_size(fork);
160 let offset = entry.offset(fork);
161 let checksum = entry.checksum(fork);
162
163 if entry.encrypted(fork) {
164 return Err(Error::UnsupportedFeature(
165 crate::error::UnsupportedFeature::Encryption,
166 ));
167 }
168
169 EntryReader::try_from(
170 &mut self.inner,
171 algo,
172 uncompressed_size as u64,
173 compressed_size,
174 offset,
175 checksum,
176 )
177 }
178
179 pub fn iter(&self) -> EntryIterator<R> {
181 self.ensure_not_iterating();
182
183 let catalog_offset = match &self.header {
186 ArchiveHeader::V1(hdr) => self.header_location + hdr.first_entry_offset(),
187 ArchiveHeader::V5(hdr) => self.header_location + hdr.first_entry_offset(),
188 };
189
190 EntryIterator::new(
191 self.inner.clone(),
192 self.header.entry_count(),
193 catalog_offset,
194 matches!(self.header, ArchiveHeader::V1(_)),
195 )
196 }
197
198 pub fn into_inner(self) -> R {
200 self.ensure_not_iterating();
201
202 let Archive { inner, .. } = self;
205 unsafe { Rc::try_unwrap(inner).unwrap_unchecked() }
206 }
207
208 pub fn reset(&mut self) -> Result<(), Error> {
209 self.ensure_not_iterating();
210
211 unsafe {
214 Rc::get_mut_unchecked(&mut self.inner)
215 .seek(io::SeekFrom::Start(self.header_location))?;
216 }
217
218 Ok(())
219 }
220
221 fn ensure_not_iterating(&self) {
222 if Rc::strong_count(&self.inner) != 1 || Rc::weak_count(&self.inner) != 0 {
223 panic!("Can not modify archive while an iterator is runnning")
224 }
225 }
226}
227
228pub trait ReadableEntry {
230 fn algorithm(&self, fork: Fork) -> Algorithm;
232 fn compressed_size(&self, fork: Fork) -> usize;
234 fn uncompressed_size(&self, fork: Fork) -> usize;
236 fn encrypted(&self, fork: Fork) -> bool;
237 fn offset(&self, fork: Fork) -> u64;
239 fn checksum(&self, fork: Fork) -> u16;
241}
242
243impl ReadableEntry for Entry {
244 fn encrypted(&self, fork: Fork) -> bool {
245 match self {
246 Entry::File(e) => e.encrypted(fork),
247 Entry::Directory(_) => false,
248 Entry::DirectoryEnd(_) => false,
249 }
250 }
251
252 fn algorithm(&self, fork: Fork) -> Algorithm {
253 match self {
254 Entry::File(e) => e.algorithm(fork),
255 Entry::Directory(e) => e.algorithm(fork),
256 Entry::DirectoryEnd(_) => Algorithm::None,
257 }
258 }
259
260 fn compressed_size(&self, fork: Fork) -> usize {
261 match self {
262 Entry::File(e) => e.compressed_size(fork),
263 Entry::Directory(e) => e.compressed_size(fork),
264 Entry::DirectoryEnd(_) => 0,
265 }
266 }
267
268 fn uncompressed_size(&self, fork: Fork) -> usize {
269 match self {
270 Entry::File(e) => e.uncompressed_size(fork),
271 Entry::Directory(e) => e.uncompressed_size(fork),
272 Entry::DirectoryEnd(_) => 0,
273 }
274 }
275
276 fn offset(&self, fork: Fork) -> u64 {
277 match self {
278 Entry::File(e) => e.offset(fork),
279 Entry::Directory(e) => e.offset(fork),
280 Entry::DirectoryEnd(off) => *off,
281 }
282 }
283
284 fn checksum(&self, fork: Fork) -> u16 {
285 match self {
286 Entry::File(file) => file.checksum(fork),
287 Entry::Directory(_) => 0,
288 Entry::DirectoryEnd(_) => 0,
289 }
290 }
291}
292
293impl ReadableEntry for File {
294 #[inline]
295 fn algorithm(&self, fork: Fork) -> Algorithm {
296 File::compression_method(self, fork)
297 }
298
299 #[inline]
300 fn compressed_size(&self, fork: Fork) -> usize {
301 File::compressed_size(self, fork)
302 }
303
304 #[inline]
305 fn uncompressed_size(&self, fork: Fork) -> usize {
306 File::uncompressed_size(self, fork)
307 }
308
309 #[inline]
310 fn encrypted(&self, fork: Fork) -> bool {
311 File::encrypted(self, fork)
312 }
313
314 #[inline]
315 fn offset(&self, fork: Fork) -> u64 {
316 File::offset(self, fork)
317 }
318
319 #[inline]
320 fn checksum(&self, fork: Fork) -> u16 {
321 File::checksum(self, fork)
322 }
323}
324
325impl ReadableEntry for v5::File {
326 #[inline]
327 fn algorithm(&self, fork: Fork) -> Algorithm {
328 v5::File::compression_method(self, fork)
329 }
330
331 #[inline]
332 fn compressed_size(&self, fork: Fork) -> usize {
333 v5::File::compressed_size(self, fork)
334 }
335
336 #[inline]
337 fn uncompressed_size(&self, fork: Fork) -> usize {
338 v5::File::uncompressed_size(self, fork)
339 }
340
341 #[inline]
342 fn encrypted(&self, fork: Fork) -> bool {
343 v5::File::encrypted(self, fork)
344 }
345
346 #[inline]
347 fn offset(&self, fork: Fork) -> u64 {
348 v5::File::offset(self, fork)
349 }
350
351 #[inline]
352 fn checksum(&self, fork: Fork) -> u16 {
353 v5::File::checksum(self, fork)
354 }
355}
356
357impl ReadableEntry for v1::File {
358 #[inline]
359 fn algorithm(&self, fork: Fork) -> Algorithm {
360 v1::File::compression_method(self, fork)
361 }
362
363 #[inline]
364 fn compressed_size(&self, fork: Fork) -> usize {
365 v1::File::compressed_size(self, fork)
366 }
367
368 #[inline]
369 fn uncompressed_size(&self, fork: Fork) -> usize {
370 v1::File::uncompressed_size(self, fork)
371 }
372
373 #[inline]
374 fn encrypted(&self, fork: Fork) -> bool {
375 v1::File::encrypted(self, fork)
376 }
377
378 #[inline]
379 fn offset(&self, fork: Fork) -> u64 {
380 v1::File::offset(self, fork)
381 }
382
383 #[inline]
384 fn checksum(&self, fork: Fork) -> u16 {
385 v1::File::checksum(self, fork)
386 }
387}
388
389pub struct EntryIterator<R: io::Read + io::Seek> {
390 next_offset: u64,
391 next_file_index: usize,
392 stack: Vec<u32>,
393 v1: bool,
394
395 pub(crate) reader: Rc<R>,
396}
397
398impl<R: io::Read + io::Seek> EntryIterator<R> {
399 fn new(reader: Rc<R>, entry_count: usize, offset: u64, v1: bool) -> Self {
400 Self {
401 next_offset: offset,
402 next_file_index: 0,
403 stack: vec![entry_count as u32],
404 reader,
405 v1,
406 }
407 }
408
409 pub fn verifying(self) -> VerifyingIterator<R> {
410 let Self {
411 next_offset,
412 stack,
413 reader,
414 v1,
415 next_file_index,
416 } = self;
417
418 VerifyingIterator {
419 next_offset,
420 stack,
421 reader,
422 v1,
423 next_file_index,
424 }
425 }
426}
427
428impl<R: io::Read + io::Seek> Iterator for EntryIterator<R> {
429 type Item = Entry;
430
431 fn next(&mut self) -> Option<Self::Item> {
432 let reader = unsafe { Rc::get_mut_unchecked(&mut self.reader) };
433
434 match self.stack.last_mut() {
435 None => return None,
436 Some(0) => {
437 self.stack.pop();
438
439 return if self.stack.is_empty() {
441 None
442 } else {
443 Some(Entry::DirectoryEnd(self.next_offset))
444 };
445 }
446 Some(d) => *d -= 1,
447 }
448
449 let Ok(entry_offset) = reader.seek(io::SeekFrom::Start(self.next_offset)) else {
450 log::warn!("Failed seeking to next archive entry");
451 return None;
452 };
453
454 if self.v1 {
455 let Ok(entry) = reader.read_be::<v1::Entry>() else {
456 return None;
457 };
458
459 let Ok(payload_offset) = reader.stream_position() else {
460 return None;
461 };
462
463 match entry {
464 v1::Entry::Directory(dir) => {
465 self.next_offset = payload_offset;
466 self.stack.push(u32::MAX);
467
468 Some(Entry::Directory(dir.into()))
469 }
470 v1::Entry::DirectoryEnd => {
471 self.next_offset = payload_offset;
472 self.stack.pop();
473
474 Some(Entry::DirectoryEnd(payload_offset))
475 }
476 v1::Entry::File(mut file) => {
477 self.next_offset = payload_offset
478 + file.data_compressed_size as u64
479 + file.rsrc_compressed_size as u64;
480 log::debug!(
481 "Entry: {}, {:?} {} 0x{:04x}",
482 file.file_name,
483 file.data_compression,
484 file.data_compressed_size,
485 file.checksum(Fork::Data)
486 );
487
488 file.index = self.next_file_index;
489 self.next_file_index += 1;
490
491 Some(Entry::File(file.into()))
492 }
493 }
494 } else {
495 let Ok(entry) = reader.read_be_args::<v5::Entry>(
496 EntryBinReadArgs::builder().offset(entry_offset).finalize(),
497 ) else {
498 return None;
499 };
500
501 let Ok(payload_offset) = reader.stream_position() else {
502 return None;
503 };
504
505 match entry {
506 v5::Entry::Directory(dir) => {
507 if dir.marks_end() {
508 self.next_offset = payload_offset;
509 return self.next();
510 }
511
512 self.stack.push(dir.child_count);
513 self.next_offset = dir.first_child_offset;
514
515 Some(Entry::Directory(dir.into()))
516 }
517 v5::Entry::File(mut file) => {
518 self.next_offset = file.next_entry_offset as u64;
519 file.payload_offset = payload_offset;
520
521 file.index = self.next_file_index;
522 self.next_file_index += 1;
523
524 Some(Entry::File(file.into()))
525 }
526 }
527 }
528 }
529}