Skip to main content

macintosh_utils/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use binrw::{binread, BinRead, BinReaderExt};
4use bitflags::bitflags;
5
6pub use chrono;
7pub use fourcc::{self, FourCC};
8
9#[macro_export]
10macro_rules! fourcc {
11    ($($tt:tt)*) => { $crate::fourcc::fourcc_rexport! { $crate::fourcc $($tt)* }
12}}
13
14/// Constants for well-known file type codes
15pub mod type_code {
16    use fourcc::FourCC;
17    /// A macintosh application
18    pub const APPLICATION: FourCC = fourcc::fourcc!("APPL");
19}
20
21#[derive(BinRead, Debug, Copy, Clone)]
22#[br(big)]
23/// Point in 2D space, serializable
24pub struct Point {
25    pub x: i16,
26    pub y: i16,
27}
28
29#[binread]
30/// Common string variant where one-byte denotes the length of the string followed by that many mac-roman encoded characters
31pub struct PascalString {
32    #[br(temp)]
33    len: u8,
34
35    #[br(count(len), map(decode_string))]
36    contents: String,
37}
38
39impl PascalString {
40    pub fn contents(&self) -> String {
41        self.contents.to_string()
42    }
43}
44
45impl From<PascalString> for String {
46    fn from(val: PascalString) -> Self {
47        val.contents
48    }
49}
50
51/// Helper to a allow binrw to read pscal strings more easily
52pub fn string(text: PascalString) -> String {
53    text.contents
54}
55
56/// Helper to a allow binrw to read mac-roman strings more easily
57pub fn decode_string(bytes: Vec<u8>) -> String {
58    encoding_rs::MACINTOSH.decode(&bytes).0.to_string()
59}
60
61/// Helper to a allow binrw to read mac-roman strings more easily
62pub fn decode_string_from_slice(bytes: &[u8]) -> String {
63    encoding_rs::MACINTOSH.decode(bytes).0.to_string()
64}
65
66/// Helper to a allow binrw to read macintosh dates more easily
67///
68/// Macintosh uses the first of January 1904 as its reference date instead of 1970
69pub fn date(seconds_since_1904: u32) -> chrono::DateTime<chrono::Utc> {
70    const SECONDS_FROM_1904_TO_1970: i64 = 2082844800;
71    chrono::DateTime::from_timestamp(seconds_since_1904 as i64 - SECONDS_FROM_1904_TO_1970, 0)
72        .unwrap()
73}
74
75#[derive(Debug, Copy, Clone)]
76/// Identifies either of the two forks that can be used to store data on classic Macintosh
77/// file systems
78pub enum Fork {
79    /// Identifies the _resource fork_ that stores data in a system defined structure
80    Resource,
81    /// Identifies the _data fork_ whose format is entirely up to the application creating it
82    Data,
83}
84
85impl Fork {
86    pub fn is_resource(&self) -> bool {
87        matches!(self, Fork::Resource)
88    }
89
90    pub fn is_data(&self) -> bool {
91        matches!(self, Fork::Data)
92    }
93}
94
95bitflags! {
96    #[derive(Debug, Default, Clone, Copy)]
97    /// Flags from the file information record used by _Finder_ to manage files.
98    ///
99    /// Adopted from [Macintosh Toolbox Essentials, 7-47](https://developer.apple.com/library/archive/documentation/mac/pdf/MacintoshToolboxEssentials.pdf#I25.1.275332)
100    ///
101    ///
102    /// ```
103    /// use macintosh_utils::FinderFlags;
104    ///
105    /// let flags = FinderFlags::from_bits(0x500).unwrap();
106    ///
107    /// assert!(flags.contains(FinderFlags::CUSTOMICON));
108    /// assert!(flags.contains(FinderFlags::INITED));
109    /// assert!(!flags.contains(FinderFlags::INVISIBLE));
110    /// ```
111
112    pub struct FinderFlags: u16 {
113        /// For a file, this bit indicates that the file is an alias file. For directories, this bit is reserved—in which case, set to 0.
114        const ALIAS = (1 << 15);
115        /// The file or directory is invisible from the Finder and from the Standard File Package dialog boxes.
116        const INVISIBLE = (1 << 14);
117        /// For a file, this bit indicates that the file contains a bundle resource. For directories, this bit is reserved – in which case, set to 0.
118        const BUNDLE = (1 << 13);
119        /// The file or directory can't be renamed from the Finder, and the icon cannot be changed.
120        const NAMELOCKED = (1 << 12);
121        /// This flag specifies that a file is a stationery pad and should be treated as a document
122        /// template rather than a document itself
123        const STATIONERY = (1 << 11);
124        /// The file or directory contains a customized icon
125        const CUSTOMICON = (1 << 10);
126        /// Reserved; set to 0.
127        const RESERVED = (1 << 9);
128        /// The Finder has recorded information from the file’s bundle resource into the desktop database and given the file or folder a position on the desktop.
129        const INITED = (1 << 8);
130        /// The file contains no 'INIT' resources; set to 0. Reserved for directories; set to 0.
131        const NOINIT = (1 << 7);
132        /// The file is an application that can be executed by multiple users simultaneously. Defined only for applications; otherwise, set to 0.
133        const SHARED = (1 << 6);
134        /// Unused and reserved in System 7; set to 0.
135        const SWITCH_LAUNCH = (1<<5);
136        /// Unused and reserved in System 7; set to 0.
137        const COLOR_RESERVED = (1<<4);
138        /// Color coding bit 2
139        const COLORBIT2 = (1 << 3);
140        /// Color coding bit 1
141        const COLORBIT1 = (1 << 2);
142        /// Color coding bit 0
143        const COLORBIT0 = (1 << 1);
144        /// Unused and reserved in System 7; set to 0.
145        const ON_DESKTOP = (1 << 0);
146    }
147}
148
149impl BinRead for FinderFlags {
150    type Args<'a> = ();
151
152    fn read_options<R: std::io::Read + std::io::Seek>(
153        reader: &mut R,
154        _endian: binrw::Endian,
155        _args: Self::Args<'_>,
156    ) -> binrw::BinResult<Self> {
157        let flags: u16 = reader.read_be()?;
158        if let Some(flags) = FinderFlags::from_bits(flags) {
159            return Ok(flags);
160        }
161
162        // TODO: consider printing a warning that flags had unknown bits set
163
164        Ok(Default::default())
165    }
166}