Skip to main content

sit/structs/
v1.rs

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    /// File identifier, this should be 'SIT!', 'SITD', 'SIT2' or 'SIT5'.
14    ///
15    /// If it says 'Stuf' you are probably dealing with a version 5 archive instead
16    #[br(assert(matches!(file_code, fourcc!("SIT!") | fourcc!("SITD") | fourcc!("SIT2") | fourcc!("SIT5"))))]
17    pub file_code: FourCC,
18    /// Number of entries in the root of the archive
19    pub entry_count: u16,
20    /// total size of the archive file in bytes (including header and everything)
21    pub archive_len: u32,
22    /// Must be 'rLau' – this is in reference to Raymond Lau who created the format
23    #[br(assert(creator_code==fourcc!("rLau")), temp)]
24    creator_code: FourCC,
25    /// File format (sub-)version
26    pub version: Version,
27    /// Reserved for future use (in v1)
28    /// - if `version==1` this is set to 00 00 00 00 00 00 00
29    /// - if `version==2` this is set to XX NN NN NN NN XX XX
30    ///    where NN NN NN NN is the offset to the first root entry from the start of the file in bytes
31    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    // Data fork is encrypted
55    pub rsrc_encrypted: bool,
56    // let data_encryption = buffer[0] & 128 != 0;
57    // Method used to compress resource fork
58    pub rsrc_compression: Algorithm,
59    // Resource fork is encrypted
60    #[br(restore_position, map(|v:u8| v & 128 != 0))]
61    // Data fork is encrypted
62    pub data_encrypted: bool,
63    // Method used to compress data fork
64    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    /// Name of the compressed file
69    pub file_name: String,
70    /// Mac OS file type
71    pub file_code: FourCC,
72    /// Mac OS file creator
73    pub creator_code: FourCC,
74    /// Attributes for file in Finder
75    pub flags: FinderFlags,
76    /// Creation date in classic Mac OS format (seconds since 1904)
77    #[br(map(macintosh_utils::date))]
78    pub created_at: chrono::DateTime<chrono::Utc>,
79    /// Modification date in classic Mac OS format (seconds since 1904)
80    #[br(map(macintosh_utils::date))]
81    pub modified_at: chrono::DateTime<chrono::Utc>,
82    /// Size of resource fork after decompression
83    pub rsrc_uncompressed_size: u32,
84    /// Size of data fork after decompression
85    pub data_uncompressed_size: u32,
86    /// Size of compressed resource fork data
87    pub rsrc_compressed_size: u32,
88    /// Size of compressed data fork data
89    pub data_compressed_size: u32,
90    /// CRC-16 checksum for decompressed resource data
91    ///
92    /// The CRC-16 configuration is `CRC-16-IBM` also known as `CRC-16-ARC` where the CRC of ASCII "123456789" is 0xbb3d.
93    pub rsrc_crc: u16,
94    /// CRC-16 checksum for decompressed data fork data
95    ///
96    /// The CRC-16 configuration is `CRC-16-IBM` also known as `CRC-16-ARC` where the CRC of ASCII "123456789" is 0xbb3d.
97    pub data_crc: u16,
98    /// Reserved for future use
99    pub reserved: [u8; 6],
100    /// CRC-16 checksum of the file header without these last two bytes
101    ///
102    /// The CRC-16 configuration is `CRC-16-IBM` also known as `CRC-16-ARC` where the CRC of ASCII "123456789" is 0xbb3d.
103    pub header_crc: u16,
104
105    #[br(calc(offset + Entry::HEADER_SIZE))]
106    pub payload_offset: u64,
107
108    #[br(ignore)]
109    /// Index of the file entry determine by counting previous file entries in depth-first search
110    /// order
111    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    /// Name of the compressed file
182    pub file_name: String,
183    /// position of  Mac OS file type and creator code in file header, unused for directories
184    garbage: [u8; 8],
185    /// Attributes for file in Finder
186    pub flags: FinderFlags,
187    /// Creation date in classic Mac OS format (seconds since 1904)
188    #[br(map(macintosh_utils::date))]
189    pub created_at: chrono::DateTime<chrono::Utc>,
190    /// Modification date in classic Mac OS format (seconds since 1904)
191    #[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
226/// Represents an entry in the sit archive
227pub enum Entry {
228    File(File),
229    Directory(Directory),
230    DirectoryEnd,
231}
232
233impl Entry {
234    /// Fixed size of and archive entry header on disk
235    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}