Skip to main content

sit/structs/
mod.rs

1use std::fmt;
2use std::io;
3
4use binrw::{BinRead, BinReaderExt};
5use fourcc::FourCC;
6use macintosh_utils::Fork;
7use v5::EntryFlags;
8
9/// Structures used in archive version from the early days until 4.5
10pub mod v1;
11/// Structures used in StuffIt 5.0 and onwards
12pub mod v5;
13
14#[derive(BinRead, Debug)]
15#[br(big)]
16pub enum ArchiveHeader {
17    V1(v1::ArchiveHeader),
18    V5(v5::ArchiveHeader),
19}
20
21impl ArchiveHeader {
22    pub fn entry_count(&self) -> usize {
23        match self {
24            ArchiveHeader::V1(header) => header.entry_count as usize,
25            ArchiveHeader::V5(header) => header.entry_count as usize,
26        }
27    }
28
29    pub fn version(&self) -> Version {
30        match self {
31            ArchiveHeader::V1(header) => header.version,
32            ArchiveHeader::V5(header) => header.version,
33        }
34    }
35
36    pub fn checksum_valid(&self) -> bool {
37        match self {
38            ArchiveHeader::V1(_) => true,
39            ArchiveHeader::V5(header) => header.checksum_valid,
40        }
41    }
42}
43
44#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Copy, Clone)]
45pub enum Algorithm {
46    None,
47    RLE,
48    LZW,
49    Huffman,
50    LZAH,
51    HuffmanFixed,
52    LZMW,
53    LzHuffman,
54    Installer,
55    Arsenic,
56
57    Unknown(u8),
58}
59
60impl BinRead for Algorithm {
61    type Args<'a> = ();
62
63    fn read_options<R: io::Read + io::Seek>(
64        reader: &mut R,
65        _endian: binrw::Endian,
66        _args: Self::Args<'_>,
67    ) -> binrw::BinResult<Self> {
68        Ok(match reader.read_be::<u8>()? & !(128) {
69            0u8 => Self::None,
70            1u8 => Self::RLE,
71            2u8 => Self::LZW,
72            3u8 => Self::Huffman,
73            5u8 => Self::LZAH,
74            6u8 => Self::HuffmanFixed,
75            8u8 => Self::LZMW,
76            13u8 => Self::LzHuffman,
77            14u8 => Self::Installer,
78            15u8 => Self::Arsenic,
79            val => Self::Unknown(val),
80        })
81    }
82}
83impl fmt::Display for Algorithm {
84    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85        match self {
86            Algorithm::RLE => "RLE".fmt(f),
87            Algorithm::None => "None".fmt(f),
88            Algorithm::LZW => "LZW".fmt(f),
89            Algorithm::Huffman => "Huff".fmt(f),
90            Algorithm::LZAH => "LZAH".fmt(f),
91            Algorithm::HuffmanFixed => "HuFi".fmt(f),
92            Algorithm::LZMW => "LZMW".fmt(f),
93            Algorithm::LzHuffman => "LzHu".fmt(f),
94            Algorithm::Installer => "Inst".fmt(f),
95            Algorithm::Arsenic => "Ars".fmt(f),
96            Algorithm::Unknown(m) => format!("Unknown {m}").fmt(f),
97        }
98    }
99}
100
101#[derive(Debug, Clone)]
102pub enum Entry {
103    File(File),
104    Directory(Directory),
105    EndOfDirectory,
106}
107
108#[derive(Debug, Clone)]
109pub enum File {
110    V1(v1::File),
111    V5(v5::File),
112}
113
114impl File {
115    pub fn name(&self) -> &str {
116        match self {
117            File::V1(file) => file.file_name.as_str(),
118            File::V5(file) => file.file_name.as_str(),
119        }
120    }
121
122    pub fn creator(&self) -> FourCC {
123        match self {
124            File::V1(file) => file.creator_code,
125            File::V5(file) => file.creator_code,
126        }
127    }
128
129    pub fn file_code(&self) -> FourCC {
130        match self {
131            File::V1(file) => file.file_code,
132            File::V5(file) => file.file_code,
133        }
134    }
135
136    pub fn comment(&self) -> &str {
137        match self {
138            File::V1(_) => "",
139            File::V5(file) => file.comment.as_str(),
140        }
141    }
142
143    pub fn uses_encryption(&self) -> bool {
144        match self {
145            File::V1(file) => file.uses_encryption(),
146            File::V5(file) => file.uses_encryption(),
147        }
148    }
149
150    #[inline]
151    pub fn has(&self, fork: Fork) -> bool {
152        match self {
153            File::V1(file) => file.compressed_size(fork) != 0,
154            File::V5(file) => file.compressed_size(fork) != 0,
155        }
156    }
157
158    #[inline]
159    pub fn uncompressed_size(&self, fork: Fork) -> usize {
160        match self {
161            File::V1(file) => file.uncompressed_size(fork),
162            File::V5(file) => file.uncompressed_size(fork),
163        }
164    }
165
166    #[inline]
167    pub fn compressed_size(&self, fork: Fork) -> usize {
168        match self {
169            File::V1(file) => file.compressed_size(fork),
170            File::V5(file) => file.compressed_size(fork),
171        }
172    }
173
174    #[inline]
175    pub fn compression_method(&self, fork: Fork) -> Algorithm {
176        match self {
177            File::V1(file) => file.compression_method(fork),
178            File::V5(file) => file.compression_method(fork),
179        }
180    }
181
182    #[inline]
183    pub fn encrypted(&self, fork: Fork) -> bool {
184        match self {
185            File::V1(file) => file.encrypted(fork),
186            File::V5(file) => file.encrypted(fork),
187        }
188    }
189
190    #[inline]
191    pub fn checksum(&self, fork: Fork) -> u16 {
192        match self {
193            File::V1(file) => file.checksum(fork),
194            File::V5(file) => file.checksum(fork),
195        }
196    }
197
198    #[inline]
199    pub fn offset(&self, fork: Fork) -> u64 {
200        match self {
201            File::V1(file) => file.offset(fork),
202            File::V5(file) => file.offset(fork),
203        }
204    }
205
206    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
207        // TODO: use same name for both properties
208        match self {
209            File::V1(file) => file.created_at,
210            File::V5(file) => file.creation_date,
211        }
212    }
213
214    pub fn modified_at(&self) -> chrono::DateTime<chrono::Utc> {
215        // TODO: use same name for both properties
216        match self {
217            File::V1(file) => file.modified_at,
218            File::V5(file) => file.modification_date,
219        }
220    }
221
222    pub fn index(&self) -> usize {
223        match self {
224            File::V1(file) => file.index,
225            File::V5(file) => file.index,
226        }
227    }
228}
229
230#[derive(Debug, Clone)]
231pub enum Directory {
232    V1(v1::Directory),
233    V5(v5::Directory),
234}
235
236impl Directory {
237    #[inline]
238    pub fn name(&self) -> &str {
239        match self {
240            Directory::V1(dir) => &dir.file_name,
241            Directory::V5(dir) => dir.file_name(),
242        }
243    }
244
245    #[inline]
246    pub fn encrypted(&self, _: Fork) -> bool {
247        match self {
248            Directory::V1(_) => false,
249            Directory::V5(dir) => dir.flags.contains(EntryFlags::ENCRYPTED),
250        }
251    }
252
253    #[inline]
254    pub fn algorithm(&self, fork: Fork) -> Algorithm {
255        match self {
256            Directory::V1(dir) => dir.algorithm(fork),
257            Directory::V5(dir) => dir.algorithm(fork),
258        }
259    }
260
261    #[inline]
262    pub fn uncompressed_size(&self, fork: Fork) -> usize {
263        match self {
264            Directory::V1(dir) => dir.uncompressed_size(fork),
265            Directory::V5(dir) => dir.uncompressed_size(fork),
266        }
267    }
268
269    #[inline]
270    pub fn compressed_size(&self, fork: Fork) -> usize {
271        match self {
272            Directory::V1(dir) => dir.compressed_size(fork),
273            Directory::V5(dir) => dir.compressed_size(fork),
274        }
275    }
276
277    #[inline]
278    pub fn offset(&self, fork: Fork) -> u64 {
279        match self {
280            Directory::V1(dir) => dir.offset(fork),
281            Directory::V5(dir) => dir.offset(fork),
282        }
283    }
284
285    #[inline]
286    pub fn checksum(&self, fork: Fork) -> u16 {
287        match self {
288            Directory::V1(dir) => dir.checksum(fork),
289            Directory::V5(dir) => dir.checksum(fork),
290        }
291    }
292
293    #[inline]
294    pub fn has(&self, fork: Fork) -> bool {
295        match self {
296            Directory::V1(dir) => dir.compressed_size(fork) != 0,
297            Directory::V5(dir) => dir.compressed_size(fork) != 0,
298        }
299    }
300
301    pub fn comment(&self) -> &str {
302        match self {
303            Directory::V1(_) => "", // pre-5 versions don't support comments
304            Directory::V5(dir) => dir.comment(),
305        }
306    }
307
308    pub fn uses_encryption(&self) -> bool {
309        match self {
310            Directory::V1(dir) => dir.uses_encryption(),
311            Directory::V5(dir) => dir.uses_encryption(),
312        }
313    }
314}
315
316/// Version of an archive file
317#[derive(BinRead, Debug, PartialEq, PartialOrd, Copy, Clone)]
318#[br(big)]
319pub enum Version {
320    /// Version used by StuffIt 1.5.x and earlier
321    #[br(magic(1u8))]
322    Early,
323    /// Version used by StuffIt 1.6 to 4.5
324    #[br(magic(2u8))]
325    Later,
326    /// Version used by StuffIt 5.x
327    #[br(magic(5u8))]
328    Five,
329    /// Unknown versions
330    Unknown(u8),
331}
332
333impl Version {
334    pub fn short_str(&self) -> &'static str {
335        match self {
336            Version::Early => "1",
337            Version::Later => "2",
338            Version::Five => "5",
339            Version::Unknown(_) => "x",
340        }
341    }
342}
343
344impl fmt::Display for Version {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        match self {
347            Version::Early => f.write_str("1.5.x and earlier"),
348            Version::Later => f.write_str("1.6 to 4.5"),
349            Version::Five => f.write_str("5.x"),
350            Version::Unknown(v) => f.write_fmt(format_args!("Unknown {v}")),
351        }
352    }
353}