1use std::{cmp::min, io};
2
3use binrw::{BinRead, BinReaderExt, binread};
4use fourcc::{FourCC, fourcc};
5use macintosh_utils::{FinderFlags, Fork, decode_string};
6
7use super::{Algorithm, Version};
8
9trait SitIdentifier {
10 fn is_v1(&self) -> bool;
11}
12
13impl SitIdentifier for FourCC {
14 fn is_v1(&self) -> bool {
15 matches!(
16 self,
17 fourcc!("SIT!")
18 | fourcc!("SITD")
19 | fourcc!("SIT2")
20 | fourcc!("SIT5")
21 | fourcc!("STi3")
23 | fourcc!("ST65")
25 )
26 }
27}
28
29#[binread]
30#[derive(Debug)]
31#[br(big)]
32pub struct ArchiveHeader {
33 #[br(assert(file_code.is_v1()))]
37 pub file_code: FourCC,
38 pub entry_count: u16,
40 pub archive_len: u32,
42 #[br(assert(creator_code==fourcc!("rLau")), temp)]
44 creator_code: FourCC,
45 pub version: Version,
47 pub reserved: [u8; 7],
52}
53
54impl ArchiveHeader {
55 pub fn first_entry_offset(&self) -> u64 {
56 match self.version {
57 Version::Early => 0x16,
58 Version::Later => {
59 (self.reserved[1] as u64) << 24
60 | (self.reserved[2] as u64) << 16
61 | (self.reserved[3] as u64) << 8
62 | (self.reserved[4] as u64)
63 }
64 _ => 0x16,
65 }
66 }
67}
68
69#[binread]
70#[derive(Debug, Clone)]
71#[br(big, import { offset: u64 })]
72pub struct File {
73 #[br(restore_position, map(|v:u8| v & 128 != 0))]
74 pub rsrc_encrypted: bool,
76 pub rsrc_compression: Algorithm,
79 #[br(restore_position, map(|v:u8| v & 128 != 0))]
81 pub data_encrypted: bool,
83 pub data_compression: Algorithm,
85 #[br(temp)]
86 name_len: u8,
87 #[br(map(|r: [u8; 63]| decode_string(r[0..min(name_len as usize,63)].to_vec())))]
88 pub file_name: String,
90 pub file_code: FourCC,
92 pub creator_code: FourCC,
94 pub flags: FinderFlags,
96 #[br(map(macintosh_utils::date))]
98 pub created_at: chrono::DateTime<chrono::Utc>,
99 #[br(map(macintosh_utils::date))]
101 pub modified_at: chrono::DateTime<chrono::Utc>,
102 pub rsrc_uncompressed_size: u32,
104 pub data_uncompressed_size: u32,
106 pub rsrc_compressed_size: u32,
108 pub data_compressed_size: u32,
110 pub rsrc_crc: u16,
114 pub data_crc: u16,
118 pub reserved: [u8; 6],
120 pub header_crc: u16,
124
125 #[br(calc(offset + Entry::HEADER_SIZE))]
126 pub payload_offset: u64,
127
128 #[br(ignore)]
129 pub index: usize,
132}
133
134impl File {
135 #[inline]
136 pub fn uncompressed_size(&self, fork: Fork) -> usize {
137 match fork {
138 Fork::Data => self.data_uncompressed_size as usize,
139 Fork::Resource => self.rsrc_uncompressed_size as usize,
140 }
141 }
142
143 #[inline]
144 pub fn compressed_size(&self, fork: Fork) -> usize {
145 match fork {
146 Fork::Data => self.data_compressed_size as usize,
147 Fork::Resource => self.rsrc_compressed_size as usize,
148 }
149 }
150
151 #[inline]
152 pub fn compression_method(&self, fork: Fork) -> Algorithm {
153 match fork {
154 Fork::Data => self.data_compression,
155 Fork::Resource => self.rsrc_compression,
156 }
157 }
158
159 #[inline]
160 pub fn checksum(&self, fork: Fork) -> u16 {
161 match fork {
162 Fork::Data => self.data_crc,
163 Fork::Resource => self.rsrc_crc,
164 }
165 }
166
167 #[inline]
168 pub fn encrypted(&self, fork: Fork) -> bool {
169 match fork {
170 Fork::Data => self.data_encrypted,
171 Fork::Resource => self.rsrc_encrypted,
172 }
173 }
174
175 #[inline]
176 pub fn offset(&self, fork: Fork) -> u64 {
177 match fork {
178 Fork::Resource => self.payload_offset,
179 Fork::Data => self.payload_offset + self.compressed_size(Fork::Resource) as u64,
180 }
181 }
182
183 pub fn uses_encryption(&self) -> bool {
184 self.encrypted(Fork::Data) || self.encrypted(Fork::Resource)
185 }
186}
187
188#[allow(unused)]
189#[binread]
190#[derive(Debug, Clone)]
191#[br(big)]
192pub struct Directory {
193 #[br(temp)]
194 flags1: u8,
195 #[br(calc(flags1 & 16 != 0))]
196 contains_encrypted_entries: bool,
197 pub flags2: u8,
198 #[br(temp)]
199 name_len: u8,
200 #[br(map(|r: [u8; 63]| decode_string(r[0..min(name_len as usize, 63)].to_vec())))]
201 pub file_name: String,
203 garbage: [u8; 8],
205 pub flags: FinderFlags,
207 #[br(map(macintosh_utils::date))]
209 pub created_at: chrono::DateTime<chrono::Utc>,
210 #[br(map(macintosh_utils::date))]
212 pub modified_at: chrono::DateTime<chrono::Utc>,
213}
214
215impl Directory {
216 #[inline]
217 pub fn uncompressed_size(&self, _: Fork) -> usize {
218 0
219 }
220
221 #[inline]
222 pub fn algorithm(&self, _: Fork) -> Algorithm {
223 Algorithm::None
224 }
225
226 #[inline]
227 pub fn compressed_size(&self, _: Fork) -> usize {
228 0
229 }
230
231 #[inline]
232 pub fn checksum(&self, _: Fork) -> u16 {
233 0
234 }
235
236 #[inline]
237 pub fn offset(&self, _: Fork) -> u64 {
238 0
239 }
240
241 pub fn uses_encryption(&self) -> bool {
242 self.contains_encrypted_entries
243 }
244}
245
246pub enum Entry {
248 File(File),
249 Directory(Directory),
250 DirectoryEnd,
251}
252
253impl Entry {
254 pub const HEADER_SIZE: u64 = 112;
256}
257
258impl BinRead for Entry {
259 type Args<'a> = ();
260
261 fn read_options<R: io::Read + io::Seek>(
262 reader: &mut R,
263 _endian: binrw::Endian,
264 _args: Self::Args<'_>,
265 ) -> binrw::BinResult<Self> {
266 let offset = reader.stream_position()?;
267 let mut buffer = vec![0u8; Entry::HEADER_SIZE as usize];
268 reader.read_exact(&mut buffer)?;
269
270 if (buffer[0] & 33) == 33 {
271 Ok(Entry::DirectoryEnd)
272 } else if (buffer[0] & 32) == 32 {
273 let mut cursor = io::Cursor::new(buffer);
274 Ok(Entry::Directory(cursor.read_be()?))
275 } else {
276 let mut cursor = io::Cursor::new(buffer);
277 Ok(Entry::File(
278 cursor.read_be_args(FileBinReadArgs { offset })?,
279 ))
280 }
281 }
282}
283
284impl From<File> for Entry {
285 fn from(val: File) -> Self {
286 Entry::File(val)
287 }
288}
289
290impl From<Directory> for Entry {
291 fn from(val: Directory) -> Self {
292 Entry::Directory(val)
293 }
294}