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| self.verify_entry(e))
129 }
130
131 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 pub fn iter(&self) -> EntryIterator<R> {
161 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 pub fn into_inner(self) -> R {
180 self.ensure_not_iterating();
181
182 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 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 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
231pub trait ReadableEntry {
233 fn algorithm(&self, fork: Fork) -> Algorithm;
235 fn compressed_size(&self, fork: Fork) -> usize;
237 fn uncompressed_size(&self, fork: Fork) -> usize;
239 fn encrypted(&self, fork: Fork) -> bool;
240 fn offset(&self, fork: Fork) -> u64;
242 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 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}