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
9#[binread]
10#[derive(Debug)]
11#[br(big)]
12pub struct ArchiveHeader {
13 #[br(assert(matches!(file_code, fourcc!("SIT!") | fourcc!("SITD") | fourcc!("SIT2") | fourcc!("SIT5"))))]
17 pub file_code: FourCC,
18 pub entry_count: u16,
20 pub archive_len: u32,
22 #[br(assert(creator_code==fourcc!("rLau")), temp)]
24 creator_code: FourCC,
25 pub version: Version,
27 pub reserved: [u8; 7],
32}
33
34impl ArchiveHeader {
35 pub fn first_entry_offset(&self) -> u64 {
36 match self.version {
37 Version::Early => 0x16,
38 Version::Later => {
39 (self.reserved[1] as u64) << 24
40 | (self.reserved[2] as u64) << 16
41 | (self.reserved[3] as u64) << 8
42 | (self.reserved[4] as u64)
43 }
44 _ => 0x16,
45 }
46 }
47}
48
49#[binread]
50#[derive(Debug, Clone)]
51#[br(big, import { offset: u64 })]
52pub struct File {
53 #[br(restore_position, map(|v:u8| v & 128 != 0))]
54 pub rsrc_encrypted: bool,
56 pub rsrc_compression: Algorithm,
59 #[br(restore_position, map(|v:u8| v & 128 != 0))]
61 pub data_encrypted: bool,
63 pub data_compression: Algorithm,
65 #[br(temp)]
66 name_len: u8,
67 #[br(map(|r: [u8; 63]| decode_string(r[0..min(name_len as usize,63)].to_vec())))]
68 pub file_name: String,
70 pub file_code: FourCC,
72 pub creator_code: FourCC,
74 pub flags: FinderFlags,
76 #[br(map(macintosh_utils::date))]
78 pub created_at: chrono::DateTime<chrono::Utc>,
79 #[br(map(macintosh_utils::date))]
81 pub modified_at: chrono::DateTime<chrono::Utc>,
82 pub rsrc_uncompressed_size: u32,
84 pub data_uncompressed_size: u32,
86 pub rsrc_compressed_size: u32,
88 pub data_compressed_size: u32,
90 pub rsrc_crc: u16,
94 pub data_crc: u16,
98 pub reserved: [u8; 6],
100 pub header_crc: u16,
104
105 #[br(calc(offset + Entry::HEADER_SIZE))]
106 pub payload_offset: u64,
107
108 #[br(ignore)]
109 pub index: usize,
112}
113
114impl File {
115 #[inline]
116 pub fn uncompressed_size(&self, fork: Fork) -> usize {
117 match fork {
118 Fork::Data => self.data_uncompressed_size as usize,
119 Fork::Resource => self.rsrc_uncompressed_size as usize,
120 }
121 }
122
123 #[inline]
124 pub fn compressed_size(&self, fork: Fork) -> usize {
125 match fork {
126 Fork::Data => self.data_compressed_size as usize,
127 Fork::Resource => self.rsrc_compressed_size as usize,
128 }
129 }
130
131 #[inline]
132 pub fn compression_method(&self, fork: Fork) -> Algorithm {
133 match fork {
134 Fork::Data => self.data_compression,
135 Fork::Resource => self.rsrc_compression,
136 }
137 }
138
139 #[inline]
140 pub fn checksum(&self, fork: Fork) -> u16 {
141 match fork {
142 Fork::Data => self.data_crc,
143 Fork::Resource => self.rsrc_crc,
144 }
145 }
146
147 #[inline]
148 pub fn encrypted(&self, fork: Fork) -> bool {
149 match fork {
150 Fork::Data => self.data_encrypted,
151 Fork::Resource => self.rsrc_encrypted,
152 }
153 }
154
155 #[inline]
156 pub fn offset(&self, fork: Fork) -> u64 {
157 match fork {
158 Fork::Resource => self.payload_offset,
159 Fork::Data => self.payload_offset + self.compressed_size(Fork::Resource) as u64,
160 }
161 }
162
163 pub fn uses_encryption(&self) -> bool {
164 self.encrypted(Fork::Data) || self.encrypted(Fork::Resource)
165 }
166}
167
168#[allow(unused)]
169#[binread]
170#[derive(Debug, Clone)]
171#[br(big)]
172pub struct Directory {
173 #[br(temp)]
174 flags1: u8,
175 #[br(calc(flags1 & 16 != 0))]
176 contains_encrypted_entries: bool,
177 pub flags2: u8,
178 #[br(temp)]
179 name_len: u8,
180 #[br(map(|r: [u8; 63]| decode_string(r[0..min(name_len as usize, 63)].to_vec())))]
181 pub file_name: String,
183 garbage: [u8; 8],
185 pub flags: FinderFlags,
187 #[br(map(macintosh_utils::date))]
189 pub created_at: chrono::DateTime<chrono::Utc>,
190 #[br(map(macintosh_utils::date))]
192 pub modified_at: chrono::DateTime<chrono::Utc>,
193}
194
195impl Directory {
196 #[inline]
197 pub fn uncompressed_size(&self, _: Fork) -> usize {
198 0
199 }
200
201 #[inline]
202 pub fn algorithm(&self, _: Fork) -> Algorithm {
203 Algorithm::None
204 }
205
206 #[inline]
207 pub fn compressed_size(&self, _: Fork) -> usize {
208 0
209 }
210
211 #[inline]
212 pub fn checksum(&self, _: Fork) -> u16 {
213 0
214 }
215
216 #[inline]
217 pub fn offset(&self, _: Fork) -> u64 {
218 0
219 }
220
221 pub fn uses_encryption(&self) -> bool {
222 self.contains_encrypted_entries
223 }
224}
225
226pub enum Entry {
228 File(File),
229 Directory(Directory),
230 DirectoryEnd,
231}
232
233impl Entry {
234 pub const HEADER_SIZE: u64 = 112;
236}
237
238impl BinRead for Entry {
239 type Args<'a> = ();
240
241 fn read_options<R: io::Read + io::Seek>(
242 reader: &mut R,
243 _endian: binrw::Endian,
244 _args: Self::Args<'_>,
245 ) -> binrw::BinResult<Self> {
246 let offset = reader.stream_position()?;
247 let mut buffer = vec![0u8; Entry::HEADER_SIZE as usize];
248 reader.read_exact(&mut buffer)?;
249
250 if (buffer[0] & 33) == 33 {
251 Ok(Entry::DirectoryEnd)
252 } else if (buffer[0] & 32) == 32 {
253 let mut cursor = io::Cursor::new(buffer);
254 Ok(Entry::Directory(cursor.read_be()?))
255 } else {
256 let mut cursor = io::Cursor::new(buffer);
257 Ok(Entry::File(
258 cursor.read_be_args(FileBinReadArgs { offset })?,
259 ))
260 }
261 }
262}
263
264impl From<File> for Entry {
265 fn from(val: File) -> Self {
266 Entry::File(val)
267 }
268}
269
270impl From<Directory> for Entry {
271 fn from(val: Directory) -> Self {
272 Entry::Directory(val)
273 }
274}