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