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