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}