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}